@geohar/un-bien 0.7.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 (174) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +726 -0
  3. package/dist/actions/handlers.d.ts +77 -0
  4. package/dist/actions/handlers.js +26 -0
  5. package/dist/actions/handlers.js.map +1 -0
  6. package/dist/actions/registry.d.ts +39 -0
  7. package/dist/actions/registry.js +62 -0
  8. package/dist/actions/registry.js.map +1 -0
  9. package/dist/bin/supervisord.d.ts +2 -0
  10. package/dist/bin/supervisord.js +86 -0
  11. package/dist/bin/supervisord.js.map +1 -0
  12. package/dist/config.d.ts +84 -0
  13. package/dist/config.js +115 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/daemon/client.d.ts +20 -0
  16. package/dist/daemon/client.js +131 -0
  17. package/dist/daemon/client.js.map +1 -0
  18. package/dist/daemon/control_protocol.d.ts +176 -0
  19. package/dist/daemon/control_protocol.js +63 -0
  20. package/dist/daemon/control_protocol.js.map +1 -0
  21. package/dist/daemon/cron_log.d.ts +45 -0
  22. package/dist/daemon/cron_log.js +71 -0
  23. package/dist/daemon/cron_log.js.map +1 -0
  24. package/dist/daemon/cron_registry.d.ts +80 -0
  25. package/dist/daemon/cron_registry.js +198 -0
  26. package/dist/daemon/cron_registry.js.map +1 -0
  27. package/dist/daemon/id.d.ts +24 -0
  28. package/dist/daemon/id.js +36 -0
  29. package/dist/daemon/id.js.map +1 -0
  30. package/dist/daemon/install.d.ts +170 -0
  31. package/dist/daemon/install.js +603 -0
  32. package/dist/daemon/install.js.map +1 -0
  33. package/dist/daemon/registry.d.ts +63 -0
  34. package/dist/daemon/registry.js +153 -0
  35. package/dist/daemon/registry.js.map +1 -0
  36. package/dist/daemon/rpc_child.d.ts +194 -0
  37. package/dist/daemon/rpc_child.js +419 -0
  38. package/dist/daemon/rpc_child.js.map +1 -0
  39. package/dist/daemon/supervisor.d.ts +93 -0
  40. package/dist/daemon/supervisor.js +666 -0
  41. package/dist/daemon/supervisor.js.map +1 -0
  42. package/dist/extension_ui_bridge.d.ts +26 -0
  43. package/dist/extension_ui_bridge.js +341 -0
  44. package/dist/extension_ui_bridge.js.map +1 -0
  45. package/dist/index.d.ts +197 -0
  46. package/dist/index.js +5750 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/mcp/mesh_result.d.ts +10 -0
  49. package/dist/mcp/mesh_result.js +42 -0
  50. package/dist/mcp/mesh_result.js.map +1 -0
  51. package/dist/mcp/mesh_server.d.ts +16 -0
  52. package/dist/mcp/mesh_server.js +373 -0
  53. package/dist/mcp/mesh_server.js.map +1 -0
  54. package/dist/mesh/canonical.d.ts +30 -0
  55. package/dist/mesh/canonical.js +63 -0
  56. package/dist/mesh/canonical.js.map +1 -0
  57. package/dist/mesh/client.d.ts +18 -0
  58. package/dist/mesh/client.js +127 -0
  59. package/dist/mesh/client.js.map +1 -0
  60. package/dist/mesh/encoding.d.ts +38 -0
  61. package/dist/mesh/encoding.js +217 -0
  62. package/dist/mesh/encoding.js.map +1 -0
  63. package/dist/mesh/self_revoke.d.ts +73 -0
  64. package/dist/mesh/self_revoke.js +407 -0
  65. package/dist/mesh/self_revoke.js.map +1 -0
  66. package/dist/mesh/siblings.d.ts +49 -0
  67. package/dist/mesh/siblings.js +159 -0
  68. package/dist/mesh/siblings.js.map +1 -0
  69. package/dist/mesh/types.d.ts +34 -0
  70. package/dist/mesh/types.js +11 -0
  71. package/dist/mesh/types.js.map +1 -0
  72. package/dist/mesh/verify.d.ts +17 -0
  73. package/dist/mesh/verify.js +86 -0
  74. package/dist/mesh/verify.js.map +1 -0
  75. package/dist/pairing/crypto.d.ts +8 -0
  76. package/dist/pairing/crypto.js +22 -0
  77. package/dist/pairing/crypto.js.map +1 -0
  78. package/dist/pairing/qr.d.ts +59 -0
  79. package/dist/pairing/qr.js +127 -0
  80. package/dist/pairing/qr.js.map +1 -0
  81. package/dist/pairing/storage.d.ts +120 -0
  82. package/dist/pairing/storage.js +614 -0
  83. package/dist/pairing/storage.js.map +1 -0
  84. package/dist/panel_bridge.d.ts +15 -0
  85. package/dist/panel_bridge.js +409 -0
  86. package/dist/panel_bridge.js.map +1 -0
  87. package/dist/paths.d.ts +39 -0
  88. package/dist/paths.js +51 -0
  89. package/dist/paths.js.map +1 -0
  90. package/dist/protocol/codec.d.ts +7 -0
  91. package/dist/protocol/codec.js +48 -0
  92. package/dist/protocol/codec.js.map +1 -0
  93. package/dist/protocol/types.d.ts +404 -0
  94. package/dist/protocol/types.js +2 -0
  95. package/dist/protocol/types.js.map +1 -0
  96. package/dist/rooms.d.ts +42 -0
  97. package/dist/rooms.js +78 -0
  98. package/dist/rooms.js.map +1 -0
  99. package/dist/session/bridge.d.ts +37 -0
  100. package/dist/session/bridge.js +134 -0
  101. package/dist/session/bridge.js.map +1 -0
  102. package/dist/session/broker.d.ts +189 -0
  103. package/dist/session/broker.js +527 -0
  104. package/dist/session/broker.js.map +1 -0
  105. package/dist/session/broker_remote.d.ts +74 -0
  106. package/dist/session/broker_remote.js +581 -0
  107. package/dist/session/broker_remote.js.map +1 -0
  108. package/dist/session/cwd_lock.d.ts +33 -0
  109. package/dist/session/cwd_lock.js +127 -0
  110. package/dist/session/cwd_lock.js.map +1 -0
  111. package/dist/session/debug_log.d.ts +3 -0
  112. package/dist/session/debug_log.js +32 -0
  113. package/dist/session/debug_log.js.map +1 -0
  114. package/dist/session/envelope.d.ts +33 -0
  115. package/dist/session/envelope.js +117 -0
  116. package/dist/session/envelope.js.map +1 -0
  117. package/dist/session/global_config.d.ts +33 -0
  118. package/dist/session/global_config.js +73 -0
  119. package/dist/session/global_config.js.map +1 -0
  120. package/dist/session/ipc.d.ts +27 -0
  121. package/dist/session/ipc.js +22 -0
  122. package/dist/session/ipc.js.map +1 -0
  123. package/dist/session/leader_election.d.ts +32 -0
  124. package/dist/session/leader_election.js +108 -0
  125. package/dist/session/leader_election.js.map +1 -0
  126. package/dist/session/local_config.d.ts +67 -0
  127. package/dist/session/local_config.js +193 -0
  128. package/dist/session/local_config.js.map +1 -0
  129. package/dist/session/mesh_node.d.ts +116 -0
  130. package/dist/session/mesh_node.js +596 -0
  131. package/dist/session/mesh_node.js.map +1 -0
  132. package/dist/session/peer.d.ts +139 -0
  133. package/dist/session/peer.js +474 -0
  134. package/dist/session/peer.js.map +1 -0
  135. package/dist/session/peer_inventory.d.ts +13 -0
  136. package/dist/session/peer_inventory.js +48 -0
  137. package/dist/session/peer_inventory.js.map +1 -0
  138. package/dist/session/peer_limits.d.ts +9 -0
  139. package/dist/session/peer_limits.js +24 -0
  140. package/dist/session/peer_limits.js.map +1 -0
  141. package/dist/session/rpc_envelope.d.ts +108 -0
  142. package/dist/session/rpc_envelope.js +221 -0
  143. package/dist/session/rpc_envelope.js.map +1 -0
  144. package/dist/session/rpc_inbound.d.ts +55 -0
  145. package/dist/session/rpc_inbound.js +136 -0
  146. package/dist/session/rpc_inbound.js.map +1 -0
  147. package/dist/session/setup_wizard.d.ts +39 -0
  148. package/dist/session/setup_wizard.js +63 -0
  149. package/dist/session/setup_wizard.js.map +1 -0
  150. package/dist/session/tools.d.ts +24 -0
  151. package/dist/session/tools.js +272 -0
  152. package/dist/session/tools.js.map +1 -0
  153. package/dist/session/wizard.d.ts +13 -0
  154. package/dist/session/wizard.js +20 -0
  155. package/dist/session/wizard.js.map +1 -0
  156. package/dist/transport/peer_channel.d.ts +52 -0
  157. package/dist/transport/peer_channel.js +145 -0
  158. package/dist/transport/peer_channel.js.map +1 -0
  159. package/dist/transport/pi_forward_client.d.ts +29 -0
  160. package/dist/transport/pi_forward_client.js +68 -0
  161. package/dist/transport/pi_forward_client.js.map +1 -0
  162. package/dist/transport/relay_client.d.ts +96 -0
  163. package/dist/transport/relay_client.js +210 -0
  164. package/dist/transport/relay_client.js.map +1 -0
  165. package/dist/ui/footer.d.ts +32 -0
  166. package/dist/ui/footer.js +34 -0
  167. package/dist/ui/footer.js.map +1 -0
  168. package/docs/daemon.md +289 -0
  169. package/package.json +92 -0
  170. package/service-templates/launchd.plist.template +35 -0
  171. package/service-templates/systemd.service.template +19 -0
  172. package/service-templates/task-launcher.vbs.template +10 -0
  173. package/service-templates/task-scheduler.xml.template +38 -0
  174. package/skills/agent-network/SKILL.md +310 -0
@@ -0,0 +1,527 @@
1
+ import { appendFile, mkdir } from "node:fs/promises";
2
+ import { dirname, posix, win32 } from "node:path";
3
+ import { parse, serialize, uuidv7, EnvelopeError, } from "./envelope.js";
4
+ import { sanitizeSegment } from "./local_config.js";
5
+ import { isBoundedPeerInfo, MAX_CWD_LENGTH, MAX_PEERS_UPDATE_ENTRIES, } from "./peer_limits.js";
6
+ /**
7
+ * THE sole encoder of a peer address (plan/38): `[<pc>:]<cwd>@<nome>`.
8
+ *
9
+ * - `cwd` present → `<cwd>@<nome>` (the `@` separates name from path so a `/`
10
+ * in the path never confuses lookup, which is exact-match anyway).
11
+ * - `cwd` empty (legacy peer that sent no cwd) → `address == name`, preserving
12
+ * pre-plan/38 behavior so a mixed mesh keeps routing.
13
+ * - `pc` present (cross-PC, Fase 2) → prefixed `<pc>:`.
14
+ *
15
+ * Does NOT sanitize — callers sanitize the `name` once (see `sanitizeMeshName`)
16
+ * before composing, so an already-appended `#N` collision suffix survives.
17
+ * Everyone else ECHOES `peer.address` verbatim; only the broker composes.
18
+ */
19
+ export function composeAddress(parts) {
20
+ const base = parts.cwd ? `${parts.cwd}@${parts.name}` : parts.name;
21
+ return parts.pc ? `${parts.pc}:${base}` : base;
22
+ }
23
+ /**
24
+ * Sanitize a requested mesh name to a safe leaf while PRESERVING a trailing
25
+ * `#N` collision suffix (which the cwd-lock or a prior assignment may have
26
+ * added — `sanitizeSegment` alone would mangle `#`→`-`). The base is run through
27
+ * `sanitizeSegment` (af66d04); an unusable base (empty / reserved keyword) falls
28
+ * back to `"agent"`.
29
+ */
30
+ export function sanitizeMeshName(raw) {
31
+ const m = /^(.*?)(#\d+)?$/.exec(raw);
32
+ const base = sanitizeSegment(m?.[1] ?? raw) ?? "agent";
33
+ return m?.[2] ? base + m[2] : base;
34
+ }
35
+ const BROKER_NAME = "broker";
36
+ /** Host-independent Windows drive absolute-path check. */
37
+ function isWindowsDriveAbsolutePath(value) {
38
+ return /^[A-Za-z]:[\\/]/.test(value);
39
+ }
40
+ /** Accept legacy empty cwd plus bounded syntactically absolute paths only. */
41
+ function isValidRegisteredCwd(value) {
42
+ if (value === "")
43
+ return true;
44
+ if (value.length > MAX_CWD_LENGTH || /[\0\r\n]/.test(value)) {
45
+ return false;
46
+ }
47
+ return (posix.isAbsolute(value) ||
48
+ isWindowsDriveAbsolutePath(value) ||
49
+ (win32.isAbsolute(value) && /^[/\\]{2}/.test(value)));
50
+ }
51
+ export class Broker {
52
+ peers = new Map();
53
+ auditPath;
54
+ onRouted;
55
+ server;
56
+ /** Plan/25 Wave C: optional handoff for cross-PC routing. Null = local only. */
57
+ remoteRouter = null;
58
+ constructor(opts) {
59
+ this.server = opts.server;
60
+ this.auditPath = opts.auditPath;
61
+ this.onRouted = opts.onRouted;
62
+ this.server.on("connection", (socket) => this._handleConnection(socket));
63
+ }
64
+ /** Attach (or detach with null) a cross-PC router. Idempotent. */
65
+ setRemoteRouter(router) {
66
+ this.remoteRouter = router;
67
+ }
68
+ /** Clear only when the caller still owns the active router slot. */
69
+ clearRemoteRouter(expected) {
70
+ if (this.remoteRouter === expected)
71
+ this.remoteRouter = null;
72
+ }
73
+ /**
74
+ * Plan/25 Wave C entry point: deliver an envelope that arrived from a
75
+ * remote PC (via relay forward) into the local UDS mesh. Skips the
76
+ * `force from = conn.name` rule (that defense is anti-spoof for local
77
+ * peers; cross-PC has its own defense via the relay's verified `from_pc`).
78
+ *
79
+ * Returns the ACK status so the caller (broker_remote) can pack and
80
+ * forward an ACK envelope back across the relay:
81
+ * - `received` — target exists, envelope delivered (plan/34: always
82
+ * delivered when the peer is online — the Pi harness enqueues mid-turn
83
+ * messages, so there is no busy-drop)
84
+ * - `denied` — no such local peer (or write failed) — caller maps to
85
+ * transport_error or denied ACK as it sees fit
86
+ */
87
+ injectFromRemote(env) {
88
+ // Remote callers do not cross the normal UDS parser, so validate the exact
89
+ // serialized payload before claiming receipt or writing it to a peer.
90
+ let validated;
91
+ try {
92
+ validated = parse(serialize(env));
93
+ }
94
+ catch {
95
+ return "denied";
96
+ }
97
+ if (typeof validated.to !== "string" ||
98
+ validated.to === "broadcast" ||
99
+ validated.to === BROKER_NAME) {
100
+ // Cross-PC is unicast-only at this protocol layer.
101
+ return "denied";
102
+ }
103
+ const targetName = validated.to;
104
+ const peer = this.peers.get(targetName);
105
+ if (!peer)
106
+ return "denied";
107
+ const line = serialize(validated);
108
+ try {
109
+ peer.socket.write(line);
110
+ }
111
+ catch {
112
+ return "denied";
113
+ }
114
+ void this._appendAudit(validated, [targetName], "received", "relay");
115
+ this.onRouted?.(validated, [targetName]);
116
+ return "received";
117
+ }
118
+ /** Peers currently registered. Snapshot, safe to read. */
119
+ peerNames() {
120
+ return [...this.peers.keys()];
121
+ }
122
+ async close() {
123
+ for (const p of this.peers.values())
124
+ p.socket.destroy();
125
+ this.peers.clear();
126
+ await new Promise((resolve) => this.server.close(() => resolve()));
127
+ }
128
+ // ── connection lifecycle ──────────────────────────────────────────────────
129
+ _handleConnection(socket) {
130
+ const conn = { name: "", cwd: "", address: "", socket, buf: "" };
131
+ socket.setEncoding("utf8");
132
+ socket.on("data", (chunk) => this._onData(conn, chunk));
133
+ socket.on("close", () => this._onClose(conn));
134
+ socket.on("error", () => {
135
+ /* ignored — close will follow */
136
+ });
137
+ }
138
+ _onData(conn, chunk) {
139
+ conn.buf += chunk;
140
+ let nl;
141
+ while ((nl = conn.buf.indexOf("\n")) >= 0) {
142
+ const line = conn.buf.slice(0, nl);
143
+ conn.buf = conn.buf.slice(nl + 1);
144
+ if (!line)
145
+ continue;
146
+ void this._handleLine(conn, line);
147
+ }
148
+ }
149
+ async _handleLine(conn, line) {
150
+ // Unregistered conn: a read-only `list_peers` probe (the `unbien peers`
151
+ // CLI — answered without registering, so it leaves no trace on the mesh) or
152
+ // the mandatory `register` handshake. Anything else `_handleRegister` drops.
153
+ if (!conn.name) {
154
+ if (this._tryObserverProbe(conn, line))
155
+ return;
156
+ this._handleRegister(conn, line);
157
+ return;
158
+ }
159
+ // Already registered — must be a regular envelope.
160
+ let env;
161
+ try {
162
+ env = parse(line);
163
+ }
164
+ catch (e) {
165
+ if (e instanceof EnvelopeError)
166
+ return; // malformed; drop silently
167
+ throw e;
168
+ }
169
+ // Force `from` to the registered ADDRESS (security: peer can't spoof; and
170
+ // replies/ACKs address back by the same canonical key the Map is keyed on).
171
+ env.from = conn.address;
172
+ await this._route(env);
173
+ }
174
+ _handleRegister(conn, line) {
175
+ let req;
176
+ try {
177
+ const parsed = JSON.parse(line);
178
+ if (!parsed ||
179
+ typeof parsed !== "object" ||
180
+ parsed.type !== "register" ||
181
+ typeof parsed.name !== "string") {
182
+ conn.socket.destroy();
183
+ return;
184
+ }
185
+ req = parsed;
186
+ }
187
+ catch {
188
+ conn.socket.destroy();
189
+ return;
190
+ }
191
+ // (cwd, name) identity (plan/38). The cwd is the first-class axis: the
192
+ // address embeds it, so two same-named agents in DIFFERENT folders get
193
+ // distinct addresses and never collide. Legacy peers (no cwd) keep the old
194
+ // global-name behavior. New peers can opt into exact-address takeover for
195
+ // same-folder reincarnations such as daemon restarts.
196
+ const requestedCwd = req.cwd === undefined ? "" : req.cwd;
197
+ if (typeof requestedCwd !== "string" ||
198
+ !isValidRegisteredCwd(requestedCwd)) {
199
+ conn.socket.destroy();
200
+ return;
201
+ }
202
+ const identity = this._identityForRegister(requestedCwd, req.name, req.takeover === true);
203
+ if (!identity) {
204
+ conn.socket.destroy();
205
+ return;
206
+ }
207
+ conn.cwd = requestedCwd;
208
+ conn.name = identity.name;
209
+ conn.address = identity.address;
210
+ // Candidate validity is established before a takeover evicts its prior
211
+ // connection, so a rejected replacement cannot drop a healthy peer.
212
+ if (identity.replaceAddress)
213
+ this._dropPeerAt(identity.replaceAddress);
214
+ this.peers.set(identity.address, conn);
215
+ // `name_assigned` doubles as the compat alias: for a legacy peer it equals
216
+ // `address_assigned` (cwd empty → address == name), so old clients that read
217
+ // `name_assigned` still get a routable identity.
218
+ const ack = {
219
+ type: "register_ack",
220
+ address_assigned: conn.address,
221
+ name_assigned: conn.name,
222
+ };
223
+ try {
224
+ conn.socket.write(JSON.stringify(ack) + "\n");
225
+ }
226
+ catch {
227
+ /* peer hung up */
228
+ }
229
+ // Notify others (peer_joined broadcast). The field carries the ADDRESS.
230
+ this._broadcastSystem({ type: "peer_joined", name: conn.address, address: conn.address }, conn.address);
231
+ }
232
+ /**
233
+ * Answer a read-only `list_peers` request from an UNREGISTERED connection
234
+ * (the `unbien peers` CLI probe). Returns true when the line was such a
235
+ * probe — the reply is written and the connection stays unregistered: no
236
+ * name assigned, no `peer_joined`/`peer_left` broadcast, no sibling push, so
237
+ * querying the roster from the shell never perturbs the mesh. Returns false
238
+ * (not a probe) so the caller falls through to the register handshake.
239
+ */
240
+ _tryObserverProbe(conn, line) {
241
+ let parsed;
242
+ try {
243
+ parsed = JSON.parse(line);
244
+ }
245
+ catch {
246
+ return false; // not JSON → let _handleRegister destroy it
247
+ }
248
+ if (!parsed || typeof parsed !== "object" || parsed.type !== "list_peers") {
249
+ return false;
250
+ }
251
+ const reply = {
252
+ from: BROKER_NAME,
253
+ to: "observer", // synthetic: the conn has no registered name
254
+ id: uuidv7(),
255
+ re: null,
256
+ body: {
257
+ type: "list_peers_reply",
258
+ peers: this._allPeerNames(),
259
+ peers_detailed: this._allPeerInfos(),
260
+ },
261
+ };
262
+ try {
263
+ conn.socket.write(serialize(reply));
264
+ }
265
+ catch {
266
+ /* probe hung up */
267
+ }
268
+ return true;
269
+ }
270
+ /** Local UDS peer names plus cross-PC `<pc>:<peer>` entries from the remote
271
+ * router (empty when no bridge). Shared by the registered `list_peers`
272
+ * handler and the unregistered observer probe. */
273
+ _allPeerNames() {
274
+ const remote = this.remoteRouter ? this.remoteRouter.listRemotePeers() : [];
275
+ return [...this.peerNames(), ...remote];
276
+ }
277
+ /** Structured roster of LOCAL UDS peers (plan/38): one `PeerInfo` each, no
278
+ * `pc` (they're on this machine). Public so the cross-PC router
279
+ * (`broker_remote`) can read the authoritative local inventory directly to
280
+ * push to siblings — no `list_peers` round-trip, no stale cache. */
281
+ localPeerInfos() {
282
+ return [...this.peers.values()].map((p) => ({
283
+ cwd: p.cwd,
284
+ name: p.name,
285
+ address: p.address,
286
+ }));
287
+ }
288
+ /** Structured roster (plan/38): local peers (no `pc`) + cross-PC peers with
289
+ * `pc`/`cwd`/`name` filled by the remote router (Fase 2). */
290
+ _allPeerInfos() {
291
+ const remote = this.remoteRouter?.listRemotePeerInfos() ?? [];
292
+ return [...this.localPeerInfos(), ...remote];
293
+ }
294
+ /**
295
+ * Resolve a free `(name, address)` for a register, keyed by **(cwd, name)**
296
+ * (plan/38): the collision check is on the composed ADDRESS, so a name only
297
+ * collides with another peer in the SAME cwd. `#N` is appended to the name
298
+ * (matching the cwd-lock's suffix scheme) until the address is free; for a
299
+ * legacy peer (cwd "") the address is the name, preserving global-name `#N`.
300
+ */
301
+ _identityForRegister(cwd, requested, takeover) {
302
+ const sanitized = sanitizeMeshName(requested);
303
+ const candidateFor = (name) => {
304
+ const address = composeAddress({ cwd, name });
305
+ return isBoundedPeerInfo({ cwd, name, address })
306
+ ? { name, address }
307
+ : null;
308
+ };
309
+ const direct = candidateFor(sanitized);
310
+ if (!direct)
311
+ return null;
312
+ if (takeover && cwd && this.peers.has(direct.address)) {
313
+ return { ...direct, replaceAddress: direct.address };
314
+ }
315
+ if (this.peers.size >= MAX_PEERS_UPDATE_ENTRIES)
316
+ return null;
317
+ if (!this.peers.has(direct.address))
318
+ return direct;
319
+ // Collision: strip any client-provided `#N`, then re-suffix from #2.
320
+ const base = sanitized.replace(/#\d+$/, "");
321
+ for (let n = 2; n < 1000; n++) {
322
+ const candidate = candidateFor(`${base}#${n}`);
323
+ if (!candidate)
324
+ return null;
325
+ if (!this.peers.has(candidate.address))
326
+ return candidate;
327
+ }
328
+ return null;
329
+ }
330
+ _dropPeerAt(address) {
331
+ const existing = this.peers.get(address);
332
+ if (!existing)
333
+ return;
334
+ this.peers.delete(address);
335
+ // The old socket's close event may arrive after the replacement has been
336
+ // inserted. Clear its address so it cannot delete the replacement.
337
+ existing.address = "";
338
+ try {
339
+ existing.socket.destroy();
340
+ }
341
+ catch {
342
+ /* ignored */
343
+ }
344
+ }
345
+ _onClose(conn) {
346
+ if (!conn.address)
347
+ return;
348
+ if (this.peers.get(conn.address) !== conn)
349
+ return;
350
+ this.peers.delete(conn.address);
351
+ this._broadcastSystem({ type: "peer_left", name: conn.address, address: conn.address }, conn.address);
352
+ }
353
+ // ── routing ───────────────────────────────────────────────────────────────
354
+ async _route(env) {
355
+ // Special handling for messages addressed to the broker itself.
356
+ if (env.to === BROKER_NAME) {
357
+ this._handleBrokerMessage(env);
358
+ return;
359
+ }
360
+ // Give known cross-PC aliases first chance to route. A syntactically
361
+ // absolute Windows drive address contains a colon but is always exact
362
+ // local; all other local registrations may not shadow a known alias.
363
+ const exactLocal = typeof env.to === "string" ? this.peers.get(env.to) : undefined;
364
+ const exactWindowsDriveLocal = !!exactLocal && isWindowsDriveAbsolutePath(exactLocal.cwd);
365
+ if (!exactWindowsDriveLocal &&
366
+ this.remoteRouter &&
367
+ typeof env.to === "string") {
368
+ if (this.remoteRouter.tryRouteOutbound(env))
369
+ return;
370
+ }
371
+ const targets = this._resolveTargets(env);
372
+ const delivered = [];
373
+ const line = serialize(env);
374
+ const isUnicast = typeof env.to === "string" && env.to !== "broadcast";
375
+ // plan/34: reliable delivery — always write to the target's socket. The
376
+ // Pi harness enqueues messages that arrive mid-turn, so there is no
377
+ // busy-drop and `busy` is no longer a possible ACK status. Unicast sends
378
+ // to an online peer always ACK `received`.
379
+ let ackStatus = "none";
380
+ for (const targetName of targets) {
381
+ const peer = this.peers.get(targetName);
382
+ if (!peer)
383
+ continue; // unknown peer: silent drop (sender times out)
384
+ try {
385
+ peer.socket.write(line);
386
+ delivered.push(targetName);
387
+ if (isUnicast) {
388
+ ackStatus = "received";
389
+ this._sendAckToSender(env, "received", targetName);
390
+ }
391
+ }
392
+ catch {
393
+ // peer dropped mid-write — close handler will fire; treat as silent
394
+ }
395
+ }
396
+ if (this.auditPath)
397
+ await this._appendAudit(env, delivered, ackStatus);
398
+ this.onRouted?.(env, delivered);
399
+ }
400
+ _resolveTargets(env) {
401
+ if (env.to === "broadcast") {
402
+ // plan/38 decision C: broadcast is scoped to the sender's cwd (folder
403
+ // colleagues), local-only. A peer in /a/b never hears /a/c. The sender is
404
+ // keyed by its address (= env.from); legacy peers (cwd "") broadcast among
405
+ // other cwd-less peers, matching pre-plan/38 behavior.
406
+ const sender = this.peers.get(env.from);
407
+ const scope = sender?.cwd ?? "";
408
+ return [...this.peers.values()]
409
+ .filter((p) => p.address !== env.from && p.cwd === scope)
410
+ .map((p) => p.address);
411
+ }
412
+ if (Array.isArray(env.to)) {
413
+ return env.to.filter((n) => n !== env.from);
414
+ }
415
+ // Unicast: drop self-loops too. The skill warns "useless" but the LLM
416
+ // might still try (especially with deceiving `re` reply chains). A
417
+ // self-loop has no upside and risks unbounded message ↔ inject ↔ message
418
+ // cycles when the inbound injector tells the LLM "reply with re=…".
419
+ if (env.to === env.from)
420
+ return [];
421
+ return [env.to];
422
+ }
423
+ /**
424
+ * Writes an ACK envelope to the original sender's socket. Synchronous —
425
+ * the caller is inside `_route` and must keep busy-check/busy-set atomic.
426
+ * Broker → sender: `from="broker"`, `to=env.from`, `re=env.id`,
427
+ * `body={type:"ack", status, target}`.
428
+ */
429
+ _sendAckToSender(env, status, target) {
430
+ const sender = this.peers.get(env.from);
431
+ if (!sender)
432
+ return; // sender vanished mid-write
433
+ const body = { type: "ack", status, target };
434
+ const ackEnv = {
435
+ from: BROKER_NAME,
436
+ to: env.from,
437
+ id: uuidv7(),
438
+ re: env.id,
439
+ body,
440
+ };
441
+ try {
442
+ sender.socket.write(serialize(ackEnv));
443
+ }
444
+ catch {
445
+ /* sender dropped; close handler will fire */
446
+ }
447
+ }
448
+ _handleBrokerMessage(env) {
449
+ const body = env.body;
450
+ if (!body || typeof body !== "object")
451
+ return;
452
+ if (body.type === "list_peers") {
453
+ const reply = {
454
+ from: BROKER_NAME,
455
+ to: env.from,
456
+ id: uuidv7(),
457
+ re: env.id,
458
+ body: {
459
+ type: "list_peers_reply",
460
+ peers: this._allPeerNames(), // addresses — legacy clients route by these
461
+ peers_detailed: this._allPeerInfos(), // plan/38 — clients group without parsing
462
+ },
463
+ };
464
+ const peer = this.peers.get(env.from);
465
+ if (peer) {
466
+ try {
467
+ peer.socket.write(serialize(reply));
468
+ }
469
+ catch {
470
+ /* ignored */
471
+ }
472
+ }
473
+ return;
474
+ }
475
+ // plan/34: `turn_state` is no longer consumed — the broker doesn't gate
476
+ // delivery on busy state. The Pi extension still publishes working state
477
+ // as room_meta over the relay (index.ts), independent of the broker.
478
+ }
479
+ _broadcastSystem(body, excludeAddress) {
480
+ for (const [address, peer] of this.peers) {
481
+ if (address === excludeAddress)
482
+ continue;
483
+ const env = {
484
+ from: BROKER_NAME,
485
+ to: address,
486
+ id: uuidv7(),
487
+ re: null,
488
+ body,
489
+ };
490
+ try {
491
+ peer.socket.write(serialize(env));
492
+ }
493
+ catch {
494
+ /* ignored */
495
+ }
496
+ }
497
+ }
498
+ async _appendAudit(env, delivered, ackStatus,
499
+ /**
500
+ * Plan/25 Wave D: provenance hint for the audit reader. `"relay"` marks
501
+ * envelopes injected via `injectFromRemote` (cross-PC). Local UDS
502
+ * delivery keeps the default `"uds"` so existing audit consumers see
503
+ * a uniform field rather than an undefined hole.
504
+ */
505
+ via = "uds") {
506
+ if (!this.auditPath)
507
+ return;
508
+ const line = JSON.stringify({
509
+ ts: Date.now(),
510
+ from: env.from,
511
+ to: env.to,
512
+ id: env.id,
513
+ re: env.re,
514
+ delivered,
515
+ ack_status: ackStatus,
516
+ via,
517
+ }) + "\n";
518
+ try {
519
+ await mkdir(dirname(this.auditPath), { recursive: true });
520
+ await appendFile(this.auditPath, line, "utf8");
521
+ }
522
+ catch {
523
+ /* audit best-effort */
524
+ }
525
+ }
526
+ }
527
+ //# sourceMappingURL=broker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broker.js","sourceRoot":"","sources":["../../src/session/broker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import type { Broker, PeerInfo, RemoteRouter } from "./broker.js";
2
+ import { type Envelope } from "./envelope.js";
3
+ import type { PiForwardClient } from "../transport/pi_forward_client.js";
4
+ import type { MeshTopologySnapshot } from "../mesh/siblings.js";
5
+ export interface WirePeerInfo {
6
+ cwd: string;
7
+ name: string;
8
+ address: string;
9
+ }
10
+ export interface RemotePeerEntry {
11
+ infos: WirePeerInfo[];
12
+ pcPubkey: string;
13
+ ts: number;
14
+ }
15
+ export interface BrokerRemoteOptions {
16
+ broker: Broker;
17
+ pi: PiForwardClient;
18
+ topology: MeshTopologySnapshot;
19
+ cacheTtlMs?: number;
20
+ reannounceIntervalMs?: number;
21
+ log?: (msg: string) => void;
22
+ /** Defaults true for compatibility; Task 6 bridge construction passes false. */
23
+ activateOnConstruct?: boolean;
24
+ }
25
+ export interface BrokerRemoteLifecycle {
26
+ activate(): void;
27
+ setTopology(next: MeshTopologySnapshot): void;
28
+ }
29
+ export declare class BrokerRemote implements RemoteRouter, BrokerRemoteLifecycle {
30
+ private readonly broker;
31
+ private readonly pi;
32
+ private readonly technicalSelfPubkey;
33
+ private readonly cacheTtlMs;
34
+ private readonly reannounceIntervalMs;
35
+ private readonly log;
36
+ private routing;
37
+ /** Canonical sibling pubkey → cached local roster. */
38
+ private readonly remotePeers;
39
+ /** Canonical sibling pubkeys whose active topology refresh must be retried. */
40
+ private readonly topologyRefreshNeeded;
41
+ /** Canonical sibling pubkey → in-flight roster fills. */
42
+ private readonly pendingFills;
43
+ private readonly onIncoming;
44
+ private reannounceTimer;
45
+ private lifecycle;
46
+ constructor(opts: BrokerRemoteOptions);
47
+ activate(): void;
48
+ detach(): void;
49
+ setTopology(next: MeshTopologySnapshot): void;
50
+ private _bootstrapWithSiblings;
51
+ private _localPeersBody;
52
+ private _remoteInfosByPubkey;
53
+ getRemotePeers(pcLabel: string): string[];
54
+ getAllRemote(): Record<string, string[]>;
55
+ listRemotePeers(): string[];
56
+ listRemotePeerInfos(): PeerInfo[];
57
+ onLocalPeersChanged(_peers: string[]): void;
58
+ tryRouteOutbound(env: Envelope): boolean;
59
+ private _routeToCanonicalSibling;
60
+ handleIncoming(env: Envelope, fromPc: string): void;
61
+ private _setRemoteCache;
62
+ private _awaitPeersFill;
63
+ private _clearPendingFills;
64
+ private _clearAllPendingFills;
65
+ private _propagateTransportError;
66
+ private _sendControlEnvelope;
67
+ private _legacyLabelForPubkey;
68
+ private _dropMetadataOnly;
69
+ private _logMetadataOnly;
70
+ }
71
+ export declare function parseAddress(to: string): {
72
+ pcLabel: string;
73
+ peerName: string;
74
+ } | null;