@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 @@
1
+ {"version":3,"file":"peer_channel.js","sourceRoot":"","sources":["../../src/transport/peer_channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { RelayClient } from "./relay_client.js";
3
+ import type { Envelope } from "../session/envelope.js";
4
+ /** Outbound API + inbound listener for Pi↔Pi envelope forwarding via relay. */
5
+ export interface PiForwardClientEvents {
6
+ /**
7
+ * Emitted whenever the relay delivers a `pi_envelope_in` frame addressed
8
+ * to this Pi. `fromPc` is the verified Pi-pubkey of the sender (relay
9
+ * authoritative — defense against spoofed `envelope.from`).
10
+ */
11
+ envelope: [env: Envelope, fromPc: string];
12
+ }
13
+ export declare class PiForwardClient extends EventEmitter {
14
+ private readonly relay;
15
+ private readonly onRelayMessage;
16
+ private detached;
17
+ constructor(relay: RelayClient);
18
+ /**
19
+ * Pack `env` in a `pi_envelope` frame addressed to `toPc` and send via
20
+ * the relay WS. Best-effort: if the relay is not connected, the call is
21
+ * silently dropped. The caller (broker_remote) handles the timeout via
22
+ * its outstanding-ACK map — a missing ACK from the destination wrapper
23
+ * surfaces as `status: "timeout"` upstream regardless.
24
+ */
25
+ sendEnvelopeToPi(toPc: string, env: Envelope): void;
26
+ /** Stop listening to the relay. Call from `_goIdle` / shutdown. */
27
+ detach(): void;
28
+ private _handleLine;
29
+ }
@@ -0,0 +1,68 @@
1
+ import { EventEmitter } from "node:events";
2
+ export class PiForwardClient extends EventEmitter {
3
+ relay;
4
+ onRelayMessage;
5
+ detached = false;
6
+ constructor(relay) {
7
+ super();
8
+ this.relay = relay;
9
+ this.onRelayMessage = (line) => this._handleLine(line);
10
+ this.relay.on("message", this.onRelayMessage);
11
+ }
12
+ /**
13
+ * Pack `env` in a `pi_envelope` frame addressed to `toPc` and send via
14
+ * the relay WS. Best-effort: if the relay is not connected, the call is
15
+ * silently dropped. The caller (broker_remote) handles the timeout via
16
+ * its outstanding-ACK map — a missing ACK from the destination wrapper
17
+ * surfaces as `status: "timeout"` upstream regardless.
18
+ */
19
+ sendEnvelopeToPi(toPc, env) {
20
+ if (this.detached)
21
+ return;
22
+ const frame = {
23
+ type: "pi_envelope",
24
+ to_pc: toPc,
25
+ envelope: env,
26
+ };
27
+ try {
28
+ this.relay.send(JSON.stringify(frame));
29
+ }
30
+ catch {
31
+ // relay not connected; broker_remote's pending logic will time out
32
+ }
33
+ }
34
+ /** Stop listening to the relay. Call from `_goIdle` / shutdown. */
35
+ detach() {
36
+ if (this.detached)
37
+ return;
38
+ this.detached = true;
39
+ this.relay.off("message", this.onRelayMessage);
40
+ }
41
+ _handleLine(line) {
42
+ // The relay multiplexes several frame types over the same WS; we only
43
+ // care about `pi_envelope_in`. Other frames (outer-encrypted owner
44
+ // envelopes, control replies) are silently ignored.
45
+ let parsed;
46
+ try {
47
+ parsed = JSON.parse(line);
48
+ }
49
+ catch {
50
+ return;
51
+ }
52
+ if (!parsed || typeof parsed !== "object")
53
+ return;
54
+ const o = parsed;
55
+ if (o.type !== "pi_envelope_in")
56
+ return;
57
+ if (typeof o.from_pc !== "string" ||
58
+ !o.envelope ||
59
+ typeof o.envelope !== "object")
60
+ return;
61
+ // Cheap shape check — full envelope parse happens downstream in broker_remote.
62
+ const env = o.envelope;
63
+ if (typeof env.from !== "string" || typeof env.id !== "string")
64
+ return;
65
+ this.emit("envelope", env, o.from_pc);
66
+ }
67
+ }
68
+ //# sourceMappingURL=pi_forward_client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi_forward_client.js","sourceRoot":"","sources":["../../src/transport/pi_forward_client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA2C3C,MAAM,OAAO,eAAgB,SAAQ,YAAY;IAIlB;IAHZ,cAAc,CAAyB;IAChD,QAAQ,GAAG,KAAK,CAAC;IAEzB,YAA6B,KAAkB;QAC7C,KAAK,EAAE,CAAC;QADmB,UAAK,GAAL,KAAK,CAAa;QAE7C,IAAI,CAAC,cAAc,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACvD,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IAChD,CAAC;IAED;;;;;;OAMG;IACH,gBAAgB,CAAC,IAAY,EAAE,GAAa;QAC1C,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,MAAM,KAAK,GAAoB;YAC7B,IAAI,EAAE,aAAa;YACnB,KAAK,EAAE,IAAI;YACX,QAAQ,EAAE,GAAG;SACd,CAAC;QACF,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;QACrE,CAAC;IACH,CAAC;IAED,mEAAmE;IACnE,MAAM;QACJ,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IACjD,CAAC;IAEO,WAAW,CAAC,IAAY;QAC9B,sEAAsE;QACtE,mEAAmE;QACnE,oDAAoD;QACpD,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO;QAClD,MAAM,CAAC,GAAG,MAAoC,CAAC;QAC/C,IAAI,CAAC,CAAC,IAAI,KAAK,gBAAgB;YAAE,OAAO;QACxC,IACE,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ;YAC7B,CAAC,CAAC,CAAC,QAAQ;YACX,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ;YAE9B,OAAO;QAET,+EAA+E;QAC/E,MAAM,GAAG,GAAG,CAAC,CAAC,QAAoB,CAAC;QACnC,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;YAAE,OAAO;QACvE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;CACF"}
@@ -0,0 +1,96 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { Ed25519Keypair } from "../pairing/crypto.js";
3
+ import type { ThinkingLevel } from "../protocol/types.js";
4
+ export interface RoomMeta {
5
+ name: string;
6
+ cwd: string;
7
+ /** Friendly model name (e.g. "claude-sonnet-4.5"). Optional — pi-ext sends
8
+ * when `ExtensionContext.model` is available; relay/app tolerate absence. */
9
+ model?: string;
10
+ }
11
+ /** Control frame sent to relay (not routed to app peer). Each publish carries
12
+ * ONE changed field (model / working / thinking); the relay merges them into
13
+ * the room's meta and fans a `room_meta_updated` to the app. */
14
+ export interface RoomMetaUpdateFrame {
15
+ type: "room_meta_update";
16
+ room_id: string;
17
+ meta: {
18
+ model?: string;
19
+ working?: boolean;
20
+ thinking?: ThinkingLevel;
21
+ };
22
+ }
23
+ export interface ConnectOptions {
24
+ roomId?: string;
25
+ roomMeta?: RoomMeta;
26
+ }
27
+ /** Relay rejected hello because another peer already holds (pubkey, room_id). */
28
+ export declare class RoomAlreadyOpenError extends Error {
29
+ readonly roomId: string | undefined;
30
+ constructor(roomId: string | undefined);
31
+ }
32
+ export interface RelayClientEvents {
33
+ /** A single JSONL line delivered by the relay (outer envelope). */
34
+ message: [line: string];
35
+ close: [];
36
+ error: [err: Error];
37
+ }
38
+ /**
39
+ * Thin WebSocket client for the Remote Pi relay.
40
+ *
41
+ * Lifecycle:
42
+ * const relay = new RelayClient(url, ed25519Keypair)
43
+ * await relay.connect() // opens WS + runs Ed25519 challenge-response
44
+ * relay.on("message", line => …) // outer envelopes: { peer, ct }
45
+ * relay.send(jsonLine) // write to relay
46
+ * relay.close()
47
+ *
48
+ * Auth sequence (pairing.md §Challenge-response):
49
+ * → { type:"hello", pubkey: "<Ed25519 pubkey base64>" }
50
+ * ← { type:"challenge", nonce: "<32 bytes base64>" }
51
+ * → { type:"auth", sig: "<Ed25519 sig base64>" }
52
+ */
53
+ export declare class RelayClient extends EventEmitter {
54
+ private readonly url;
55
+ private readonly keypair;
56
+ private ws;
57
+ /** Epoch ms of the last inbound frame (message / relay ping / pong). */
58
+ private lastActivityAt;
59
+ private livenessTimer;
60
+ constructor(url: string, keypair: Ed25519Keypair);
61
+ /**
62
+ * Connects and completes Ed25519 auth. Resolves when relay is ready.
63
+ *
64
+ * `options.roomId` (12-char id derived from cwd, see `src/rooms.ts`) is
65
+ * included in the hello so the relay can multiplex N concurrent peers
66
+ * with the same Ed25519 pubkey but different rooms (one pi-ext per cwd).
67
+ * Omitting `roomId` is backward-compat with old relays (treated as the
68
+ * default "main" room server-side).
69
+ *
70
+ * Throws `RoomAlreadyOpenError` if the relay rejects the hello because
71
+ * another peer already holds the (pubkey, room_id) tuple. Caller (e.g.
72
+ * `_cmdStart`) maps that to a clearer UI message.
73
+ */
74
+ connect(options?: ConnectOptions): Promise<void>;
75
+ /** True only while the authenticated Relay WebSocket is currently open. */
76
+ isOpen(): boolean;
77
+ /** Sends a raw line to the relay (caller is responsible for framing). */
78
+ send(line: string): void;
79
+ /**
80
+ * Sends a control frame to the relay (not routed to app peer). Used for
81
+ * out-of-band metadata updates like `room_meta_update`. Silently no-ops if
82
+ * the WS isn't open (best-effort: control frames are observational; we
83
+ * don't want them throwing inside SDK event callbacks).
84
+ */
85
+ sendControl(frame: RoomMetaUpdateFrame): void;
86
+ close(): void;
87
+ /** Force-close the WS when no inbound frame has arrived for
88
+ * `LIVENESS_TIMEOUT_MS` — see the constant's doc for why. `terminate()`
89
+ * fires `close`, which the owner turns into a reconnect. */
90
+ private _startLiveness;
91
+ private _stopLiveness;
92
+ private _authenticate;
93
+ /** Waits for the next single WS message with a timeout. */
94
+ private _nextMsg;
95
+ private _rawSend;
96
+ }
@@ -0,0 +1,210 @@
1
+ import { EventEmitter } from "node:events";
2
+ import WebSocket from "ws";
3
+ import { ed25519Sign } from "../pairing/crypto.js";
4
+ const AUTH_TIMEOUT_MS = 5_000;
5
+ /**
6
+ * Liveness watchdog. The relay sends a WS Ping every ~25s (relay `peer.rs`),
7
+ * so a healthy connection sees inbound frames at least that often. If NOTHING
8
+ * arrives for this long the socket is silently dead — NAT/router idle drop,
9
+ * laptop sleep, or the relay/Cloudflare reaping the connection WITHOUT a clean
10
+ * close frame. That half-open case never fires `close`, so reconnect never
11
+ * triggers and a background daemon sits "online" but dead after a few idle
12
+ * hours. We force-close on timeout so `close` drives the caller's reconnect.
13
+ */
14
+ const LIVENESS_TIMEOUT_MS = 70_000; // ~2.8 missed relay pings → confidently dead
15
+ const LIVENESS_CHECK_MS = 20_000;
16
+ /** Relay rejected hello because another peer already holds (pubkey, room_id). */
17
+ export class RoomAlreadyOpenError extends Error {
18
+ roomId;
19
+ constructor(roomId) {
20
+ super(roomId
21
+ ? `relay rejected hello: room ${roomId} already open for this peer`
22
+ : "relay rejected hello: peer already connected");
23
+ this.roomId = roomId;
24
+ this.name = "RoomAlreadyOpenError";
25
+ }
26
+ }
27
+ /**
28
+ * Thin WebSocket client for the Remote Pi relay.
29
+ *
30
+ * Lifecycle:
31
+ * const relay = new RelayClient(url, ed25519Keypair)
32
+ * await relay.connect() // opens WS + runs Ed25519 challenge-response
33
+ * relay.on("message", line => …) // outer envelopes: { peer, ct }
34
+ * relay.send(jsonLine) // write to relay
35
+ * relay.close()
36
+ *
37
+ * Auth sequence (pairing.md §Challenge-response):
38
+ * → { type:"hello", pubkey: "<Ed25519 pubkey base64>" }
39
+ * ← { type:"challenge", nonce: "<32 bytes base64>" }
40
+ * → { type:"auth", sig: "<Ed25519 sig base64>" }
41
+ */
42
+ export class RelayClient extends EventEmitter {
43
+ url;
44
+ keypair;
45
+ ws = null;
46
+ /** Epoch ms of the last inbound frame (message / relay ping / pong). */
47
+ lastActivityAt = 0;
48
+ livenessTimer = null;
49
+ constructor(url, keypair) {
50
+ super();
51
+ this.url = url;
52
+ this.keypair = keypair;
53
+ }
54
+ // ── Public API ──────────────────────────────────────────────────────────────
55
+ /**
56
+ * Connects and completes Ed25519 auth. Resolves when relay is ready.
57
+ *
58
+ * `options.roomId` (12-char id derived from cwd, see `src/rooms.ts`) is
59
+ * included in the hello so the relay can multiplex N concurrent peers
60
+ * with the same Ed25519 pubkey but different rooms (one pi-ext per cwd).
61
+ * Omitting `roomId` is backward-compat with old relays (treated as the
62
+ * default "main" room server-side).
63
+ *
64
+ * Throws `RoomAlreadyOpenError` if the relay rejects the hello because
65
+ * another peer already holds the (pubkey, room_id) tuple. Caller (e.g.
66
+ * `_cmdStart`) maps that to a clearer UI message.
67
+ */
68
+ async connect(options = {}) {
69
+ return new Promise((resolve, reject) => {
70
+ const ws = new WebSocket(this.url);
71
+ this.ws = ws;
72
+ ws.on("error", (err) => reject(err));
73
+ ws.on("open", async () => {
74
+ try {
75
+ await this._authenticate(ws, options);
76
+ // Auth done — wire persistent message handler. Every inbound frame
77
+ // (data, plus the relay's keepalive ping/pong below) refreshes the
78
+ // liveness clock.
79
+ this.lastActivityAt = Date.now();
80
+ ws.on("message", (raw) => {
81
+ this.lastActivityAt = Date.now();
82
+ const text = Buffer.isBuffer(raw) ? raw.toString() : String(raw);
83
+ for (const line of text.split("\n")) {
84
+ const trimmed = line.trim();
85
+ if (trimmed)
86
+ this.emit("message", trimmed);
87
+ }
88
+ });
89
+ // The relay pings every ~25s; `ws` auto-replies Pong (keeping the
90
+ // relay's view of us alive). The relay ignores client pings rather
91
+ // than ponging, so these inbound pings — not a ping/pong we initiate
92
+ // — are our liveness signal.
93
+ ws.on("ping", () => {
94
+ this.lastActivityAt = Date.now();
95
+ });
96
+ ws.on("pong", () => {
97
+ this.lastActivityAt = Date.now();
98
+ });
99
+ ws.on("close", () => {
100
+ this._stopLiveness();
101
+ this.emit("close");
102
+ });
103
+ this._startLiveness(ws);
104
+ resolve();
105
+ }
106
+ catch (err) {
107
+ ws.terminate();
108
+ reject(err);
109
+ }
110
+ });
111
+ });
112
+ }
113
+ /** True only while the authenticated Relay WebSocket is currently open. */
114
+ isOpen() {
115
+ return this.ws?.readyState === WebSocket.OPEN;
116
+ }
117
+ /** Sends a raw line to the relay (caller is responsible for framing). */
118
+ send(line) {
119
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
120
+ throw new Error("relay: not connected");
121
+ }
122
+ this.ws.send(line);
123
+ }
124
+ /**
125
+ * Sends a control frame to the relay (not routed to app peer). Used for
126
+ * out-of-band metadata updates like `room_meta_update`. Silently no-ops if
127
+ * the WS isn't open (best-effort: control frames are observational; we
128
+ * don't want them throwing inside SDK event callbacks).
129
+ */
130
+ sendControl(frame) {
131
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
132
+ return;
133
+ this.ws.send(JSON.stringify(frame));
134
+ }
135
+ close() {
136
+ this._stopLiveness();
137
+ this.ws?.close();
138
+ this.ws = null;
139
+ }
140
+ // ── Liveness watchdog ─────────────────────────────────────────────────────
141
+ /** Force-close the WS when no inbound frame has arrived for
142
+ * `LIVENESS_TIMEOUT_MS` — see the constant's doc for why. `terminate()`
143
+ * fires `close`, which the owner turns into a reconnect. */
144
+ _startLiveness(ws) {
145
+ this._stopLiveness();
146
+ this.livenessTimer = setInterval(() => {
147
+ if (Date.now() - this.lastActivityAt > LIVENESS_TIMEOUT_MS) {
148
+ this._stopLiveness();
149
+ ws.terminate();
150
+ }
151
+ }, LIVENESS_CHECK_MS);
152
+ }
153
+ _stopLiveness() {
154
+ if (this.livenessTimer) {
155
+ clearInterval(this.livenessTimer);
156
+ this.livenessTimer = null;
157
+ }
158
+ }
159
+ // ── Auth ────────────────────────────────────────────────────────────────────
160
+ async _authenticate(ws, opts) {
161
+ const pubkeyB64 = Buffer.from(this.keypair.publicKey).toString("base64");
162
+ const hello = { type: "hello", pubkey: pubkeyB64 };
163
+ if (opts.roomId)
164
+ hello.room_id = opts.roomId;
165
+ if (opts.roomMeta)
166
+ hello.room_meta = opts.roomMeta;
167
+ this._rawSend(ws, JSON.stringify(hello));
168
+ const challengeRaw = await this._nextMsg(ws);
169
+ let challenge;
170
+ try {
171
+ challenge = JSON.parse(challengeRaw);
172
+ }
173
+ catch {
174
+ throw new Error(`relay auth_failed: not JSON: ${challengeRaw}`);
175
+ }
176
+ if (challenge.type === "error") {
177
+ const code = challenge.code ?? "";
178
+ if (code === "room_already_open") {
179
+ throw new RoomAlreadyOpenError(opts.roomId);
180
+ }
181
+ throw new Error(`relay rejected hello: ${code || challenge.message || "unknown"}`);
182
+ }
183
+ if (challenge.type !== "challenge" || !challenge.nonce) {
184
+ throw new Error(`relay auth_failed: expected challenge, got ${challengeRaw}`);
185
+ }
186
+ const nonce = Buffer.from(challenge.nonce, "base64");
187
+ const sig = ed25519Sign(this.keypair.secretKey, nonce);
188
+ const auth = {
189
+ type: "auth",
190
+ sig: Buffer.from(sig).toString("base64"),
191
+ };
192
+ this._rawSend(ws, JSON.stringify(auth));
193
+ // Relay does not send an explicit "ok" — it simply starts routing.
194
+ // Proceed immediately after sending auth.
195
+ }
196
+ /** Waits for the next single WS message with a timeout. */
197
+ _nextMsg(ws) {
198
+ return new Promise((resolve, reject) => {
199
+ const timer = setTimeout(() => reject(new Error("relay auth timeout")), AUTH_TIMEOUT_MS);
200
+ ws.once("message", (raw) => {
201
+ clearTimeout(timer);
202
+ resolve(Buffer.isBuffer(raw) ? raw.toString() : String(raw));
203
+ });
204
+ });
205
+ }
206
+ _rawSend(ws, data) {
207
+ ws.send(data);
208
+ }
209
+ }
210
+ //# sourceMappingURL=relay_client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay_client.js","sourceRoot":"","sources":["../../src/transport/relay_client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Footer renderer for the Pi TUI. Three status slots + window title.
3
+ *
4
+ * Slot keys (intentionally namespaced so other extensions don't collide):
5
+ * - un-bien:session — current local session + peer count
6
+ * - un-bien:relay — relay state (off / on / paired)
7
+ * - un-bien:peer-active — active mobile device, if paired
8
+ */
9
+ export interface FooterContext {
10
+ ui: {
11
+ setStatus(key: string, value: string | undefined): void;
12
+ setTitle(title: string): void;
13
+ };
14
+ }
15
+ export interface FooterState {
16
+ session?: string;
17
+ peerCount?: number;
18
+ relayOn?: boolean;
19
+ /** Active device session right now (drives the 📱 slot).
20
+ * Independent from `hasPairings` — a device may be paired globally
21
+ * in peers.json without being actively connected to THIS Pi process. */
22
+ devicePaired?: string;
23
+ /** At least one device has been paired with this machine before
24
+ * (peers.json is non-empty). Drives the 🟢/🟡 icon on the relay slot:
25
+ * 🟢 when true (ready — devices can connect), 🟡 when false (first
26
+ * pairing needed). Pairing is per-machine (global), not per-process. */
27
+ hasPairings?: boolean;
28
+ /** Assigned agent name in the current session. Becomes the title prefix
29
+ * (e.g. "backend · foo · relay") when set. Falls back to "Pi" otherwise. */
30
+ agentName?: string;
31
+ }
32
+ export declare function updateFooter(ctx: FooterContext, state: FooterState): void;
@@ -0,0 +1,34 @@
1
+ const K_SESSION = "un-bien:session";
2
+ const K_RELAY = "un-bien:relay";
3
+ const K_PEER = "un-bien:peer-active";
4
+ export function updateFooter(ctx, state) {
5
+ if (state.session) {
6
+ const count = state.peerCount ?? 0;
7
+ ctx.ui.setStatus(K_SESSION, `📡 ${state.session} (${count})`);
8
+ }
9
+ else {
10
+ ctx.ui.setStatus(K_SESSION, undefined);
11
+ }
12
+ if (state.relayOn) {
13
+ ctx.ui.setStatus(K_RELAY, state.hasPairings ? "🟢 relay" : "🟡 relay waiting for pairing");
14
+ }
15
+ else {
16
+ ctx.ui.setStatus(K_RELAY, undefined);
17
+ }
18
+ if (state.devicePaired) {
19
+ ctx.ui.setStatus(K_PEER, `📱 ${state.devicePaired}`);
20
+ }
21
+ else {
22
+ ctx.ui.setStatus(K_PEER, undefined);
23
+ }
24
+ // Terminal title — two parts only: `<agent-name> · <On|Off>`.
25
+ // Pre-2026-05-24 the title carried three segments (`name · local · relay`),
26
+ // but `local` was always the same string (single fixed UDS session) and
27
+ // `relay` repeated information the relay slot already shows. Collapsed
28
+ // to "name + relay state in plain English" — same info, clearer at a
29
+ // glance: terminal tabs read like `backend · On` / `backend · Off`.
30
+ const prefix = state.agentName?.trim() || "Pi";
31
+ const relayState = state.relayOn ? "On" : "Off";
32
+ ctx.ui.setTitle(`${prefix} · ${relayState}`);
33
+ }
34
+ //# sourceMappingURL=footer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../src/ui/footer.ts"],"names":[],"mappings":"AAiCA,MAAM,SAAS,GAAG,iBAAiB,CAAC;AACpC,MAAM,OAAO,GAAG,eAAe,CAAC;AAChC,MAAM,MAAM,GAAG,qBAAqB,CAAC;AAErC,MAAM,UAAU,YAAY,CAAC,GAAkB,EAAE,KAAkB;IACjE,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC;QACnC,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,KAAK,CAAC,OAAO,KAAK,KAAK,GAAG,CAAC,CAAC;IAChE,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,GAAG,CAAC,EAAE,CAAC,SAAS,CACd,OAAO,EACP,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,8BAA8B,CAChE,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QACvB,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;IACvD,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACtC,CAAC;IAED,8DAA8D;IAC9D,4EAA4E;IAC5E,wEAAwE;IACxE,uEAAuE;IACvE,qEAAqE;IACrE,oEAAoE;IACpE,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;IAC/C,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IAChD,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,MAAM,MAAM,UAAU,EAAE,CAAC,CAAC;AAC/C,CAAC"}