@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.
- package/LICENSE +21 -0
- package/README.md +726 -0
- package/dist/actions/handlers.d.ts +77 -0
- package/dist/actions/handlers.js +26 -0
- package/dist/actions/handlers.js.map +1 -0
- package/dist/actions/registry.d.ts +39 -0
- package/dist/actions/registry.js +62 -0
- package/dist/actions/registry.js.map +1 -0
- package/dist/bin/supervisord.d.ts +2 -0
- package/dist/bin/supervisord.js +86 -0
- package/dist/bin/supervisord.js.map +1 -0
- package/dist/config.d.ts +84 -0
- package/dist/config.js +115 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon/client.d.ts +20 -0
- package/dist/daemon/client.js +131 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/control_protocol.d.ts +176 -0
- package/dist/daemon/control_protocol.js +63 -0
- package/dist/daemon/control_protocol.js.map +1 -0
- package/dist/daemon/cron_log.d.ts +45 -0
- package/dist/daemon/cron_log.js +71 -0
- package/dist/daemon/cron_log.js.map +1 -0
- package/dist/daemon/cron_registry.d.ts +80 -0
- package/dist/daemon/cron_registry.js +198 -0
- package/dist/daemon/cron_registry.js.map +1 -0
- package/dist/daemon/id.d.ts +24 -0
- package/dist/daemon/id.js +36 -0
- package/dist/daemon/id.js.map +1 -0
- package/dist/daemon/install.d.ts +170 -0
- package/dist/daemon/install.js +603 -0
- package/dist/daemon/install.js.map +1 -0
- package/dist/daemon/registry.d.ts +63 -0
- package/dist/daemon/registry.js +153 -0
- package/dist/daemon/registry.js.map +1 -0
- package/dist/daemon/rpc_child.d.ts +194 -0
- package/dist/daemon/rpc_child.js +419 -0
- package/dist/daemon/rpc_child.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +93 -0
- package/dist/daemon/supervisor.js +666 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/extension_ui_bridge.d.ts +26 -0
- package/dist/extension_ui_bridge.js +341 -0
- package/dist/extension_ui_bridge.js.map +1 -0
- package/dist/index.d.ts +197 -0
- package/dist/index.js +5750 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/mesh_result.d.ts +10 -0
- package/dist/mcp/mesh_result.js +42 -0
- package/dist/mcp/mesh_result.js.map +1 -0
- package/dist/mcp/mesh_server.d.ts +16 -0
- package/dist/mcp/mesh_server.js +373 -0
- package/dist/mcp/mesh_server.js.map +1 -0
- package/dist/mesh/canonical.d.ts +30 -0
- package/dist/mesh/canonical.js +63 -0
- package/dist/mesh/canonical.js.map +1 -0
- package/dist/mesh/client.d.ts +18 -0
- package/dist/mesh/client.js +127 -0
- package/dist/mesh/client.js.map +1 -0
- package/dist/mesh/encoding.d.ts +38 -0
- package/dist/mesh/encoding.js +217 -0
- package/dist/mesh/encoding.js.map +1 -0
- package/dist/mesh/self_revoke.d.ts +73 -0
- package/dist/mesh/self_revoke.js +407 -0
- package/dist/mesh/self_revoke.js.map +1 -0
- package/dist/mesh/siblings.d.ts +49 -0
- package/dist/mesh/siblings.js +159 -0
- package/dist/mesh/siblings.js.map +1 -0
- package/dist/mesh/types.d.ts +34 -0
- package/dist/mesh/types.js +11 -0
- package/dist/mesh/types.js.map +1 -0
- package/dist/mesh/verify.d.ts +17 -0
- package/dist/mesh/verify.js +86 -0
- package/dist/mesh/verify.js.map +1 -0
- package/dist/pairing/crypto.d.ts +8 -0
- package/dist/pairing/crypto.js +22 -0
- package/dist/pairing/crypto.js.map +1 -0
- package/dist/pairing/qr.d.ts +59 -0
- package/dist/pairing/qr.js +127 -0
- package/dist/pairing/qr.js.map +1 -0
- package/dist/pairing/storage.d.ts +120 -0
- package/dist/pairing/storage.js +614 -0
- package/dist/pairing/storage.js.map +1 -0
- package/dist/panel_bridge.d.ts +15 -0
- package/dist/panel_bridge.js +409 -0
- package/dist/panel_bridge.js.map +1 -0
- package/dist/paths.d.ts +39 -0
- package/dist/paths.js +51 -0
- package/dist/paths.js.map +1 -0
- package/dist/protocol/codec.d.ts +7 -0
- package/dist/protocol/codec.js +48 -0
- package/dist/protocol/codec.js.map +1 -0
- package/dist/protocol/types.d.ts +404 -0
- package/dist/protocol/types.js +2 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/rooms.d.ts +42 -0
- package/dist/rooms.js +78 -0
- package/dist/rooms.js.map +1 -0
- package/dist/session/bridge.d.ts +37 -0
- package/dist/session/bridge.js +134 -0
- package/dist/session/bridge.js.map +1 -0
- package/dist/session/broker.d.ts +189 -0
- package/dist/session/broker.js +527 -0
- package/dist/session/broker.js.map +1 -0
- package/dist/session/broker_remote.d.ts +74 -0
- package/dist/session/broker_remote.js +581 -0
- package/dist/session/broker_remote.js.map +1 -0
- package/dist/session/cwd_lock.d.ts +33 -0
- package/dist/session/cwd_lock.js +127 -0
- package/dist/session/cwd_lock.js.map +1 -0
- package/dist/session/debug_log.d.ts +3 -0
- package/dist/session/debug_log.js +32 -0
- package/dist/session/debug_log.js.map +1 -0
- package/dist/session/envelope.d.ts +33 -0
- package/dist/session/envelope.js +117 -0
- package/dist/session/envelope.js.map +1 -0
- package/dist/session/global_config.d.ts +33 -0
- package/dist/session/global_config.js +73 -0
- package/dist/session/global_config.js.map +1 -0
- package/dist/session/ipc.d.ts +27 -0
- package/dist/session/ipc.js +22 -0
- package/dist/session/ipc.js.map +1 -0
- package/dist/session/leader_election.d.ts +32 -0
- package/dist/session/leader_election.js +108 -0
- package/dist/session/leader_election.js.map +1 -0
- package/dist/session/local_config.d.ts +67 -0
- package/dist/session/local_config.js +193 -0
- package/dist/session/local_config.js.map +1 -0
- package/dist/session/mesh_node.d.ts +116 -0
- package/dist/session/mesh_node.js +596 -0
- package/dist/session/mesh_node.js.map +1 -0
- package/dist/session/peer.d.ts +139 -0
- package/dist/session/peer.js +474 -0
- package/dist/session/peer.js.map +1 -0
- package/dist/session/peer_inventory.d.ts +13 -0
- package/dist/session/peer_inventory.js +48 -0
- package/dist/session/peer_inventory.js.map +1 -0
- package/dist/session/peer_limits.d.ts +9 -0
- package/dist/session/peer_limits.js +24 -0
- package/dist/session/peer_limits.js.map +1 -0
- package/dist/session/rpc_envelope.d.ts +108 -0
- package/dist/session/rpc_envelope.js +221 -0
- package/dist/session/rpc_envelope.js.map +1 -0
- package/dist/session/rpc_inbound.d.ts +55 -0
- package/dist/session/rpc_inbound.js +136 -0
- package/dist/session/rpc_inbound.js.map +1 -0
- package/dist/session/setup_wizard.d.ts +39 -0
- package/dist/session/setup_wizard.js +63 -0
- package/dist/session/setup_wizard.js.map +1 -0
- package/dist/session/tools.d.ts +24 -0
- package/dist/session/tools.js +272 -0
- package/dist/session/tools.js.map +1 -0
- package/dist/session/wizard.d.ts +13 -0
- package/dist/session/wizard.js +20 -0
- package/dist/session/wizard.js.map +1 -0
- package/dist/transport/peer_channel.d.ts +52 -0
- package/dist/transport/peer_channel.js +145 -0
- package/dist/transport/peer_channel.js.map +1 -0
- package/dist/transport/pi_forward_client.d.ts +29 -0
- package/dist/transport/pi_forward_client.js +68 -0
- package/dist/transport/pi_forward_client.js.map +1 -0
- package/dist/transport/relay_client.d.ts +96 -0
- package/dist/transport/relay_client.js +210 -0
- package/dist/transport/relay_client.js.map +1 -0
- package/dist/ui/footer.d.ts +32 -0
- package/dist/ui/footer.js +34 -0
- package/dist/ui/footer.js.map +1 -0
- package/docs/daemon.md +289 -0
- package/package.json +92 -0
- package/service-templates/launchd.plist.template +35 -0
- package/service-templates/systemd.service.template +19 -0
- package/service-templates/task-launcher.vbs.template +10 -0
- package/service-templates/task-scheduler.xml.template +38 -0
- 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"}
|