agent-comms 8.15.8 → 8.15.10

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 (184) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/bridges/user/web/server.d.ts.map +1 -1
  4. package/dist/bridges/user/web/server.js +44 -24
  5. package/dist/bridges/user/web/server.js.map +1 -1
  6. package/dist/bridges/user/web/test/web-assets.unit.test.d.ts +5 -0
  7. package/dist/bridges/user/web/test/web-assets.unit.test.d.ts.map +1 -0
  8. package/dist/bridges/user/web/test/web-assets.unit.test.js +90 -0
  9. package/dist/bridges/user/web/test/web-assets.unit.test.js.map +1 -0
  10. package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts +2 -0
  11. package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.d.ts.map +1 -0
  12. package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js +75 -0
  13. package/dist/bridges/user/web/test/web-server-unbuilt-frontend.integration.test.js.map +1 -0
  14. package/dist/bridges/user/web/web-assets.d.ts +28 -0
  15. package/dist/bridges/user/web/web-assets.d.ts.map +1 -0
  16. package/dist/bridges/user/web/web-assets.js +67 -0
  17. package/dist/bridges/user/web/web-assets.js.map +1 -0
  18. package/package.json +8 -2
  19. package/src/account-cli.ts +195 -0
  20. package/src/account-join-cli.ts +191 -0
  21. package/src/bridges/cc-peer/approval-commands.ts +82 -0
  22. package/src/bridges/cc-peer/bridge.ts +138 -0
  23. package/src/bridges/cc-peer/default-front.ts +58 -0
  24. package/src/bridges/cc-peer/front-controller.ts +160 -0
  25. package/src/bridges/cc-peer/front-relay.ts +191 -0
  26. package/src/bridges/cc-peer/front-runtime.ts +167 -0
  27. package/src/bridges/cc-peer/front.ts +59 -0
  28. package/src/bridges/cc-peer/reply-aliases.ts +90 -0
  29. package/src/bridges/cc-peer/run.ts +100 -0
  30. package/src/bridges/cc-peer/shared-peer.ts +28 -0
  31. package/src/bridges/claude-code/channel.ts +318 -0
  32. package/src/bridges/codex/tool.ts +132 -0
  33. package/src/bridges/mcp/server.ts +137 -0
  34. package/src/bridges/opencode/plugin.ts +130 -0
  35. package/src/bridges/pi/extension.ts +330 -0
  36. package/src/bridges/registry.ts +17 -0
  37. package/src/bridges/user/cli.ts +87 -0
  38. package/src/bridges/user/controller.ts +345 -0
  39. package/src/bridges/user/tui.ts +300 -0
  40. package/src/bridges/user/web/actions.ts +173 -0
  41. package/src/bridges/user/web/contract.ts +222 -0
  42. package/src/bridges/user/web/dist/bundle.js +49 -0
  43. package/src/bridges/user/web/dist/icons/icon-192x192.svg +9 -0
  44. package/src/bridges/user/web/dist/icons/icon-512x512.svg +9 -0
  45. package/src/bridges/user/web/dist/icons/icon-96x96.svg +9 -0
  46. package/src/bridges/user/web/dist/index.html +21 -0
  47. package/src/bridges/user/web/dist/manifest.json +26 -0
  48. package/src/bridges/user/web/dist/mesh-worker.js +64 -0
  49. package/src/bridges/user/web/dist/sw.js +1 -0
  50. package/src/bridges/user/web/event-publisher.ts +114 -0
  51. package/src/bridges/user/web/port-discovery.ts +49 -0
  52. package/src/bridges/user/web/reads.ts +35 -0
  53. package/src/bridges/user/web/router.ts +158 -0
  54. package/src/bridges/user/web/server.ts +782 -0
  55. package/src/bridges/user/web/vite.config.ts +71 -0
  56. package/src/bridges/user/web/web-access.ts +404 -0
  57. package/src/bridges/user/web/web-assets.ts +90 -0
  58. package/src/bridges/user/web/web-beacon.ts +69 -0
  59. package/src/bridges/user/web/web-console-static.ts +90 -0
  60. package/src/cli-main.ts +635 -0
  61. package/src/cli.ts +13 -0
  62. package/src/core/account-bundle.ts +264 -0
  63. package/src/core/account-join.ts +289 -0
  64. package/src/core/account-ledger-crypto.ts +154 -0
  65. package/src/core/account-ledger-store.ts +66 -0
  66. package/src/core/account-ledger.ts +385 -0
  67. package/src/core/agent-details.ts +216 -0
  68. package/src/core/agent-list-format.ts +97 -0
  69. package/src/core/agent-registry.ts +282 -0
  70. package/src/core/agent-table.ts +58 -0
  71. package/src/core/atomic-file.ts +46 -0
  72. package/src/core/bind-retry.ts +38 -0
  73. package/src/core/bridge-mesh.ts +176 -0
  74. package/src/core/bridge.ts +718 -0
  75. package/src/core/capability-ask.ts +162 -0
  76. package/src/core/capability-bubble-up.ts +237 -0
  77. package/src/core/comms-store.ts +130 -0
  78. package/src/core/connection-approval.ts +134 -0
  79. package/src/core/connection-code-tool.ts +92 -0
  80. package/src/core/connection-code.ts +296 -0
  81. package/src/core/coordinator-role.ts +224 -0
  82. package/src/core/data-directory.ts +32 -0
  83. package/src/core/data-frame-routing.ts +309 -0
  84. package/src/core/default-command.ts +12 -0
  85. package/src/core/delegation-policy.ts +41 -0
  86. package/src/core/delivery-engine.ts +649 -0
  87. package/src/core/device-membership-verification.ts +125 -0
  88. package/src/core/device-membership.ts +130 -0
  89. package/src/core/directory-admission.ts +155 -0
  90. package/src/core/discovery-mdns.ts +247 -0
  91. package/src/core/discovery-tailscale.ts +224 -0
  92. package/src/core/discovery.ts +303 -0
  93. package/src/core/display-name.ts +73 -0
  94. package/src/core/dm-grant-actions.ts +72 -0
  95. package/src/core/dm-send-delegation.ts +73 -0
  96. package/src/core/dm-token-verification.ts +76 -0
  97. package/src/core/election-sessions.ts +135 -0
  98. package/src/core/error-reporting.ts +85 -0
  99. package/src/core/first-contact.ts +242 -0
  100. package/src/core/gateway-trust-actions.ts +184 -0
  101. package/src/core/gateway-trust.ts +305 -0
  102. package/src/core/gossip-directory.ts +276 -0
  103. package/src/core/gossip-extensions.ts +123 -0
  104. package/src/core/group-proofs.ts +205 -0
  105. package/src/core/handshake.ts +354 -0
  106. package/src/core/hub-forwarding.ts +24 -0
  107. package/src/core/hub-link.ts +176 -0
  108. package/src/core/hub-session.ts +374 -0
  109. package/src/core/identity-store.ts +715 -0
  110. package/src/core/identity.ts +489 -0
  111. package/src/core/index.ts +65 -0
  112. package/src/core/issuer-grouping.ts +165 -0
  113. package/src/core/issuer-identity-file.ts +187 -0
  114. package/src/core/lan-web-actions.ts +34 -0
  115. package/src/core/lan-web-uis.ts +55 -0
  116. package/src/core/ledger-lock.ts +81 -0
  117. package/src/core/listener-registry.ts +133 -0
  118. package/src/core/loopback-address.ts +22 -0
  119. package/src/core/machine-identity.ts +77 -0
  120. package/src/core/machine-list-groups.ts +53 -0
  121. package/src/core/mcp-registry-retry.ts +69 -0
  122. package/src/core/mcp-registry-version.ts +18 -0
  123. package/src/core/membership-proof.ts +139 -0
  124. package/src/core/membership-proofs.ts +149 -0
  125. package/src/core/mesh-graph-trace-tool.ts +81 -0
  126. package/src/core/mesh-graph.ts +89 -0
  127. package/src/core/mesh-network-actions.ts +183 -0
  128. package/src/core/mesh-store-options.ts +23 -0
  129. package/src/core/mesh-store-shared.ts +134 -0
  130. package/src/core/mesh-store.ts +1114 -0
  131. package/src/core/name-claim.ts +145 -0
  132. package/src/core/naming-actions.ts +90 -0
  133. package/src/core/naming.ts +138 -0
  134. package/src/core/nanoid.ts +20 -0
  135. package/src/core/network-interfaces.ts +24 -0
  136. package/src/core/package-version.ts +34 -0
  137. package/src/core/peer-lifecycle.ts +377 -0
  138. package/src/core/peer-link-error.ts +41 -0
  139. package/src/core/peer-versions.ts +98 -0
  140. package/src/core/pending-connection.ts +53 -0
  141. package/src/core/petnames.ts +121 -0
  142. package/src/core/pgp-keyserver.ts +32 -0
  143. package/src/core/pid-lock-file.ts +122 -0
  144. package/src/core/push-manager.ts +76 -0
  145. package/src/core/random-id.ts +9 -0
  146. package/src/core/receiver-set.ts +42 -0
  147. package/src/core/relay-role.ts +182 -0
  148. package/src/core/relay-selection.ts +120 -0
  149. package/src/core/relay-server.ts +198 -0
  150. package/src/core/relay-uplink.ts +78 -0
  151. package/src/core/request-timeouts.ts +37 -0
  152. package/src/core/room-discovery.ts +48 -0
  153. package/src/core/room-lifecycle.ts +838 -0
  154. package/src/core/room-lookup.ts +41 -0
  155. package/src/core/room-messaging.ts +247 -0
  156. package/src/core/room-notice-log.ts +61 -0
  157. package/src/core/room-path.ts +84 -0
  158. package/src/core/room-protocol.ts +893 -0
  159. package/src/core/room-router.ts +180 -0
  160. package/src/core/room-token-verification.ts +85 -0
  161. package/src/core/room-wire-extensions.ts +60 -0
  162. package/src/core/self-name-claim.ts +67 -0
  163. package/src/core/send-outcome.ts +69 -0
  164. package/src/core/shutdown-signals.ts +57 -0
  165. package/src/core/stale-agent-checker.ts +108 -0
  166. package/src/core/store.ts +704 -0
  167. package/src/core/token-text.ts +40 -0
  168. package/src/core/tool.ts +853 -0
  169. package/src/core/transport-events.ts +73 -0
  170. package/src/core/transport-queries.ts +54 -0
  171. package/src/core/transport.ts +367 -0
  172. package/src/core/types.ts +550 -0
  173. package/src/core/user-identity.ts +290 -0
  174. package/src/core/vapid.ts +81 -0
  175. package/src/core/version-check.ts +147 -0
  176. package/src/core/version-report-actions.ts +79 -0
  177. package/src/core/web-beacon.ts +31 -0
  178. package/src/core/web-push.ts +322 -0
  179. package/src/core/wire-mesh-core-version.ts +25 -0
  180. package/src/core/wire-mesh-identity.ts +36 -0
  181. package/src/core/wire-mesh-transport-options.ts +82 -0
  182. package/src/core/wire-mesh-transport.ts +1007 -0
  183. package/src/core/wire-protocol.ts +153 -0
  184. package/src/core/ws-dial.ts +191 -0
@@ -0,0 +1,73 @@
1
+ /**
2
+ * transport-events -- builds the TransportEvents object a bridge hands its WireMeshTransport, dispatching each transport event to whichever MeshStore collaborator owns it. A free function over the collaborators rather than a method on MeshStore, split out purely to keep mesh-store.ts under the repo's max-lines cap, the same reason agent-registry.ts, delivery-engine.ts and their siblings were split out of it.
3
+ */
4
+
5
+ import type { TransportEvents } from "./transport.js";
6
+ import type { ConnectionApproval } from "./connection-approval.js";
7
+ import type { CoordinatorRole } from "./coordinator-role.js";
8
+ import type { DeliveryEngine } from "./delivery-engine.js";
9
+ import type { PeerLifecycle } from "./peer-lifecycle.js";
10
+ import type { RoomProtocol } from "./room-protocol.js";
11
+
12
+ /** The MeshStore collaborators each transport event is dispatched to. reportError is the store's own error channel, an arrow field that reads the mutable onError each time it runs, so a handler a caller sets after this object was built is still the one that receives the error. */
13
+ export interface TransportEventTargets {
14
+ peerLifecycle: PeerLifecycle;
15
+ connectionApproval: ConnectionApproval;
16
+ deliveryEngine: DeliveryEngine;
17
+ roomProtocol: RoomProtocol;
18
+ coordinatorRole: CoordinatorRole;
19
+ reportError: (error: unknown) => void;
20
+ }
21
+
22
+ /** The TransportEvents object bridges pass to the transport constructor. */
23
+ export function createTransportEvents(
24
+ store: Readonly<TransportEventTargets>,
25
+ ): TransportEvents {
26
+ return {
27
+ onMessage: (handle, msg) => {
28
+ void store.peerLifecycle.handleDataMessage(handle, msg);
29
+ },
30
+ onPeerConnected: (handle, info) => {
31
+ void store.peerLifecycle.handlePeerConnected(handle, info);
32
+ },
33
+ onPeerDisconnected: (handle) => {
34
+ store.peerLifecycle.handlePeerDisconnected(handle);
35
+ },
36
+ onIntroduction: (handle, msg) => {
37
+ void store.peerLifecycle.handleIntroduction(handle, msg);
38
+ },
39
+ onPeerList: (peers) => {
40
+ store.peerLifecycle.handlePeerList(peers);
41
+ },
42
+ onPeerJoined: (peer) => {
43
+ store.peerLifecycle.handlePeerJoined(peer);
44
+ },
45
+ onBecomeCoordinator: (peerList) => {
46
+ // A handover this side cannot honour (the outgoing coordinator's port never freed, say) leaves the mesh without a coordinator until the next crash race, which is worth reporting rather than losing to an unhandled rejection.
47
+ void store.peerLifecycle
48
+ .handleBecomeCoordinator(peerList)
49
+ .catch(store.reportError);
50
+ },
51
+ onConnectionRequest: (handle, request) => {
52
+ store.connectionApproval.handleConnectionRequest(handle, request);
53
+ },
54
+ onError: store.reportError,
55
+ onRevocationAnnounce: (entry) => {
56
+ void store.deliveryEngine.handleRevocationAnnounce(entry);
57
+ },
58
+ onCoordinatorClaim: (handle, frame) => {
59
+ void store.coordinatorRole
60
+ .handleClaim(handle, frame)
61
+ .catch(store.reportError);
62
+ },
63
+ onElectionSessionEnrolled: (handle) => {
64
+ void store.coordinatorRole.announceTo(handle).catch(store.reportError);
65
+ },
66
+ onPresenceAdvert: (handle, status) => {
67
+ store.deliveryEngine.handlePresenceAdvert(handle.id, status);
68
+ },
69
+ onDeviceReachable: (deviceHex) => {
70
+ void store.roomProtocol.flushPendingRoomRequests(deviceHex);
71
+ },
72
+ };
73
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The mesh-introspection reads MeshStore exposes over its transport (listener policy parsing, mesh graph, path trace), each failing with a typed CommsError when the transport lacks the capability.
3
+ */
4
+
5
+ import { CommsError } from "./store.js";
6
+ import type {
7
+ ListenerPolicy,
8
+ MeshGraph,
9
+ MeshTraceResult,
10
+ MeshTransport,
11
+ } from "./transport.js";
12
+
13
+ function isListenerPolicy(v: string): v is ListenerPolicy {
14
+ return (
15
+ v === "full" || v === "observe" || v === "rooms-only" || v === "gateway"
16
+ );
17
+ }
18
+
19
+ /** Narrows a caller-supplied policy string, throwing INVALID_POLICY for anything the transport does not define. */
20
+ export function parseListenerPolicy(policy: string): ListenerPolicy {
21
+ if (!isListenerPolicy(policy)) {
22
+ throw new CommsError(`Invalid policy "${policy}"`, "INVALID_POLICY");
23
+ }
24
+ return policy;
25
+ }
26
+
27
+ /** The transport's own meshGraph (agent-comms#199), throwing NOT_SUPPORTED if it has none. */
28
+ export function requireMeshGraph(
29
+ transport: Readonly<MeshTransport>,
30
+ ): MeshGraph {
31
+ const graph = transport.meshGraph?.();
32
+ if (graph === undefined) {
33
+ throw new CommsError(
34
+ "mesh_graph requires a transport that supports it",
35
+ "NOT_SUPPORTED",
36
+ );
37
+ }
38
+ return graph;
39
+ }
40
+
41
+ /** The transport's own meshTrace, throwing NOT_SUPPORTED if it has none. */
42
+ export async function requireMeshTrace(
43
+ transport: Readonly<MeshTransport>,
44
+ target: string,
45
+ timeoutMs?: number,
46
+ ): Promise<MeshTraceResult> {
47
+ if (transport.meshTrace === undefined) {
48
+ throw new CommsError(
49
+ "mesh_trace requires a transport that supports it",
50
+ "NOT_SUPPORTED",
51
+ );
52
+ }
53
+ return transport.meshTrace(target, timeoutMs);
54
+ }
@@ -0,0 +1,367 @@
1
+ /**
2
+ * MeshTransport — abstract transport layer for the peer mesh.
3
+ *
4
+ * The transport owns connections (TCP sockets, TLS sockets, WebSockets)
5
+ * and provides send/receive primitives. MeshStore handles state management
6
+ * and delegates all I/O to the transport.
7
+ *
8
+ * Lifecycle:
9
+ * 1. startDataServer() — listen for incoming peer data connections
10
+ * 2. connectToCoordinator() — join an existing mesh
11
+ * OR becomeCoordinator() — start a new mesh as coordinator
12
+ * 3. connectToPeer() — establish data connection to a discovered peer
13
+ * 4. send() / broadcast() — send wire messages
14
+ * 5. shutdown() — close all connections and servers
15
+ *
16
+ * The transport emits received messages via the onMessage callback.
17
+ * It does not interpret messages — that's MeshStore's job.
18
+ */
19
+
20
+ import type { MeshMessage, PeerInfo } from "./wire-protocol.js";
21
+ import type {
22
+ CapabilityScope,
23
+ CapabilityToken,
24
+ CoordinatorFrame,
25
+ ManageCommand,
26
+ RevocationEntry,
27
+ } from "wire-mesh-core/generated/protocol";
28
+ import type { ManageOutcome } from "wire-mesh-core/domain/mesh-session";
29
+ import type { AgentStatus } from "./types.js";
30
+ import type { ConnectToRemoteOptions } from "./wire-mesh-transport-options.js";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Connection handle — opaque reference to a specific peer connection
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /**
37
+ * A handle identifying a specific peer connection. The transport creates
38
+ * these and passes them to MeshStore via callbacks. MeshStore uses them
39
+ * as keys for send() and to identify message sources.
40
+ */
41
+ export type ListenerPolicy = "full" | "observe" | "rooms-only" | "gateway";
42
+
43
+ export interface ListenerInfo {
44
+ id: string;
45
+ host: string;
46
+ port: number;
47
+ policy: ListenerPolicy;
48
+ /** Whether this is the default localhost listener (cannot be removed). */
49
+ isDefault: boolean;
50
+ }
51
+
52
+ export interface ConnectionHandle {
53
+ /** Stable identifier for this connection (used as peer ID once identified). */
54
+ id: string;
55
+ /** Policy inherited from the listener that accepted this connection. */
56
+ policy?: ListenerPolicy;
57
+ }
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Transport events
61
+ // ---------------------------------------------------------------------------
62
+
63
+ export interface TransportEvents {
64
+ /**
65
+ * A wire message was received from a peer. Called for every complete message after framing.
66
+ */
67
+ onMessage: (handle: Readonly<ConnectionHandle>, message: MeshMessage) => void;
68
+
69
+ /**
70
+ * A new data connection was established and the peer identified itself via a pong message. MeshStore should wire up state handling for this peer.
71
+ */
72
+ onPeerConnected: (
73
+ handle: Readonly<ConnectionHandle>,
74
+ info: Readonly<PeerInfo>,
75
+ ) => void;
76
+
77
+ /**
78
+ * A peer connection was lost (close, error, or timeout).
79
+ */
80
+ onPeerDisconnected: (handle: Readonly<ConnectionHandle>) => void;
81
+
82
+ /**
83
+ * A non-fatal error occurred that consumers should know about.
84
+ */
85
+ onError?: (error: Error) => void;
86
+
87
+ /**
88
+ * A peer introduced itself to the coordinator. Only fires on the coordinator instance. MeshStore should send the peer list and broadcast the arrival.
89
+ */
90
+ onIntroduction: (
91
+ handle: Readonly<ConnectionHandle>,
92
+ msg: Readonly<{ peerId: string; dataPort: number }>,
93
+ ) => void;
94
+
95
+ /**
96
+ * A new peer introduced itself and is awaiting approval. Replaces onIntroduction when connection approval is active. Only fires on the coordinator instance. MeshStore should queue the request and deliver a connection_request event to the owning agent.
97
+ */
98
+ onConnectionRequest: (
99
+ handle: Readonly<ConnectionHandle>,
100
+ info: Readonly<{
101
+ peerId: string;
102
+ dataPort: number;
103
+ name: string;
104
+ fingerprint: string;
105
+ }>,
106
+ ) => void;
107
+
108
+ /**
109
+ * The coordinator sent us a peer list (received during initial connection). MeshStore should connect to each peer's data server.
110
+ */
111
+ onPeerList: (peers: readonly PeerInfo[]) => void;
112
+
113
+ /**
114
+ * The coordinator told us a new peer joined.
115
+ */
116
+ onPeerJoined: (peer: Readonly<PeerInfo>) => void;
117
+
118
+ /**
119
+ * We received a become_coordinator message — take over as coordinator.
120
+ */
121
+ onBecomeCoordinator: (peerList: readonly PeerInfo[]) => void;
122
+
123
+ /**
124
+ * A peer announced one already-minted revocation-entry over an established session (management.cddl's revocation-announce, flattened to one call per entry). MeshStore should verify it and, if it verifies, record it in its own RevocationView -- a bearer's already-issued token stays valid to every peer that never received this until it does, per the design's own honest "detection with propagation delay" limit.
125
+ */
126
+ onRevocationAnnounce: (entry: RevocationEntry) => void;
127
+
128
+ /**
129
+ * A peer on this machine gossiped a coordinator-frame: a term-based claim to the elected coordinator role (agent-comms#341). Fires only for sessions the transport counts as machine-local, since the duties that role carries are per machine. MeshStore evaluates it against the claim it currently accepts.
130
+ */
131
+ onCoordinatorClaim: (
132
+ handle: Readonly<ConnectionHandle>,
133
+ frame: Readonly<CoordinatorFrame>,
134
+ ) => void;
135
+
136
+ /**
137
+ * A machine-local session was enrolled in the coordinator election (agent-comms#341), on either end of it: accepted on a loopback listener, or dialled at a loopback address. MeshStore tells the peer at once which claim it accepts, so a joiner learns the incumbent instead of claiming over it, whichever side dialled.
138
+ */
139
+ onElectionSessionEnrolled: (handle: Readonly<ConnectionHandle>) => void;
140
+
141
+ /**
142
+ * A device this side can now route a request to became reachable: the relay hub admitted its directory entry into this side's own view, or an operator trusted it outright. Fires for a route that appears through the hub as well as a direct peer connection, since onPeerConnected only ever covers the latter and a device fronted by a remote gateway has no local session of its own to connect. MeshStore retries anything queued for that device on this.
143
+ */
144
+ onDeviceReachable: (deviceHex: string) => void;
145
+
146
+ /**
147
+ * A peer's gossiped self-advert carried a `presence/status` extension (wire-mesh-core's peer-advert open extension tail, re-sent periodically via sendGossipUpdate). Fires only when that key is present and a recognised AgentStatus value -- an advert with no presence extension, or an unrecognised value, is a peer that simply isn't advertising presence over this mechanism, not an error.
148
+ */
149
+ onPresenceAdvert: (
150
+ handle: Readonly<ConnectionHandle>,
151
+ status: AgentStatus,
152
+ ) => void;
153
+ }
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // MeshTransport interface
157
+ // ---------------------------------------------------------------------------
158
+
159
+ export interface MeshTransport {
160
+ /** The port this instance's data server is listening on (0 before startDataServer). */
161
+ readonly dataPort: number;
162
+
163
+ /** Whether this instance holds the well-known coordinator port listener, the compatibility first-contact address and introduction path. Not the elected coordinator role (agent-comms#341), which is held by gossiped claim and need not be on the peer that bound the port. */
164
+ readonly isCoordinator: boolean;
165
+
166
+ /** Whether this instance has a live connection to a coordinator. */
167
+ readonly hasCoordinatorConnection: boolean;
168
+
169
+ /** The peer ID of the coordinator this instance currently answers to, or undefined when it is the coordinator itself or has never reached one. Survives that coordinator's session closing, so an onPeerDisconnected handler can tell the coordinator's own departure apart from an ordinary peer's and contest the vacated role. */
170
+ readonly coordinatorPeerId: string | undefined;
171
+
172
+ /**
173
+ * Start the data server on an OS-assigned port.
174
+ * Resolves when the server is listening.
175
+ */
176
+ startDataServer: () => Promise<void>;
177
+
178
+ /**
179
+ * Connect to the coordinator at the given host:port and send an introduction message. Resolves when the connection is established and the introduction has been sent. Rejects if no coordinator is reachable (caller should becomeCoordinator).
180
+ */
181
+ connectToCoordinator: (
182
+ host: string,
183
+ port: number,
184
+ peerId: string,
185
+ dataPort: number,
186
+ ) => Promise<void>;
187
+
188
+ /**
189
+ * Start listening as the coordinator on the given host:port. Resolves when the coordinator server is listening.
190
+ */
191
+ becomeCoordinator: (host: string, port: number) => Promise<void>;
192
+
193
+ /**
194
+ * Connect to a peer's data server and send a pong identification. Resolves when the connection is established and pong sent. No-op if already connected to this peer.
195
+ */
196
+ connectToPeer: (peer: Readonly<PeerInfo>, ownPeerId: string) => Promise<void>;
197
+
198
+ /**
199
+ * Send a wire message to a specific peer connection.
200
+ */
201
+ send: (
202
+ handle: Readonly<ConnectionHandle>,
203
+ message: MeshMessage,
204
+ ) => Promise<void>;
205
+
206
+ /**
207
+ * Accept a pending connection. Sends connect_accepted and processes the introduction as normal (peer_list, peer_joined broadcast).
208
+ */
209
+ acceptConnection: (handle: Readonly<ConnectionHandle>) => Promise<void>;
210
+
211
+ /**
212
+ * Reject a pending connection. Sends connect_rejected and closes the socket.
213
+ */
214
+ rejectConnection: (
215
+ handle: Readonly<ConnectionHandle>,
216
+ reason: string,
217
+ ) => Promise<void>;
218
+
219
+ /**
220
+ * Initiate an outbound connection that requires approval. Sends connect_request instead of introduce and waits for connect_accepted or connect_rejected from the remote coordinator.
221
+ */
222
+ connectToRemote: (options: Readonly<ConnectToRemoteOptions>) => Promise<void>;
223
+
224
+ /**
225
+ * Broadcast a wire message to all connected peer data connections.
226
+ */
227
+ broadcast: (message: MeshMessage) => Promise<void>;
228
+
229
+ /**
230
+ * Announces one or more already-minted revocation-entries to every connected peer session, best-effort (an unreachable peer misses it and learns of the revocation later, if ever -- the same honest gossip-propagation-delay limit every other broadcast in this codebase already accepts).
231
+ */
232
+ broadcastRevocation: (entries: readonly RevocationEntry[]) => Promise<void>;
233
+
234
+ /**
235
+ * Gossips a coordinator-frame to every machine-local peer session. A failed send is reported through onError rather than thrown, since it only happens on a session that is already closing.
236
+ */
237
+ broadcastCoordinatorClaim: (
238
+ frame: Readonly<CoordinatorFrame>,
239
+ ) => Promise<void>;
240
+
241
+ /**
242
+ * Sends a coordinator-frame to one peer, as an announcement to a new session or an answer to a stale claim. A no-op when that peer has no machine-local session.
243
+ */
244
+ sendCoordinatorClaim: (
245
+ handle: Readonly<ConnectionHandle>,
246
+ frame: Readonly<CoordinatorFrame>,
247
+ ) => Promise<void>;
248
+
249
+ /**
250
+ * The device-ids of every peer this side has a machine-local session to, the peers that take part in the coordinator election with it. Peers reached only across machines, and peers merely named in a peer list, are not among them.
251
+ */
252
+ electionPeerIds: () => ReadonlySet<string>;
253
+
254
+ /**
255
+ * Sends a real core/room manage-request to a specific member's own established session, returning its outcome (e.g. a room.join request's granted-token, or a room.send's delivery receipt) rather than swallowing it the way send() does for the legacy opaque-frame path. Resolves to a not_connected error outcome if no live session to that member exists, rather than throwing -- the caller (currently room-join, and P3.5's directed fan-out once it lands) decides how to react to an unreachable member.
256
+ */
257
+ sendRoomRequest: (
258
+ memberId: string,
259
+ command: ManageCommand,
260
+ scope: Readonly<CapabilityScope>,
261
+ token?: CapabilityToken,
262
+ ) => Promise<ManageOutcome>;
263
+
264
+ /**
265
+ * Add a coordinator listener on a specific adapter. Only valid when this instance is the coordinator. Returns listener ID.
266
+ */
267
+ addListener: (
268
+ host: string,
269
+ port: number,
270
+ policy: ListenerPolicy,
271
+ ) => Promise<string>;
272
+
273
+ /**
274
+ * Remove a listener by ID. Cannot remove the default localhost listener.
275
+ */
276
+ removeListener: (id: string) => Promise<void>;
277
+
278
+ /** List all active listeners. */
279
+ listListeners: () => ListenerInfo[];
280
+
281
+ /**
282
+ * Gracefully shut down all servers and connections. After shutdown, no further callbacks will fire.
283
+ */
284
+ shutdown: () => Promise<void>;
285
+
286
+ /**
287
+ * Unref all root handles so the event loop can exit when the agent process shuts down. Sockets still function for I/O but don't keep the process alive.
288
+ */
289
+ unref: () => void;
290
+
291
+ /**
292
+ * Every device this side has ever heard gossip from, mesh-wide, with each one's own latest full advert (any open-extension field such as room/hosted). Optional: WireMeshTransport is the only implementation that offers it today (agent-comms#48/#50's own gossip-directory aggregation), so a caller (listRooms' own room-discovery merge) must treat its absence as "nothing to merge," never assume every MeshTransport has it.
293
+ */
294
+ listKnownDevices?: () => readonly {
295
+ deviceId: string;
296
+ advert: Readonly<Record<string, unknown>>;
297
+ }[];
298
+
299
+ /**
300
+ * Holds a session of this side's own on the relay hub at the given URL for as long as shouldConnect answers true, redialling with backoff whenever the hub drops it, until shutdown (agent-comms#293: every store is its own hub peer). shouldConnect is re-read periodically and on reconsiderHub, so a store with nothing to say to another machine holds no session and puts nothing on the hub. Returns at once: the dial runs in the background and a failed dial reaches events.onError. Optional: WireMeshTransport is the only implementation that offers it today, matching listKnownDevices' own precedent, so a caller must treat its absence as "this transport has no hub capability," never assume every MeshTransport supports it.
301
+ */
302
+ joinHub?: (url: string, shouldConnect: () => boolean) => void;
303
+
304
+ /**
305
+ * Makes the hub session re-evaluate shouldConnect now instead of at the next poll. A no-op before joinHub. Same optionality caveat as joinHub.
306
+ */
307
+ reconsiderHub?: () => void;
308
+
309
+ /**
310
+ * Tells the transport whether this store holds the elected coordinator role, which makes it the one store on the machine that serves the relay role while it holds an uplink to the configured hub, and keeps its own hub session on that uplink (agent-comms#342). Same optionality caveat as joinHub.
311
+ */
312
+ setRelayEligible?: (eligible: boolean) => void;
313
+
314
+ /**
315
+ * Asks deviceId for its own, currently-running wire-mesh-core version, live, right now rather than whatever it last gossiped (agent-comms#198's own cache-bust query_version action). Optional, same caveat as listKnownDevices/joinHub: WireMeshTransport is the only implementation that offers it today, riding wire-mesh-core's own version.get manage-command (wire-mesh#179).
316
+ */
317
+ queryVersion?: (deviceId: string) => Promise<ManageOutcome>;
318
+
319
+ /**
320
+ * Assembles this side's own best-effort view of the mesh's connection graph (agent-comms#199) out of every device's own self-reported `topology/peers` gossip extension (wire-mesh#180) -- the cheap, possibly-stale counterpart to meshTrace's own live read. Device-ids throughout are hex strings (matching listKnownDevices' own precedent), never raw wire-mesh-core DeviceId bytes. Same optionality caveat as listKnownDevices/joinHub.
321
+ */
322
+ meshGraph?: () => MeshGraph;
323
+
324
+ /**
325
+ * Sends the wire-level path.trace manage-command (wire-mesh#181) to a specific device-id, direct or via the hub, and measures the real end-to-end round trip -- the live, cache-bust counterpart to meshGraph's gossiped snapshot. Resolves an error-shaped MeshTraceResult.outcome (never rejects) when targetDeviceHex is unreachable through any known path, mirroring sendRoomRequest's own not_connected convention. Same optionality caveat as listKnownDevices/joinHub.
326
+ */
327
+ meshTrace?: (
328
+ targetDeviceHex: string,
329
+ timeoutMs?: number,
330
+ ) => Promise<MeshTraceResult>;
331
+ }
332
+
333
+ // ---------------------------------------------------------------------------
334
+ // Mesh topology graph and path trace (agent-comms#199)
335
+ // ---------------------------------------------------------------------------
336
+
337
+ export interface MeshGraphEdge {
338
+ kind: "direct" | "relay";
339
+ from: string;
340
+ to: string;
341
+ /** The hub device-id this relay edge is reached via, when known -- absent for a "direct" edge, and for a "relay" edge whose hub is a bare, identity-less relay (wire-mesh-node/cloudflare-hub's own RelayHub) with no device-id of its own to report. */
342
+ via?: string;
343
+ }
344
+
345
+ export interface MeshGraph {
346
+ nodes: string[];
347
+ edges: MeshGraphEdge[];
348
+ }
349
+
350
+ export interface MeshTraceLocal {
351
+ relayed: boolean;
352
+ hubAddress?: string;
353
+ }
354
+
355
+ export interface MeshTraceRemote {
356
+ relayed: boolean;
357
+ hubAddress?: string;
358
+ }
359
+
360
+ export interface MeshTraceResult {
361
+ /** Real end-to-end round-trip time in milliseconds. */
362
+ rttMs: number;
363
+ local: MeshTraceLocal;
364
+ /** The receiver's own reported relayed/hub-address -- absent when outcome is an error rather than a successful path-trace-ok. */
365
+ remote?: MeshTraceRemote;
366
+ outcome: ManageOutcome;
367
+ }