agent-comms 3.13.0 → 3.15.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 (50) 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 +2 -0
  5. package/dist/core/bridge-mesh.d.ts.map +1 -1
  6. package/dist/core/bridge-mesh.js +3 -1
  7. package/dist/core/bridge-mesh.js.map +1 -1
  8. package/dist/core/gateway-trust.d.ts +9 -3
  9. package/dist/core/gateway-trust.d.ts.map +1 -1
  10. package/dist/core/gateway-trust.js +20 -2
  11. package/dist/core/gateway-trust.js.map +1 -1
  12. package/dist/core/gossip-directory.d.ts +5 -1
  13. package/dist/core/gossip-directory.d.ts.map +1 -1
  14. package/dist/core/gossip-directory.js +23 -1
  15. package/dist/core/gossip-directory.js.map +1 -1
  16. package/dist/core/hub-forwarding.d.ts +3 -1
  17. package/dist/core/hub-forwarding.d.ts.map +1 -1
  18. package/dist/core/hub-forwarding.js +7 -0
  19. package/dist/core/hub-forwarding.js.map +1 -1
  20. package/dist/core/hub-session.d.ts +3 -1
  21. package/dist/core/hub-session.d.ts.map +1 -1
  22. package/dist/core/hub-session.js +22 -3
  23. package/dist/core/hub-session.js.map +1 -1
  24. package/dist/core/identity-store.d.ts +9 -1
  25. package/dist/core/identity-store.d.ts.map +1 -1
  26. package/dist/core/identity-store.js +30 -1
  27. package/dist/core/identity-store.js.map +1 -1
  28. package/dist/core/mesh-store.d.ts +3 -2
  29. package/dist/core/mesh-store.d.ts.map +1 -1
  30. package/dist/core/mesh-store.js +4 -3
  31. package/dist/core/mesh-store.js.map +1 -1
  32. package/dist/core/room-router.d.ts +2 -2
  33. package/dist/core/room-router.d.ts.map +1 -1
  34. package/dist/core/room-router.js +32 -2
  35. package/dist/core/room-router.js.map +1 -1
  36. package/dist/core/wire-mesh-transport.d.ts +4 -3
  37. package/dist/core/wire-mesh-transport.d.ts.map +1 -1
  38. package/dist/core/wire-mesh-transport.js +11 -29
  39. package/dist/core/wire-mesh-transport.js.map +1 -1
  40. package/dist/test/bridge-mesh.test.js +14 -0
  41. package/dist/test/bridge-mesh.test.js.map +1 -1
  42. package/dist/test/gateway-trust.test.js +58 -1
  43. package/dist/test/gateway-trust.test.js.map +1 -1
  44. package/dist/test/hub-todevice-routing.integration.test.d.ts +7 -0
  45. package/dist/test/hub-todevice-routing.integration.test.d.ts.map +1 -0
  46. package/dist/test/hub-todevice-routing.integration.test.js +114 -0
  47. package/dist/test/hub-todevice-routing.integration.test.js.map +1 -0
  48. package/dist/test/identity-store.test.js +64 -1
  49. package/dist/test/identity-store.test.js.map +1 -1
  50. package/package.json +2 -2
@@ -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": "3.13.0"
18
+ "version": "3.15.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": "3.13.0",
4
+ "version": "3.15.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-3.13.0-blue)](https://github.com/ExaDev/agent-comms/releases/tag/v3.13.0)
5
+ [![version](https://img.shields.io/badge/version-3.15.0-blue)](https://github.com/ExaDev/agent-comms/releases/tag/v3.15.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.
@@ -8,6 +8,8 @@
8
8
  * Also starts this bridge's own VersionDriftChecker (agent-comms#166) and wires its result into the CommsTool it builds, so every real bridge gets npm release-drift reporting on whoami/update for free from this one construction point, with no per-bridge wiring. fetchLatestVersion is exposed purely for tests -- every real caller omits it and gets VersionDriftChecker's own default (a real npm registry lookup); a test that would otherwise trigger real network I/O on every createBridgeMesh call injects a fake resolver instead.
9
9
  *
10
10
  * createBridgeMeshSync/createBridgeMesh own loadOrCreateIdentity's slot lock on the caller's behalf; createBridgeMeshSyncFromIdentity/createBridgeMeshFromIdentity take an already-loaded identity instead and never touch the lock at all -- the cc-peer front (agent-comms#157) uses these directly, via loadIdentityForFront's lock-free load, to build a mesh identity for a not-yet-live session's slot while leaving that slot's own lock free for its real bridge to acquire normally later.
11
+ *
12
+ * Passes slot through to MeshStore's own constructor (agent-comms#186) so the gatewayTrust allowlist it builds loads whatever remote device-ids were trusted before the last restart, and persists every subsequent addTrustedGateway/removeTrustedGateway back to that same slot's storage.
11
13
  */
12
14
  import { MeshStore } from "./mesh-store.js";
13
15
  import { CommsTool } from "./tool.js";
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-mesh.d.ts","sourceRoot":"","sources":["../../src/core/bridge-mesh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAKlD,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,EACxB,MAAM,CAAC,EAAE,MAAM,EACf,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GACrD,cAAc,CAQhB;AAED;;GAEG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,eAAe,CAAC,EAAE,MAAM,EACxB,MAAM,CAAC,EAAE,MAAM,EACf,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GACrD,cAAc,CA8ChB;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,eAAe,CAAC,EAAE,MAAM,EACxB,MAAM,CAAC,EAAE,MAAM,EACf,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GACrD,OAAO,CAAC,UAAU,CAAC,CASrB;AAED,6JAA6J;AAC7J,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,eAAe,CAAC,EAAE,MAAM,EACxB,MAAM,CAAC,EAAE,MAAM,EACf,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GACrD,OAAO,CAAC,UAAU,CAAC,CAUrB"}
1
+ {"version":3,"file":"bridge-mesh.d.ts","sourceRoot":"","sources":["../../src/core/bridge-mesh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAKlD,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,EACxB,MAAM,CAAC,EAAE,MAAM,EACf,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GACrD,cAAc,CAQhB;AAED;;GAEG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,eAAe,CAAC,EAAE,MAAM,EACxB,MAAM,CAAC,EAAE,MAAM,EACf,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GACrD,cAAc,CA8ChB;AAED,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,eAAe,CAAC,EAAE,MAAM,EACxB,MAAM,CAAC,EAAE,MAAM,EACf,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GACrD,OAAO,CAAC,UAAU,CAAC,CASrB;AAED,6JAA6J;AAC7J,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,eAAe,CAAC,EAAE,MAAM,EACxB,MAAM,CAAC,EAAE,MAAM,EACf,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GACrD,OAAO,CAAC,UAAU,CAAC,CAUrB"}
@@ -8,6 +8,8 @@
8
8
  * Also starts this bridge's own VersionDriftChecker (agent-comms#166) and wires its result into the CommsTool it builds, so every real bridge gets npm release-drift reporting on whoami/update for free from this one construction point, with no per-bridge wiring. fetchLatestVersion is exposed purely for tests -- every real caller omits it and gets VersionDriftChecker's own default (a real npm registry lookup); a test that would otherwise trigger real network I/O on every createBridgeMesh call injects a fake resolver instead.
9
9
  *
10
10
  * createBridgeMeshSync/createBridgeMesh own loadOrCreateIdentity's slot lock on the caller's behalf; createBridgeMeshSyncFromIdentity/createBridgeMeshFromIdentity take an already-loaded identity instead and never touch the lock at all -- the cc-peer front (agent-comms#157) uses these directly, via loadIdentityForFront's lock-free load, to build a mesh identity for a not-yet-live session's slot while leaving that slot's own lock free for its real bridge to acquire normally later.
11
+ *
12
+ * Passes slot through to MeshStore's own constructor (agent-comms#186) so the gatewayTrust allowlist it builds loads whatever remote device-ids were trusted before the last restart, and persists every subsequent addTrustedGateway/removeTrustedGateway back to that same slot's storage.
11
13
  */
12
14
  import { deviceIdToHex } from "wire-mesh-core/domain/device-id";
13
15
  import { createSystemClock } from "wire-mesh-core/adapters/system-clock";
@@ -32,7 +34,7 @@ export function createBridgeMeshSyncFromIdentity(identity, slot, coordinatorPort
32
34
  // The user-principal identity (agent-comms#160) is shared by every bridge on this machine account -- deliberately not scoped to slot, unlike identity above. userIdentityOptions is empty (the default ~/.agent-comms location); every real bridge shares it, and only tests need an override.
33
35
  const userIdentityOptions = {};
34
36
  const userIdentity = loadOrCreateUserIdentity(userIdentityOptions);
35
- const store = new MeshStore(coordinatorPort, hubUrl);
37
+ const store = new MeshStore(coordinatorPort, hubUrl, slot);
36
38
  store.peerId = deviceIdToHex(Uint8Array.from(identity.deviceId));
37
39
  // One shared dataStorage instance for both the transport's own data-domain frame responder and the store's own durable-send mint path (P5, agent-comms#50) -- oplogDirFor(slot) needs only the slot, not the async identity below, so this can be constructed synchronously right here.
38
40
  const dataStorage = createNodeFsStorage({ dir: oplogDirFor(slot) });
@@ -1 +1 @@
1
- {"version":3,"file":"bridge-mesh.js","sourceRoot":"","sources":["../../src/core/bridge-mesh.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bridge-mesh.js","sourceRoot":"","sources":["../../src/core/bridge-mesh.ts"],"names":[],"mappings":"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"}
@@ -1,18 +1,24 @@
1
1
  /**
2
2
  * GatewayTrust -- the cross-machine trust boundary (agent-comms#156, agent-comms#153's third leg): an allowlist of remote device-ids this machine's gateway will advertise its local agents to, accept forwarded hub traffic from, and route outbound hub requests to. Deny-all by default: empty until an operator explicitly trusts at least one remote device, the same no-CA pin-the-key model ordinary peer connections already use.
3
3
  *
4
- * In-memory only, deliberately mirroring the precedent set by v1's own FederationManager.trustedFingerprints (retired with federation.ts, commit 4232b08) -- neither persists to disk, so trust is re-established each run rather than carried across restarts. This isn't a gap being deferred: v1 never persisted its own equivalent allowlist either, so no existing behaviour is being narrowed by keeping this one in memory too.
4
+ * Persisted per bridge slot when constructed with one (agent-comms#186), mirroring identity-store.ts's own per-slot room-token/issued-grant persistence: the trusted set is loaded from that slot's own sibling JSON file (identity-store.ts's loadGatewayTrust) on construction, and written back in full (saveGatewayTrust) after every add/remove, so trust survives a gateway restart instead of needing to be re-established every run. The in-memory Set below remains the live source of truth for isTrusted/hasAny at all times; persistence is purely load-on-construct and save-on-mutate. Constructed with no slot, this class keeps the original v1 FederationManager.trustedFingerprints precedent (retired with federation.ts, commit 4232b08): in-memory only, never touching disk. Every pre-#186 construction site (most tests, and any caller with no bridge identity slot to hand) falls into this no-slot case unchanged.
5
5
  *
6
6
  * Keyed by individual device-id, not by "one entry per remote machine": wire-mesh-core's relay-hub protocol (relay-hub.ts, gossip-frame, relay-data-frame) carries no field identifying which remote gateway connection a given directory entry or relayed request actually originated from -- only the entry/request's own device-id, which may be an ordinary local peer forwarded on a remote machine's behalf rather than that machine's own coordinator. Gating per individual device-id is therefore the finest-grained, and only wire-protocol-honest, trust boundary actually implementable without a wire-mesh-core protocol change (deliberately out of scope here, matching agent-comms#156's own "gating the hub itself is out of scope" framing) -- confirmed as the intended granularity by hub-session.ts's own pre-existing isStateMutatingMessage doc comment, which already named this exact gap as "agent-comms#156's own future deliverable" of "per-peer" admission control. An operator who wants every local peer on a remote machine reachable trusts each of that machine's device-ids individually, not just its coordinator's.
7
7
  */
8
+ import type { IdentitySlot } from "./identity-store.js";
8
9
  /** The read-only slice of GatewayTrust every consumer of the trust boundary actually needs (WireMeshTransport, HubSession, hub-forwarding.ts) -- named so call sites that only ever read trust decisions, never mutate them, don't repeat the same `Pick<GatewayTrust, "isTrusted" | "hasAny">` inline at every field/parameter that takes one. */
9
10
  export type GatewayTrustReader = Pick<GatewayTrust, "isTrusted" | "hasAny">;
10
11
  export declare class GatewayTrust {
11
12
  private readonly trusted;
12
- /** Marks a remote device-id (hex, case-insensitive) as trusted: this side will merge its gossiped directory entries, dispatch its relayed requests, and route outbound hub requests to it. Idempotent. */
13
+ private readonly slot;
14
+ /** Constructs the trust boundary, optionally bound to a bridge identity slot for persistence (agent-comms#186); see this class's own doc comment for what a slot does and doesn't change. Given a slot, immediately loads whatever device-ids were trusted before the last restart into the initial in-memory set. */
15
+ constructor(slot?: Readonly<IdentitySlot>);
16
+ /** Marks a remote device-id (hex, case-insensitive) as trusted: this side will merge its gossiped directory entries, dispatch its relayed requests, and route outbound hub requests to it. Idempotent. Persists the updated set when this instance was constructed with a slot. */
13
17
  add(deviceHex: string): void;
14
- /** Withdraws a previously trusted device-id (hex, case-insensitive). A no-op if it was never trusted. Mirrors FederationManager.removeTrustedFingerprint's own precedent: already-merged directory entries and in-flight requests are unaffected -- this governs future traffic only. */
18
+ /** Withdraws a previously trusted device-id (hex, case-insensitive). A no-op if it was never trusted. Mirrors FederationManager.removeTrustedFingerprint's own precedent: already-merged directory entries and in-flight requests are unaffected -- this governs future traffic only. Persists the updated set when this instance was constructed with a slot. */
15
19
  remove(deviceHex: string): void;
20
+ /** Writes the complete current trusted set back to this instance's own slot, if it was constructed with one. A no-op for the in-memory-only (no slot) case. */
21
+ private persist;
16
22
  /** Every currently trusted device-id, lowercase hex, in insertion order. */
17
23
  list(): string[];
18
24
  /** Whether the given device-id (hex, case-insensitive) is currently trusted. */
@@ -1 +1 @@
1
- {"version":3,"file":"gateway-trust.d.ts","sourceRoot":"","sources":["../../src/core/gateway-trust.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,mVAAmV;AACnV,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,YAAY,EAAE,WAAW,GAAG,QAAQ,CAAC,CAAC;AAE5E,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAE7C,0MAA0M;IAC1M,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI5B,yRAAyR;IACzR,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI/B,4EAA4E;IAC5E,IAAI,IAAI,MAAM,EAAE;IAIhB,gFAAgF;IAChF,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIrC;;OAEG;IACH,MAAM,IAAI,OAAO;CAGlB"}
1
+ {"version":3,"file":"gateway-trust.d.ts","sourceRoot":"","sources":["../../src/core/gateway-trust.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,mVAAmV;AACnV,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,YAAY,EAAE,WAAW,GAAG,QAAQ,CAAC,CAAC;AAE5E,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqC;IAE1D,sTAAsT;gBAC1S,IAAI,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC;IASzC,mRAAmR;IACnR,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAK5B,kWAAkW;IAClW,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAK/B,+JAA+J;IAC/J,OAAO,CAAC,OAAO;IAIf,4EAA4E;IAC5E,IAAI,IAAI,MAAM,EAAE;IAIhB,gFAAgF;IAChF,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIrC;;OAEG;IACH,MAAM,IAAI,OAAO;CAGlB"}
@@ -1,12 +1,30 @@
1
+ import { loadGatewayTrust, saveGatewayTrust } from "./identity-store.js";
1
2
  export class GatewayTrust {
2
3
  trusted = new Set();
3
- /** Marks a remote device-id (hex, case-insensitive) as trusted: this side will merge its gossiped directory entries, dispatch its relayed requests, and route outbound hub requests to it. Idempotent. */
4
+ slot;
5
+ /** Constructs the trust boundary, optionally bound to a bridge identity slot for persistence (agent-comms#186); see this class's own doc comment for what a slot does and doesn't change. Given a slot, immediately loads whatever device-ids were trusted before the last restart into the initial in-memory set. */
6
+ constructor(slot) {
7
+ this.slot = slot;
8
+ if (slot !== undefined) {
9
+ for (const deviceHex of loadGatewayTrust(slot)) {
10
+ this.trusted.add(deviceHex);
11
+ }
12
+ }
13
+ }
14
+ /** Marks a remote device-id (hex, case-insensitive) as trusted: this side will merge its gossiped directory entries, dispatch its relayed requests, and route outbound hub requests to it. Idempotent. Persists the updated set when this instance was constructed with a slot. */
4
15
  add(deviceHex) {
5
16
  this.trusted.add(deviceHex.toLowerCase());
17
+ this.persist();
6
18
  }
7
- /** Withdraws a previously trusted device-id (hex, case-insensitive). A no-op if it was never trusted. Mirrors FederationManager.removeTrustedFingerprint's own precedent: already-merged directory entries and in-flight requests are unaffected -- this governs future traffic only. */
19
+ /** Withdraws a previously trusted device-id (hex, case-insensitive). A no-op if it was never trusted. Mirrors FederationManager.removeTrustedFingerprint's own precedent: already-merged directory entries and in-flight requests are unaffected -- this governs future traffic only. Persists the updated set when this instance was constructed with a slot. */
8
20
  remove(deviceHex) {
9
21
  this.trusted.delete(deviceHex.toLowerCase());
22
+ this.persist();
23
+ }
24
+ /** Writes the complete current trusted set back to this instance's own slot, if it was constructed with one. A no-op for the in-memory-only (no slot) case. */
25
+ persist() {
26
+ if (this.slot !== undefined)
27
+ saveGatewayTrust(this.slot, this.list());
10
28
  }
11
29
  /** Every currently trusted device-id, lowercase hex, in insertion order. */
12
30
  list() {
@@ -1 +1 @@
1
- {"version":3,"file":"gateway-trust.js","sourceRoot":"","sources":["../../src/core/gateway-trust.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,YAAY;IACN,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAE7C,0MAA0M;IAC1M,GAAG,CAAC,SAAiB;QACnB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,yRAAyR;IACzR,MAAM,CAAC,SAAiB;QACtB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,4EAA4E;IAC5E,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,gFAAgF;IAChF,SAAS,CAAC,SAAiB;QACzB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,MAAM;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC;IAC/B,CAAC;CACF"}
1
+ {"version":3,"file":"gateway-trust.js","sourceRoot":"","sources":["../../src/core/gateway-trust.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAKzE,MAAM,OAAO,YAAY;IACN,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAC5B,IAAI,CAAqC;IAE1D,sTAAsT;IACtT,YAAY,IAA6B;QACvC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,KAAK,MAAM,SAAS,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;IAED,mRAAmR;IACnR,GAAG,CAAC,SAAiB;QACnB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;QAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAED,kWAAkW;IAClW,MAAM,CAAC,SAAiB;QACtB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAED,+JAA+J;IACvJ,OAAO;QACb,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,4EAA4E;IAC5E,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,gFAAgF;IAChF,SAAS,CAAC,SAAiB;QACzB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,MAAM;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC;IAC/B,CAAC;CACF"}
@@ -1,11 +1,15 @@
1
1
  /**
2
2
  * gossip-directory — the mesh-wide gossip-directory aggregation WireMeshTransport's own listKnownDevices reads from, plus per-event directory lookups (a specific peer's presence advert). Split out purely to keep wire-mesh-transport.ts under the repo's max-lines cap, the same reason connection-approval.ts, room-router.ts, hub-session.ts, and peer-lifecycle.ts were each split from their own owning file.
3
3
  */
4
- import type { DirectoryEntry } from "wire-mesh-core/domain/mesh-session";
4
+ import type { AcceptedMeshSession, DirectoryEntry } from "wire-mesh-core/domain/mesh-session";
5
5
  import type { PeerAdvert } from "wire-mesh-core/generated/protocol";
6
6
  import { AgentStatus } from "./types.js";
7
+ import { type AgentSelfAdvert, type HostedRoomAdvert } from "./wire-mesh-transport.js";
8
+ import type { HubSession } from "./hub-session.js";
7
9
  /** Merges one session event's own directory into the mesh-wide knownDevices view (mutated in place), keeping the newer advert (by snapshot-seconds) whenever a device-id is already known from an earlier event or a different session. */
8
10
  export declare function mergeKnownDevices(knownDevices: Map<string, PeerAdvert>, directory: readonly DirectoryEntry[]): void;
9
11
  /** Reads a presence extension from one specific device's own gossiped self-advert, if this event's directory carries a fresh one for exactly that device-id -- never for any other device-id a multi-hop directory might mention, since only the session's own authenticated peer's advert is that session's business to report. Returns undefined for a missing presence/status key, or a value that isn't a recognised AgentStatus -- an advert simply not participating in this convention, not an error (the same verifier obligation peer-advert's own open extension tail is documented under). */
10
12
  export declare function findPresenceAdvert(deviceIdHex: string, directory: readonly DirectoryEntry[]): AgentStatus | undefined;
13
+ /** Re-sends this side's own current presence status and currently-hosted rooms, together, onto every live session's gossip self-advert -- one gossip frame per tick carrying whichever of the two sources is wired in, rather than a separate frame per fact. Split out of wire-mesh-transport.ts's own WireMeshTransport class purely to keep that file under the repo's max-lines cap, the same reason mergeKnownDevices/findPresenceAdvert above already live here rather than there. A session that fails to send (mid-disconnect, most likely -- watchForDisconnect will independently notice and clean it up) is reported via onError and skipped, not allowed to stop the tick from reaching the rest of allSessions: a periodic broadcast to N peers is N independent operations, not one atomic unit. A no-op tick (neither source wired in, or no sessions exist yet) is expected and silent. The hub's own session (agent-comms#156) is gated separately from every ordinary local-peer session in allSessions: local mesh trust is a different layer (connect_request/introduce approval already gated it before it ever joined allSessions), but the hub session is a broadcast to every connected hub peer, trusted or not, and would otherwise leak this side's own presence/hosted-rooms/self-agent advert onto the hub regardless of GatewayTrust -- forwardAdvertsToHub/pushHubCatchUp's own hasAny gate exists to prevent exactly this for OTHER local peers' adverts, and this side's own self-advert deserves the identical gate, not a bypass. */
14
+ export declare function readvertiseGossip(allSessions: ReadonlySet<AcceptedMeshSession>, hub: Readonly<Pick<HubSession, "ownsSession">>, hasAnyTrustedGateway: () => boolean, onError: ((error: Error) => void) | undefined, getCurrentPresence: (() => AgentStatus | undefined) | undefined, getHostedRooms: (() => readonly HostedRoomAdvert[]) | undefined, getSelfAgentAdvert: (() => AgentSelfAdvert | undefined) | undefined): void;
11
15
  //# sourceMappingURL=gossip-directory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"gossip-directory.d.ts","sourceRoot":"","sources":["../../src/core/gossip-directory.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,2OAA2O;AAC3O,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACrC,SAAS,EAAE,SAAS,cAAc,EAAE,GACnC,IAAI,CAWN;AAED,ykBAAykB;AACzkB,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,SAAS,cAAc,EAAE,GACnC,WAAW,GAAG,SAAS,CAOzB"}
1
+ {"version":3,"file":"gossip-directory.d.ts","sourceRoot":"","sources":["../../src/core/gossip-directory.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EACV,mBAAmB,EACnB,cAAc,EACf,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,2OAA2O;AAC3O,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACrC,SAAS,EAAE,SAAS,cAAc,EAAE,GACnC,IAAI,CAWN;AAED,ykBAAykB;AACzkB,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,SAAS,cAAc,EAAE,GACnC,WAAW,GAAG,SAAS,CAOzB;AAED,w+CAAw+C;AACx+C,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,WAAW,CAAC,mBAAmB,CAAC,EAC7C,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC,EAC9C,oBAAoB,EAAE,MAAM,OAAO,EACnC,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,EAC7C,kBAAkB,EAAE,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC,GAAG,SAAS,EAC/D,cAAc,EAAE,CAAC,MAAM,SAAS,gBAAgB,EAAE,CAAC,GAAG,SAAS,EAC/D,kBAAkB,EAAE,CAAC,MAAM,eAAe,GAAG,SAAS,CAAC,GAAG,SAAS,GAClE,IAAI,CAiBN"}
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { deviceIdToHex } from "wire-mesh-core/domain/device-id";
5
5
  import { AgentStatus } from "./types.js";
6
- import { PRESENCE_GOSSIP_KEY } from "./wire-mesh-transport.js";
6
+ import { AGENT_SELF_GOSSIP_KEY, HOSTED_ROOMS_GOSSIP_KEY, PRESENCE_GOSSIP_KEY, } from "./wire-mesh-transport.js";
7
7
  /** Merges one session event's own directory into the mesh-wide knownDevices view (mutated in place), keeping the newer advert (by snapshot-seconds) whenever a device-id is already known from an earlier event or a different session. */
8
8
  export function mergeKnownDevices(knownDevices, directory) {
9
9
  for (const entry of directory) {
@@ -23,4 +23,26 @@ export function findPresenceAdvert(deviceIdHex, directory) {
23
23
  const status = entry.advert[PRESENCE_GOSSIP_KEY];
24
24
  return AgentStatus.is(status) ? status : undefined;
25
25
  }
26
+ /** Re-sends this side's own current presence status and currently-hosted rooms, together, onto every live session's gossip self-advert -- one gossip frame per tick carrying whichever of the two sources is wired in, rather than a separate frame per fact. Split out of wire-mesh-transport.ts's own WireMeshTransport class purely to keep that file under the repo's max-lines cap, the same reason mergeKnownDevices/findPresenceAdvert above already live here rather than there. A session that fails to send (mid-disconnect, most likely -- watchForDisconnect will independently notice and clean it up) is reported via onError and skipped, not allowed to stop the tick from reaching the rest of allSessions: a periodic broadcast to N peers is N independent operations, not one atomic unit. A no-op tick (neither source wired in, or no sessions exist yet) is expected and silent. The hub's own session (agent-comms#156) is gated separately from every ordinary local-peer session in allSessions: local mesh trust is a different layer (connect_request/introduce approval already gated it before it ever joined allSessions), but the hub session is a broadcast to every connected hub peer, trusted or not, and would otherwise leak this side's own presence/hosted-rooms/self-agent advert onto the hub regardless of GatewayTrust -- forwardAdvertsToHub/pushHubCatchUp's own hasAny gate exists to prevent exactly this for OTHER local peers' adverts, and this side's own self-advert deserves the identical gate, not a bypass. */
27
+ export function readvertiseGossip(allSessions, hub, hasAnyTrustedGateway, onError, getCurrentPresence, getHostedRooms, getSelfAgentAdvert) {
28
+ const extensions = {};
29
+ const status = getCurrentPresence?.();
30
+ if (status !== undefined)
31
+ extensions[PRESENCE_GOSSIP_KEY] = status;
32
+ const hostedRooms = getHostedRooms?.();
33
+ if (hostedRooms !== undefined)
34
+ extensions[HOSTED_ROOMS_GOSSIP_KEY] = hostedRooms;
35
+ const selfAgentAdvert = getSelfAgentAdvert?.();
36
+ if (selfAgentAdvert !== undefined)
37
+ extensions[AGENT_SELF_GOSSIP_KEY] = selfAgentAdvert;
38
+ if (Object.keys(extensions).length === 0)
39
+ return;
40
+ for (const session of allSessions) {
41
+ if (hub.ownsSession(session) && !hasAnyTrustedGateway())
42
+ continue;
43
+ session.sendGossipUpdate(extensions).catch((error) => {
44
+ onError?.(error instanceof Error ? error : new Error(String(error)));
45
+ });
46
+ }
47
+ }
26
48
  //# sourceMappingURL=gossip-directory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gossip-directory.js","sourceRoot":"","sources":["../../src/core/gossip-directory.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGhE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,2OAA2O;AAC3O,MAAM,UAAU,iBAAiB,CAC/B,YAAqC,EACrC,SAAoC;IAEpC,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC9B,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC/C,IACE,QAAQ,KAAK,SAAS;YACtB,KAAK,CAAC,MAAM,CAAC,kBAAkB,CAAC,IAAI,QAAQ,CAAC,kBAAkB,CAAC,EAChE,CAAC;YACD,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;AACH,CAAC;AAED,ykBAAykB;AACzkB,MAAM,UAAU,kBAAkB,CAChC,WAAmB,EACnB,SAAoC;IAEpC,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAC1B,CAAC,SAAS,EAAE,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,WAAW,CAC/D,CAAC;IACF,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,MAAM,GAAY,KAAK,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC1D,OAAO,WAAW,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACrD,CAAC"}
1
+ {"version":3,"file":"gossip-directory.js","sourceRoot":"","sources":["../../src/core/gossip-directory.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAMhE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,GAGpB,MAAM,0BAA0B,CAAC;AAGlC,2OAA2O;AAC3O,MAAM,UAAU,iBAAiB,CAC/B,YAAqC,EACrC,SAAoC;IAEpC,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC9B,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC/C,IACE,QAAQ,KAAK,SAAS;YACtB,KAAK,CAAC,MAAM,CAAC,kBAAkB,CAAC,IAAI,QAAQ,CAAC,kBAAkB,CAAC,EAChE,CAAC;YACD,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;AACH,CAAC;AAED,ykBAAykB;AACzkB,MAAM,UAAU,kBAAkB,CAChC,WAAmB,EACnB,SAAoC;IAEpC,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAC1B,CAAC,SAAS,EAAE,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,WAAW,CAC/D,CAAC;IACF,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,MAAM,GAAY,KAAK,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC1D,OAAO,WAAW,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACrD,CAAC;AAED,w+CAAw+C;AACx+C,MAAM,UAAU,iBAAiB,CAC/B,WAA6C,EAC7C,GAA8C,EAC9C,oBAAmC,EACnC,OAA6C,EAC7C,kBAA+D,EAC/D,cAA+D,EAC/D,kBAAmE;IAEnE,MAAM,UAAU,GAA4B,EAAE,CAAC;IAC/C,MAAM,MAAM,GAAG,kBAAkB,EAAE,EAAE,CAAC;IACtC,IAAI,MAAM,KAAK,SAAS;QAAE,UAAU,CAAC,mBAAmB,CAAC,GAAG,MAAM,CAAC;IACnE,MAAM,WAAW,GAAG,cAAc,EAAE,EAAE,CAAC;IACvC,IAAI,WAAW,KAAK,SAAS;QAC3B,UAAU,CAAC,uBAAuB,CAAC,GAAG,WAAW,CAAC;IACpD,MAAM,eAAe,GAAG,kBAAkB,EAAE,EAAE,CAAC;IAC/C,IAAI,eAAe,KAAK,SAAS;QAC/B,UAAU,CAAC,qBAAqB,CAAC,GAAG,eAAe,CAAC;IACtD,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACjD,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;QAClC,IAAI,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE;YAAE,SAAS;QAClE,OAAO,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC5D,OAAO,EAAE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACvE,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
@@ -2,12 +2,14 @@
2
2
  * hub-forwarding -- agent-comms#155's gateway forwarding, split from wire-mesh-transport.ts under the repo's max-lines cap. Outbound: filters a directory of gossiped local devices down to the ones eligible for cross-machine advertisement (an agent/self-bearing entry, meaning only ever a "visible" agent -- see AGENT_SELF_GOSSIP_KEY's own doc for why no separate visibility check is needed here) and pushes them onto a connected HubSession, best-effort. Local-to-remote routing: falls a room-domain request back through the hub's own relay-connect/relay-data pairing when its target isn't a local peer session.
3
3
  */
4
4
  import type { CapabilityScope, CapabilityToken, ManageCommand, PeerAdvert } from "wire-mesh-core/generated/protocol";
5
- import type { DirectoryEntry, ManageOutcome } from "wire-mesh-core/domain/mesh-session";
5
+ import type { AcceptedMeshSession, DirectoryEntry, ManageOutcome } from "wire-mesh-core/domain/mesh-session";
6
6
  import type { HubSession } from "./hub-session.js";
7
7
  /** Forwards every directory entry carrying an agent/self extension onto hub, if hub currently holds a live connection AND at least one remote gateway is currently trusted -- a no-op otherwise. The trust gate (agent-comms#156, GatewayTrust.hasAny's own doc) is coarse by necessity: wire-mesh-core's relay-hub broadcasts a gossip frame to every connected peer with no per-recipient targeting, so "advertise only to allowlisted remote gateways" can only be approximated as "advertise nothing at all until the operator has trusted someone" -- it is not a per-recipient filter. Called both from WireMeshTransport's own watchForDisconnect (as each local peer session's directory changes) and from connectHub's own initial catch-up push, so this side's already-known local devices reach the hub immediately on taking over the gateway role rather than waiting for their own next periodic gossip tick. Forwards unconditionally on every call once past the gates above (no caching or dedup against a previous call), mirroring relay-hub.ts's own "forward every gossip frame, as received" philosophy: an advert's own snapshot-seconds/addresses are meant to keep propagating as a liveness heartbeat, so suppressing a "duplicate" would silently stop legitimate freshness updates from reaching remote gateways. A forward that fails is reported via onError and swallowed, matching every other best-effort gossip send in this codebase. */
8
8
  export declare function forwardAdvertsToHub(hub: Readonly<Pick<HubSession, "isConnected" | "advertiseDevices">>, directory: readonly DirectoryEntry[], onError: ((error: Error) => void) | undefined, hasAnyTrustedGateway: () => boolean): void;
9
9
  /** Catches the hub up with every local device already known at the moment this side becomes the gateway (connectHub's own trailing call, right after hub.connect resolves) -- without this, a device whose own last gossip arrived before this coordinator took over the gateway role would never be (re-)advertised until its own next periodic gossip tick (hub-side state is rebuilt from scratch on every takeover, per coordinator-gateway.ts's own class doc). Reuses forwardAdvertsToHub's own eligibility filter and trust gate, so only ever a visible, agent/self-bearing device is pushed, and only once a remote gateway is trusted, exactly as an ordinary directory-change forward would. */
10
10
  export declare function pushHubCatchUp(hub: Readonly<Pick<HubSession, "isConnected" | "advertiseDevices">>, knownDevices: ReadonlyMap<string, Readonly<PeerAdvert>>, onError: ((error: Error) => void) | undefined, hasAnyTrustedGateway: () => boolean): void;
11
+ /** Sends a room-domain manage-request to a LOCAL peer session only (peerSessions), never falling back to hub routing -- HubSession's own toDevice-forwarding leg (agent-comms#184: a hub-relayed request explicitly addressed to a non-gateway local peer this gateway also fronts), wired in as WireMeshTransport's forwardToLocalPeer dependency. Returns undefined when no local session exists for that device-id, in which case HubSession falls back to dispatching the request against this gateway's own local state instead. */
12
+ export declare function sendToLocalPeer(peerSessions: ReadonlyMap<string, AcceptedMeshSession>, memberId: string, command: ManageCommand, scope: Readonly<CapabilityScope>, token?: CapabilityToken): Promise<ManageOutcome> | undefined;
11
13
  /** Routes a room-domain request through the hub's relay-connect/relay-data pairing when memberId isn't a local peer session -- WireMeshTransport.sendRoomRequest's own fallback, since the member may be a remote agent reachable only via this machine's gateway connection (agent-comms#155's local-to-remote leg). WireMeshTransport.sendRoomRequest itself gates memberId against the gateway trust boundary (agent-comms#156) before ever calling this, so by the time this runs memberId is already known-trusted -- this function stays focused on the hub-connectivity outcome alone. Resolves the same not_connected outcome sendRoomRequest already returned before the hub existed at all when this side isn't currently the gateway. */
12
14
  export declare function routeRoomRequestViaHub(hub: Readonly<Pick<HubSession, "isConnected" | "sendRoomRequest">>, memberId: string, command: ManageCommand, scope: Readonly<CapabilityScope>, token?: CapabilityToken): Promise<ManageOutcome>;
13
15
  /** Dials the hub and immediately pushes a catch-up of every already-known local device onto it (agent-comms#154's own hub-connection-establishment sequence, kept together here rather than split across two call-site statements in WireMeshTransport.connectHub) -- without the trailing catch-up, a device whose own last gossip arrived before this coordinator took over the gateway role would stay invisible on the hub until its own next periodic gossip tick. */
@@ -1 +1 @@
1
- {"version":3,"file":"hub-forwarding.d.ts","sourceRoot":"","sources":["../../src/core/hub-forwarding.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,aAAa,EACb,UAAU,EACX,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACd,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,64CAA64C;AAC74C,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,kBAAkB,CAAC,CAAC,EACnE,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,EAC7C,oBAAoB,EAAE,MAAM,OAAO,GAClC,IAAI,CAUN;AAED,2qBAA2qB;AAC3qB,wBAAgB,cAAc,CAC5B,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,kBAAkB,CAAC,CAAC,EACnE,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,EACvD,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,EAC7C,oBAAoB,EAAE,MAAM,OAAO,GAClC,IAAI,CAMN;AAED,otBAAotB;AACptB,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,iBAAiB,CAAC,CAAC,EAClE,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,EAChC,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,aAAa,CAAC,CAGxB;AAED,2cAA2c;AAC3c,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,QAAQ,CACX,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,kBAAkB,GAAG,SAAS,CAAC,CACjE,EACD,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,EACvD,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,EAC7C,oBAAoB,EAAE,MAAM,OAAO,GAClC,OAAO,CAAC,IAAI,CAAC,CAGf"}
1
+ {"version":3,"file":"hub-forwarding.d.ts","sourceRoot":"","sources":["../../src/core/hub-forwarding.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,aAAa,EACb,UAAU,EACX,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EACV,mBAAmB,EACnB,cAAc,EACd,aAAa,EACd,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,64CAA64C;AAC74C,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,kBAAkB,CAAC,CAAC,EACnE,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,EAC7C,oBAAoB,EAAE,MAAM,OAAO,GAClC,IAAI,CAUN;AAED,2qBAA2qB;AAC3qB,wBAAgB,cAAc,CAC5B,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,kBAAkB,CAAC,CAAC,EACnE,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,EACvD,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,EAC7C,oBAAoB,EAAE,MAAM,OAAO,GAClC,IAAI,CAMN;AAED,ygBAAygB;AACzgB,wBAAgB,eAAe,CAC7B,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACtD,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,EAChC,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,aAAa,CAAC,GAAG,SAAS,CAIpC;AAED,otBAAotB;AACptB,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,iBAAiB,CAAC,CAAC,EAClE,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,EAChC,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,aAAa,CAAC,CAGxB;AAED,2cAA2c;AAC3c,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,QAAQ,CACX,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,kBAAkB,GAAG,SAAS,CAAC,CACjE,EACD,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,EACvD,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,EAC7C,oBAAoB,EAAE,MAAM,OAAO,GAClC,OAAO,CAAC,IAAI,CAAC,CAGf"}
@@ -23,6 +23,13 @@ export function pushHubCatchUp(hub, knownDevices, onError, hasAnyTrustedGateway)
23
23
  }));
24
24
  forwardAdvertsToHub(hub, catchUp, onError, hasAnyTrustedGateway);
25
25
  }
26
+ /** Sends a room-domain manage-request to a LOCAL peer session only (peerSessions), never falling back to hub routing -- HubSession's own toDevice-forwarding leg (agent-comms#184: a hub-relayed request explicitly addressed to a non-gateway local peer this gateway also fronts), wired in as WireMeshTransport's forwardToLocalPeer dependency. Returns undefined when no local session exists for that device-id, in which case HubSession falls back to dispatching the request against this gateway's own local state instead. */
27
+ export function sendToLocalPeer(peerSessions, memberId, command, scope, token) {
28
+ const session = peerSessions.get(memberId);
29
+ if (session === undefined)
30
+ return undefined;
31
+ return session.sendManageRequest(command, scope, undefined, token);
32
+ }
26
33
  /** Routes a room-domain request through the hub's relay-connect/relay-data pairing when memberId isn't a local peer session -- WireMeshTransport.sendRoomRequest's own fallback, since the member may be a remote agent reachable only via this machine's gateway connection (agent-comms#155's local-to-remote leg). WireMeshTransport.sendRoomRequest itself gates memberId against the gateway trust boundary (agent-comms#156) before ever calling this, so by the time this runs memberId is already known-trusted -- this function stays focused on the hub-connectivity outcome alone. Resolves the same not_connected outcome sendRoomRequest already returned before the hub existed at all when this side isn't currently the gateway. */
27
34
  export async function routeRoomRequestViaHub(hub, memberId, command, scope, token) {
28
35
  if (!hub.isConnected)
@@ -1 +1 @@
1
- {"version":3,"file":"hub-forwarding.js","sourceRoot":"","sources":["../../src/core/hub-forwarding.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,64CAA64C;AAC74C,MAAM,UAAU,mBAAmB,CACjC,GAAmE,EACnE,SAAoC,EACpC,OAA6C,EAC7C,oBAAmC;IAEnC,IAAI,CAAC,GAAG,CAAC,WAAW;QAAE,OAAO;IAC7B,IAAI,CAAC,oBAAoB,EAAE;QAAE,OAAO;IACpC,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAC/B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,qBAAqB,CAAC,KAAK,SAAS,CAC7D,CAAC;IACF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAClC,GAAG,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QACtD,OAAO,EAAE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,2qBAA2qB;AAC3qB,MAAM,UAAU,cAAc,CAC5B,GAAmE,EACnE,YAAuD,EACvD,OAA6C,EAC7C,oBAAmC;IAEnC,MAAM,OAAO,GAAG,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC1D,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM;KACP,CAAC,CAAC,CAAC;IACJ,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,oBAAoB,CAAC,CAAC;AACnE,CAAC;AAED,otBAAotB;AACptB,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,GAAkE,EAClE,QAAgB,EAChB,OAAsB,EACtB,KAAgC,EAChC,KAAuB;IAEvB,IAAI,CAAC,GAAG,CAAC,WAAW;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;IACxE,OAAO,GAAG,CAAC,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED,2cAA2c;AAC3c,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAEC,EACD,GAAW,EACX,YAAuD,EACvD,OAA6C,EAC7C,oBAAmC;IAEnC,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACvB,cAAc,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,oBAAoB,CAAC,CAAC;AACnE,CAAC"}
1
+ {"version":3,"file":"hub-forwarding.js","sourceRoot":"","sources":["../../src/core/hub-forwarding.ts"],"names":[],"mappings":"AAAA;;GAEG;AAcH,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,64CAA64C;AAC74C,MAAM,UAAU,mBAAmB,CACjC,GAAmE,EACnE,SAAoC,EACpC,OAA6C,EAC7C,oBAAmC;IAEnC,IAAI,CAAC,GAAG,CAAC,WAAW;QAAE,OAAO;IAC7B,IAAI,CAAC,oBAAoB,EAAE;QAAE,OAAO;IACpC,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAC/B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,qBAAqB,CAAC,KAAK,SAAS,CAC7D,CAAC;IACF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAClC,GAAG,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QACtD,OAAO,EAAE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,2qBAA2qB;AAC3qB,MAAM,UAAU,cAAc,CAC5B,GAAmE,EACnE,YAAuD,EACvD,OAA6C,EAC7C,oBAAmC;IAEnC,MAAM,OAAO,GAAG,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC1D,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM;KACP,CAAC,CAAC,CAAC;IACJ,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,oBAAoB,CAAC,CAAC;AACnE,CAAC;AAED,ygBAAygB;AACzgB,MAAM,UAAU,eAAe,CAC7B,YAAsD,EACtD,QAAgB,EAChB,OAAsB,EACtB,KAAgC,EAChC,KAAuB;IAEvB,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC5C,OAAO,OAAO,CAAC,iBAAiB,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;AACrE,CAAC;AAED,otBAAotB;AACptB,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,GAAkE,EAClE,QAAgB,EAChB,OAAsB,EACtB,KAAgC,EAChC,KAAuB;IAEvB,IAAI,CAAC,GAAG,CAAC,WAAW;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;IACxE,OAAO,GAAG,CAAC,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED,2cAA2c;AAC3c,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAEC,EACD,GAAW,EACX,YAAuD,EACvD,OAA6C,EAC7C,oBAAmC;IAEnC,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACvB,cAAc,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,oBAAoB,CAAC,CAAC;AACnE,CAAC"}
@@ -25,6 +25,8 @@ export interface HubSessionDeps {
25
25
  handleRoomRequest: (request: IncomingManageRequest, handle: Readonly<ConnectionHandle>) => Promise<void>;
26
26
  /** The gateway trust boundary (agent-comms#156): whether the given device-id (hex) is currently trusted. Checked against every gossiped directory entry's own device and every relayed request's own fromDevice before this side merges or dispatches it -- see consume()/connect()'s own doc comments for exactly where and why. */
27
27
  isTrusted: (deviceHex: string) => boolean;
28
+ /** Forwards a room-domain manage-request on to a specific LOCAL peer session (one this gateway is directly connected to over the ordinary local mesh, keyed by device-id hex) rather than dispatching it against this gateway's own local state -- consume()'s own toDevice disambiguation (agent-comms#184, wire-mesh-core 1.48.1's IncomingManageRequest.toDevice). Returns undefined when no local session exists for that device-id, in which case consume() falls back to handleRoomRequest exactly as it always has. */
29
+ forwardToLocalPeer: (deviceHex: string, command: ManageCommand, scope: Readonly<CapabilityScope>, token?: CapabilityToken) => Promise<ManageOutcome> | undefined;
28
30
  }
29
31
  export declare class HubSession {
30
32
  private readonly deps;
@@ -45,7 +47,7 @@ export declare class HubSession {
45
47
  peers(): readonly string[];
46
48
  /** Dials the hub and participates as a peer (see the class doc for the discovery and routing model). */
47
49
  connect(url: string): Promise<void>;
48
- /** Dispatches inbound relayed manage-requests: each is handled with a handle keyed by the SENDING device (request.fromDevice names it on relay-routed requests), so every downstream consumer sees the true origin, never the hub -- the same discipline extends to a real room-domain verb (agent-comms#155's "remote to local" leg) as it already applied to the legacy opaque-frame path. Every request is first checked against the gateway trust boundary (agent-comms#156, deps.isTrusted): a request with no fromDevice at all (senderHex falls back to the literal string "hub-peer", never a real trusted device-id) or an unrecognised fromDevice is never dispatched to either path below -- a legacy FRAME_VERB message is silently dropped (matching isStateMutatingMessage's own swallow-and-ack style, so an untrusted sender learns nothing about why), and a room-domain request gets an explicit `unauthorized` error rather than being dispatched, so its caller fails fast instead of waiting out HUB_ROOM_REQUEST_TIMEOUT_MS's local-session-side counterpart. A legacy FRAME_VERB carrying state_sync/state_update is dropped before ever reaching onMessage/applyPatch even from an otherwise-trusted sender -- see isStateMutatingMessage's own doc for why: gateway trust says "this device's traffic is worth acting on," not "this device may directly overwrite this side's mesh state," which is a strictly stronger claim the trust boundary here was never meant to grant (a security review finding on agent-comms#169). A real room-domain verb (room.send, room.join, room.notify, ...) carries no equivalent risk -- it is independently gated by its own room:member capability token, verified regardless of which transport path it arrived over -- so a trusted sender's request is dispatched to the same roomVerbHandlers a local peer session's own drainSession uses, via handleRoomRequest, against THIS side's own local mesh state. Known limitation, inherited from wire-mesh-core's own session layer rather than something agent-comms can fix here: a session tracks at most one active relay pairing per remote device (mesh-session.ts's own single relayPeerDevice slot), so a request relayed here is dispatched as "addressed to this gateway's own agent" unconditionally -- there is no target-device disambiguation available to route it on to a DIFFERENT local peer this gateway also advertises. Forwarding this gateway's own agent's traffic is therefore correct; a remote request genuinely meant for another local peer behind this same gateway is not yet distinguishable from one meant for this gateway's own agent. */
50
+ /** Dispatches inbound relayed manage-requests: each is handled with a handle keyed by the SENDING device (request.fromDevice names it on relay-routed requests), so every downstream consumer sees the true origin, never the hub -- the same discipline extends to a real room-domain verb (agent-comms#155's "remote to local" leg) as it already applied to the legacy opaque-frame path. Every request is first checked against the gateway trust boundary (agent-comms#156, deps.isTrusted): a request with no fromDevice at all (senderHex falls back to the literal string "hub-peer", never a real trusted device-id) or an unrecognised fromDevice is never dispatched to either path below -- a legacy FRAME_VERB message is silently dropped (matching isStateMutatingMessage's own swallow-and-ack style, so an untrusted sender learns nothing about why), and a room-domain request gets an explicit `unauthorized` error rather than being dispatched, so its caller fails fast instead of waiting out HUB_ROOM_REQUEST_TIMEOUT_MS's local-session-side counterpart. A legacy FRAME_VERB carrying state_sync/state_update is dropped before ever reaching onMessage/applyPatch even from an otherwise-trusted sender -- see isStateMutatingMessage's own doc for why: gateway trust says "this device's traffic is worth acting on," not "this device may directly overwrite this side's mesh state," which is a strictly stronger claim the trust boundary here was never meant to grant (a security review finding on agent-comms#169). A real room-domain verb (room.send, room.join, room.notify, ...) carries no equivalent risk -- it is independently gated by its own room:member capability token, verified regardless of which transport path it arrived over. Multi-device gateway routing (agent-comms#184, wire-mesh-core 1.48.1's own IncomingManageRequest.toDevice, read directly from each relay-data frame's own to-device field rather than guessed from pairing state): when the request carries a toDevice that names a different device than ownDeviceHex, the command is first re-stamped with an "on-behalf-of" params field naming senderHex (already verified trusted above) before deps.forwardToLocalPeer forwards it on to that device's own local mesh session (one this gateway is directly connected to, never merely gossiped-about) -- room-router.ts's own resolveHandle reads that field back out on the receiving end, so the forwarded request is attributed to the true remote sender there, not to this gateway, matching this same method's own "every downstream consumer sees the true origin, never the hub" discipline for the local hop too. Its outcome is relayed straight back. Only when forwardToLocalPeer finds no such local session (toDevice is absent, matches ownDeviceHex, or names a device this gateway doesn't actually front) does the request fall through to handleRoomRequest, dispatched to the same roomVerbHandlers a local peer session's own drainSession uses, against THIS side's own local mesh state -- correct for traffic genuinely addressed to this gateway's own agent, and the same fallback a sender still on a pre-#184 wire-mesh-core (never stamping toDevice at all) already relied on. */
49
51
  private consume;
50
52
  private watchDisconnect;
51
53
  /** Sends one message to a hub-discovered peer through the hub's relay pairing. */
@@ -1 +1 @@
1
- {"version":3,"file":"hub-session.d.ts","sourceRoot":"","sources":["../../src/core/hub-session.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EACnB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,KAAK,EACL,aAAa,EACd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAatD,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,aAAa,EAAE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;IAC/C,MAAM,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IAClC,yHAAyH;IACzH,cAAc,EAAE,MAAM,OAAO,CAAC;IAC9B,wEAAwE;IACxE,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,sIAAsI;IACtI,OAAO,EAAE,CACP,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,EAChC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,KACnB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,2FAA2F;IAC3F,gBAAgB,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACzD,uZAAuZ;IACvZ,WAAW,EAAE,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,KAAK,IAAI,CAAC;IAC1D,kgBAAkgB;IAClgB,iBAAiB,EAAE,CACjB,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC,KAC/B,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,qUAAqU;IACrU,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CAC3C;AAED,qBAAa,UAAU;IAMT,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,OAAO,CAAC,OAAO,CAAkC;IACjD,6TAA6T;IAC7T,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;gBAEtB,IAAI,EAAE,QAAQ,CAAC,cAAc,CAAC;IAE3D,wFAAwF;IAClF,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAKrC,kOAAkO;IAClO,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,kVAAkV;IAClV,WAAW,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO;IAIlD,mKAAmK;IAC7J,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC,qFAAqF;IACrF,KAAK,IAAI,SAAS,MAAM,EAAE;IAI1B,wGAAwG;IAClG,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyCzC,+hFAA+hF;IAC/hF,OAAO,CAAC,OAAO;YAiCD,eAAe;IAU7B,kFAAkF;IAC5E,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAY5E,svBAAsvB;IAChvB,gBAAgB,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAWzE,6iBAA6iB;IACviB,eAAe,CACnB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,EAChC,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,aAAa,CAAC;CAa1B"}
1
+ {"version":3,"file":"hub-session.d.ts","sourceRoot":"","sources":["../../src/core/hub-session.ts"],"names":[],"mappings":"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"}
@@ -79,13 +79,13 @@ export class HubSession {
79
79
  break;
80
80
  }
81
81
  })();
82
- this.consume(session);
82
+ this.consume(session, deviceIdToHex(identity.deviceId));
83
83
  void (async () => {
84
84
  await this.watchDisconnect(session);
85
85
  })();
86
86
  }
87
- /** Dispatches inbound relayed manage-requests: each is handled with a handle keyed by the SENDING device (request.fromDevice names it on relay-routed requests), so every downstream consumer sees the true origin, never the hub -- the same discipline extends to a real room-domain verb (agent-comms#155's "remote to local" leg) as it already applied to the legacy opaque-frame path. Every request is first checked against the gateway trust boundary (agent-comms#156, deps.isTrusted): a request with no fromDevice at all (senderHex falls back to the literal string "hub-peer", never a real trusted device-id) or an unrecognised fromDevice is never dispatched to either path below -- a legacy FRAME_VERB message is silently dropped (matching isStateMutatingMessage's own swallow-and-ack style, so an untrusted sender learns nothing about why), and a room-domain request gets an explicit `unauthorized` error rather than being dispatched, so its caller fails fast instead of waiting out HUB_ROOM_REQUEST_TIMEOUT_MS's local-session-side counterpart. A legacy FRAME_VERB carrying state_sync/state_update is dropped before ever reaching onMessage/applyPatch even from an otherwise-trusted sender -- see isStateMutatingMessage's own doc for why: gateway trust says "this device's traffic is worth acting on," not "this device may directly overwrite this side's mesh state," which is a strictly stronger claim the trust boundary here was never meant to grant (a security review finding on agent-comms#169). A real room-domain verb (room.send, room.join, room.notify, ...) carries no equivalent risk -- it is independently gated by its own room:member capability token, verified regardless of which transport path it arrived over -- so a trusted sender's request is dispatched to the same roomVerbHandlers a local peer session's own drainSession uses, via handleRoomRequest, against THIS side's own local mesh state. Known limitation, inherited from wire-mesh-core's own session layer rather than something agent-comms can fix here: a session tracks at most one active relay pairing per remote device (mesh-session.ts's own single relayPeerDevice slot), so a request relayed here is dispatched as "addressed to this gateway's own agent" unconditionally -- there is no target-device disambiguation available to route it on to a DIFFERENT local peer this gateway also advertises. Forwarding this gateway's own agent's traffic is therefore correct; a remote request genuinely meant for another local peer behind this same gateway is not yet distinguishable from one meant for this gateway's own agent. */
88
- consume(session) {
87
+ /** Dispatches inbound relayed manage-requests: each is handled with a handle keyed by the SENDING device (request.fromDevice names it on relay-routed requests), so every downstream consumer sees the true origin, never the hub -- the same discipline extends to a real room-domain verb (agent-comms#155's "remote to local" leg) as it already applied to the legacy opaque-frame path. Every request is first checked against the gateway trust boundary (agent-comms#156, deps.isTrusted): a request with no fromDevice at all (senderHex falls back to the literal string "hub-peer", never a real trusted device-id) or an unrecognised fromDevice is never dispatched to either path below -- a legacy FRAME_VERB message is silently dropped (matching isStateMutatingMessage's own swallow-and-ack style, so an untrusted sender learns nothing about why), and a room-domain request gets an explicit `unauthorized` error rather than being dispatched, so its caller fails fast instead of waiting out HUB_ROOM_REQUEST_TIMEOUT_MS's local-session-side counterpart. A legacy FRAME_VERB carrying state_sync/state_update is dropped before ever reaching onMessage/applyPatch even from an otherwise-trusted sender -- see isStateMutatingMessage's own doc for why: gateway trust says "this device's traffic is worth acting on," not "this device may directly overwrite this side's mesh state," which is a strictly stronger claim the trust boundary here was never meant to grant (a security review finding on agent-comms#169). A real room-domain verb (room.send, room.join, room.notify, ...) carries no equivalent risk -- it is independently gated by its own room:member capability token, verified regardless of which transport path it arrived over. Multi-device gateway routing (agent-comms#184, wire-mesh-core 1.48.1's own IncomingManageRequest.toDevice, read directly from each relay-data frame's own to-device field rather than guessed from pairing state): when the request carries a toDevice that names a different device than ownDeviceHex, the command is first re-stamped with an "on-behalf-of" params field naming senderHex (already verified trusted above) before deps.forwardToLocalPeer forwards it on to that device's own local mesh session (one this gateway is directly connected to, never merely gossiped-about) -- room-router.ts's own resolveHandle reads that field back out on the receiving end, so the forwarded request is attributed to the true remote sender there, not to this gateway, matching this same method's own "every downstream consumer sees the true origin, never the hub" discipline for the local hop too. Its outcome is relayed straight back. Only when forwardToLocalPeer finds no such local session (toDevice is absent, matches ownDeviceHex, or names a device this gateway doesn't actually front) does the request fall through to handleRoomRequest, dispatched to the same roomVerbHandlers a local peer session's own drainSession uses, against THIS side's own local mesh state -- correct for traffic genuinely addressed to this gateway's own agent, and the same fallback a sender still on a pre-#184 wire-mesh-core (never stamping toDevice at all) already relied on. */
88
+ consume(session, ownDeviceHex) {
89
89
  void (async () => {
90
90
  for await (const request of session.incomingManageRequests) {
91
91
  if (this.deps.isShuttingDown())
@@ -113,6 +113,25 @@ export class HubSession {
113
113
  await request.respond({ result: "ok" }).catch(() => undefined);
114
114
  continue;
115
115
  }
116
+ const toDeviceHex = request.toDevice !== undefined
117
+ ? deviceIdToHex(request.toDevice)
118
+ : undefined;
119
+ if (toDeviceHex !== undefined && toDeviceHex !== ownDeviceHex) {
120
+ // Stamps the already-verified true sender (senderHex -- trusted above, never the "hub-peer" fallback, since an untrusted or fromDevice-less request already continued away) onto the forwarded command's own params, so the local peer's own resolveHandle (room-router.ts) can attribute the request to senderHex instead of this side's own device once it arrives over that peer's ordinary local-mesh session -- otherwise every downstream consumer at the local peer would see this gateway as the requester, never the real remote origin, defeating consume()'s own "true origin, never the hub" discipline for this forwarded leg specifically.
121
+ const forwardedCommand = {
122
+ ...request.command,
123
+ params: {
124
+ ...request.command.params,
125
+ "on-behalf-of": senderHex,
126
+ },
127
+ };
128
+ const forwarded = this.deps.forwardToLocalPeer(toDeviceHex, forwardedCommand, request.scope, request.token);
129
+ if (forwarded !== undefined) {
130
+ const outcome = await forwarded;
131
+ await request.respond(outcome).catch(() => undefined);
132
+ continue;
133
+ }
134
+ }
116
135
  await this.deps.handleRoomRequest(request, handle);
117
136
  }
118
137
  })();
@@ -1 +1 @@
1
- {"version":3,"file":"hub-session.js","sourceRoot":"","sources":["../../src/core/hub-session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hub-session.js","sourceRoot":"","sources":["../../src/core/hub-session.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Persistent bridge identity: load-or-create the TLS key material for a (harness, cwd) slot so the device-id -- and therefore the peer and agent ID -- survives restarts. Also persists a per-room capability token set in the same slot, so a room membership grant survives a restart the same way the identity it was minted against does.
2
+ * Persistent bridge identity: load-or-create the TLS key material for a (harness, cwd) slot so the device-id -- and therefore the peer and agent ID -- survives restarts. Also persists a per-room capability token set in the same slot, so a room membership grant survives a restart the same way the identity it was minted against does. Also persists a slot's own trusted-gateway device-id set (agent-comms#186) in a sibling JSON file, so GatewayTrust's allowlist survives a restart the same way; unlike the room/group tokens above, this doesn't require an identity to already exist for the slot, since the trusted set has no dependency on this slot's own key material.
3
3
  *
4
4
  * Mesh state stays in memory and on the wire; the only thing on disk is this local credential (plus, now, the tokens minted against it), the same trust model as an SSH key. A lock file holding a PID keeps two live bridges in one slot from sharing an identity, which would put duplicate peer IDs on the mesh; the second bridge runs with an ephemeral identity (the behaviour before persistence) instead. Bridges without a graceful shutdown hook can skip releasing the lock: a stale lock is detected by probing the recorded PID, the same way the coordinator probes for stale agents.
5
5
  *
@@ -62,4 +62,12 @@ export declare function loadIssuedRoomGrant(slot: Readonly<IdentitySlot>, roomPa
62
62
  export declare function saveIssuedRoomGrant(slot: Readonly<IdentitySlot>, roomPath: string, memberDeviceHex: string, tokenId: Uint8Array): void;
63
63
  /** Removes the recorded token-id for one (roomPath, member) grant, if any -- called once a kick has revoked it, so a later re-join mints and records a genuinely fresh one rather than leaving a stale entry alongside it. A no-op if none was recorded. */
64
64
  export declare function deleteIssuedRoomGrant(slot: Readonly<IdentitySlot>, roomPath: string, memberDeviceHex: string): void;
65
+ /**
66
+ * Every remote device-id this slot's gateway currently trusts, lowercase hex, in insertion order. Empty if the slot has never saved a trusted set, or its gateway trust file is missing or unparseable.
67
+ */
68
+ export declare function loadGatewayTrust(slot: Readonly<IdentitySlot>): string[];
69
+ /**
70
+ * Persists a slot's complete trusted-gateway device-id set, surviving a restart the same way the identity it gates alongside does. Overwrites whatever was saved before in full: GatewayTrust always calls this with its own current list() after every add/remove, so there is no per-device partial update to preserve here the way saveRoomToken preserves other rooms' tokens.
71
+ */
72
+ export declare function saveGatewayTrust(slot: Readonly<IdentitySlot>, trusted: readonly string[]): void;
65
73
  //# sourceMappingURL=identity-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"identity-store.d.ts","sourceRoot":"","sources":["../../src/core/identity-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAQzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA2ID,4UAA4U;AAC5U,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,MAAM,CAIhE;AAuCD;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC3B,YAAY,CAgBd;AAkDD;;GAEG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC3B,MAAM,GAAG,SAAS,CAKpB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC3B,YAAY,CAId;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,IAAI,CAKtE;AAuBD;;GAEG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CASjC;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,eAAe,GACrB,IAAI,CAaN;AAED,sGAAsG;AACtG,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,QAAQ,EAAE,MAAM,GACf,IAAI,CAUN;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CASjC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CAaN;AAED,6JAA6J;AAC7J,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,SAAS,EAAE,MAAM,GAChB,IAAI,CAUN;AAMD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACtB,UAAU,CAAC,WAAW,CAAC,GAAG,SAAS,CAQrC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,UAAU,GAClB,IAAI,CAcN;AAED,4PAA4P;AAC5P,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACtB,IAAI,CASN"}
1
+ {"version":3,"file":"identity-store.d.ts","sourceRoot":"","sources":["../../src/core/identity-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAQzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA2ID,4UAA4U;AAC5U,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,MAAM,CAIhE;AAuCD;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC3B,YAAY,CAgBd;AAkDD;;GAEG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC3B,MAAM,GAAG,SAAS,CAKpB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC3B,YAAY,CAId;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,IAAI,CAKtE;AAuBD;;GAEG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CASjC;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,eAAe,GACrB,IAAI,CAaN;AAED,sGAAsG;AACtG,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,QAAQ,EAAE,MAAM,GACf,IAAI,CAUN;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CASjC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CAaN;AAED,6JAA6J;AAC7J,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,SAAS,EAAE,MAAM,GAChB,IAAI,CAUN;AAMD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACtB,UAAU,CAAC,WAAW,CAAC,GAAG,SAAS,CAQrC;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,UAAU,GAClB,IAAI,CAcN;AAED,4PAA4P;AAC5P,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACtB,IAAI,CASN;AASD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,MAAM,EAAE,CASvE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5B,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,IAAI,CAQN"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Persistent bridge identity: load-or-create the TLS key material for a (harness, cwd) slot so the device-id -- and therefore the peer and agent ID -- survives restarts. Also persists a per-room capability token set in the same slot, so a room membership grant survives a restart the same way the identity it was minted against does.
2
+ * Persistent bridge identity: load-or-create the TLS key material for a (harness, cwd) slot so the device-id -- and therefore the peer and agent ID -- survives restarts. Also persists a per-room capability token set in the same slot, so a room membership grant survives a restart the same way the identity it was minted against does. Also persists a slot's own trusted-gateway device-id set (agent-comms#186) in a sibling JSON file, so GatewayTrust's allowlist survives a restart the same way; unlike the room/group tokens above, this doesn't require an identity to already exist for the slot, since the trusted set has no dependency on this slot's own key material.
3
3
  *
4
4
  * Mesh state stays in memory and on the wire; the only thing on disk is this local credential (plus, now, the tokens minted against it), the same trust model as an SSH key. A lock file holding a PID keeps two live bridges in one slot from sharing an identity, which would put duplicate peer IDs on the mesh; the second bridge runs with an ephemeral identity (the behaviour before persistence) instead. Bridges without a graceful shutdown hook can skip releasing the lock: a stale lock is detected by probing the recorded PID, the same way the coordinator probes for stale agents.
5
5
  *
@@ -370,4 +370,33 @@ export function deleteIssuedRoomGrant(slot, roomPath, memberDeviceHex) {
370
370
  const issuedGrants = Object.fromEntries(Object.entries(stored.issuedGrants).filter(([k]) => k !== key));
371
371
  writeStoredIdentity(identityFile, { ...stored, issuedGrants });
372
372
  }
373
+ /** A slot's own trusted-gateway allowlist (agent-comms#186) lives in its own sibling JSON file rather than inside the identity file: the trusted set has no dependency on this slot's own key material, so it doesn't share loadRoomTokens/saveRoomToken's "call loadOrCreateIdentity first" requirement, and GatewayTrust can be constructed against a slot before or independently of that slot's identity ever being loaded. */
374
+ function gatewayTrustFilePath(slot) {
375
+ const { dir } = slotPaths(slot);
376
+ const base = `gateway-trust-${slot.harness}--${slugifyCwd(slot.cwd)}`;
377
+ return path.join(dir, `${base}.json`);
378
+ }
379
+ /**
380
+ * Every remote device-id this slot's gateway currently trusts, lowercase hex, in insertion order. Empty if the slot has never saved a trusted set, or its gateway trust file is missing or unparseable.
381
+ */
382
+ export function loadGatewayTrust(slot) {
383
+ let parsed;
384
+ try {
385
+ parsed = JSON.parse(fs.readFileSync(gatewayTrustFilePath(slot), "utf-8"));
386
+ }
387
+ catch {
388
+ return [];
389
+ }
390
+ if (!Array.isArray(parsed))
391
+ return [];
392
+ return parsed.filter((entry) => typeof entry === "string");
393
+ }
394
+ /**
395
+ * Persists a slot's complete trusted-gateway device-id set, surviving a restart the same way the identity it gates alongside does. Overwrites whatever was saved before in full: GatewayTrust always calls this with its own current list() after every add/remove, so there is no per-device partial update to preserve here the way saveRoomToken preserves other rooms' tokens.
396
+ */
397
+ export function saveGatewayTrust(slot, trusted) {
398
+ const { dir } = slotPaths(slot);
399
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
400
+ fs.writeFileSync(gatewayTrustFilePath(slot), `${JSON.stringify(trusted, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
401
+ }
373
402
  //# sourceMappingURL=identity-store.js.map