remote-pi 0.2.1 → 0.4.1

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 (46) hide show
  1. package/README.md +44 -0
  2. package/dist/actions/handlers.d.ts +116 -0
  3. package/dist/actions/handlers.js +152 -0
  4. package/dist/actions/handlers.js.map +1 -0
  5. package/dist/actions/registry.d.ts +25 -0
  6. package/dist/actions/registry.js +34 -0
  7. package/dist/actions/registry.js.map +1 -0
  8. package/dist/bin/supervisord.js +43 -1
  9. package/dist/bin/supervisord.js.map +1 -1
  10. package/dist/commands/builtin_mirror.d.ts +58 -0
  11. package/dist/commands/builtin_mirror.js +71 -0
  12. package/dist/commands/builtin_mirror.js.map +1 -0
  13. package/dist/commands/list_commands.d.ts +60 -0
  14. package/dist/commands/list_commands.js +73 -0
  15. package/dist/commands/list_commands.js.map +1 -0
  16. package/dist/daemon/control_protocol.d.ts +9 -1
  17. package/dist/daemon/control_protocol.js +1 -1
  18. package/dist/daemon/control_protocol.js.map +1 -1
  19. package/dist/daemon/rpc_child.d.ts +24 -0
  20. package/dist/daemon/rpc_child.js +41 -2
  21. package/dist/daemon/rpc_child.js.map +1 -1
  22. package/dist/daemon/supervisor.d.ts +11 -0
  23. package/dist/daemon/supervisor.js +56 -4
  24. package/dist/daemon/supervisor.js.map +1 -1
  25. package/dist/index.d.ts +7 -1
  26. package/dist/index.js +749 -208
  27. package/dist/index.js.map +1 -1
  28. package/dist/mcp/mesh_server.d.ts +16 -0
  29. package/dist/mcp/mesh_server.js +207 -0
  30. package/dist/mcp/mesh_server.js.map +1 -0
  31. package/dist/protocol/types.d.ts +103 -0
  32. package/dist/session/bridge.d.ts +39 -0
  33. package/dist/session/bridge.js +41 -0
  34. package/dist/session/bridge.js.map +1 -0
  35. package/dist/session/mesh_node.d.ts +123 -0
  36. package/dist/session/mesh_node.js +203 -0
  37. package/dist/session/mesh_node.js.map +1 -0
  38. package/dist/session/setup_wizard.d.ts +6 -23
  39. package/dist/session/setup_wizard.js +6 -15
  40. package/dist/session/setup_wizard.js.map +1 -1
  41. package/dist/session/tools.d.ts +1 -1
  42. package/dist/transport/relay_client.d.ts +8 -0
  43. package/dist/transport/relay_client.js +50 -2
  44. package/dist/transport/relay_client.js.map +1 -1
  45. package/package.json +5 -3
  46. package/skills/claude-agent-network/SKILL.md +239 -0
@@ -8,6 +8,7 @@ export type ClientMessage = {
8
8
  type: "user_message";
9
9
  id: string;
10
10
  text: string;
11
+ images?: WireImage[];
11
12
  } | {
12
13
  type: "approve_tool";
13
14
  id: string;
@@ -24,7 +25,38 @@ export type ClientMessage = {
24
25
  type: "session_sync";
25
26
  id: string;
26
27
  limit?: number;
28
+ } | {
29
+ type: "session_new";
30
+ id: string;
31
+ } | {
32
+ type: "session_compact";
33
+ id: string;
34
+ } | {
35
+ type: "model_set";
36
+ id: string;
37
+ provider: string;
38
+ model_id: string;
39
+ } | {
40
+ type: "thinking_set";
41
+ id: string;
42
+ level: ThinkingLevel;
43
+ } | {
44
+ type: "list_models";
45
+ id: string;
27
46
  };
47
+ /**
48
+ * Plan/30 — one inline image attachment on a `user_message`. Mirrors the
49
+ * SDK's `ImageContent` ({@link https }) split across the wire: `data` is the
50
+ * base64-encoded (compressed) image bytes, `mime` its content type
51
+ * (e.g. `"image/jpeg"`). The Pi maps `{ data, mime }` → the SDK's
52
+ * `{ type:"image", data, mimeType }` before handing it to the model.
53
+ */
54
+ export interface WireImage {
55
+ /** Base64-encoded image bytes (compressed app-side). */
56
+ data: string;
57
+ /** MIME type, e.g. `"image/jpeg"`. Maps to the SDK's `mimeType`. */
58
+ mime: string;
59
+ }
28
60
  export type Usage = {
29
61
  input_tokens: number;
30
62
  output_tokens: number;
@@ -36,6 +68,7 @@ export type SessionHistoryEvent = {
36
68
  type: "user_input";
37
69
  id: string;
38
70
  text: string;
71
+ images?: WireImage[];
39
72
  } | {
40
73
  ts: number;
41
74
  type: "tool_request";
@@ -54,6 +87,11 @@ export type SessionHistoryEvent = {
54
87
  in_reply_to: string;
55
88
  text: string;
56
89
  usage?: Usage;
90
+ } | {
91
+ ts: number;
92
+ type: "compaction";
93
+ summary: string;
94
+ tokens_before: number;
57
95
  };
58
96
  export type ServerMessage = {
59
97
  type: "pair_ok";
@@ -93,6 +131,7 @@ export type ServerMessage = {
93
131
  type: "user_message";
94
132
  id: string;
95
133
  text: string;
134
+ images?: WireImage[];
96
135
  } | {
97
136
  type: "agent_chunk";
98
137
  in_reply_to: string;
@@ -106,6 +145,11 @@ export type ServerMessage = {
106
145
  in_reply_to: string;
107
146
  text: string;
108
147
  usage?: Usage;
148
+ } | {
149
+ type: "compaction";
150
+ summary: string;
151
+ tokens_before: number;
152
+ ts?: number;
109
153
  } | {
110
154
  type: "tool_request";
111
155
  tool_call_id: string;
@@ -138,5 +182,64 @@ export type ServerMessage = {
138
182
  events: SessionHistoryEvent[];
139
183
  eos: boolean;
140
184
  truncated: boolean;
185
+ } | {
186
+ type: "action_ok";
187
+ in_reply_to: string;
188
+ action: ActionName;
189
+ } | {
190
+ type: "action_error";
191
+ in_reply_to: string;
192
+ action: ActionName;
193
+ error: string;
194
+ } | {
195
+ type: "models_list";
196
+ in_reply_to: string;
197
+ models: WireModel[];
198
+ current?: WireModel;
141
199
  };
200
+ /**
201
+ * Plan/28 — Stable names for the typed actions the app can request. Kept
202
+ * as a closed string union so a switch in either side gets exhaustiveness
203
+ * checking from the compiler.
204
+ */
205
+ export type ActionName = "session_new" | "session_compact" | "model_set" | "thinking_set";
206
+ /**
207
+ * Plan/28 — Mirror of the SDK's `ThinkingLevel` (defined in
208
+ * `@earendil-works/pi-agent-core/types`). Re-declared locally so the wire
209
+ * protocol owns its own enum and we don't leak SDK-internal types onto
210
+ * the app's network surface.
211
+ *
212
+ * Note: `"xhigh"` is only honored by select model families — the SDK uses
213
+ * each `Model.thinkingLevelMap` to decide if the requested level is
214
+ * supported, falling back to a sensible neighbour when not. The app
215
+ * surfaces all 6 buttons but can grey out unsupported ones using the
216
+ * model's metadata if the picker fetches it later.
217
+ */
218
+ export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
219
+ /**
220
+ * Plan/28 — Wire shape for one model entry in the app's model picker.
221
+ *
222
+ * Subset of the SDK's `Model<Api>` interface — only the fields the app
223
+ * actually renders. Cost / max-tokens / API class are left off the wire
224
+ * deliberately; if the app's picker grows to need them, they get added
225
+ * here and to the handler's mapping in `index.ts` in one diff.
226
+ */
227
+ export interface WireModel {
228
+ /** Stable identifier inside the provider's catalog. E.g. `"claude-opus-4-7"`. */
229
+ id: string;
230
+ /** Display name for the picker row. E.g. `"Claude Opus 4.7"`. */
231
+ name: string;
232
+ /** Provider slug. E.g. `"anthropic"`, `"openai"`. */
233
+ provider: string;
234
+ /** Whether the model supports the thinking surface (`reasoning: true`
235
+ * in the SDK). Useful so the app can decide whether the thinking
236
+ * segmented control should be enabled when this model is selected. */
237
+ reasoning: boolean;
238
+ /** Context window in tokens, for display in the picker subtitle. */
239
+ context_window: number;
240
+ /** Plan/30: true when the model accepts image input (SDK `Model.input`
241
+ * includes `"image"`). The app uses it to enable/disable the attach
242
+ * button — a text-only model greys out image attachments. */
243
+ vision: boolean;
244
+ }
142
245
  export type ByeReason = "peer_stop" | "session_replaced" | "shutdown";
@@ -0,0 +1,39 @@
1
+ import type { Broker } from "./broker.js";
2
+ import { BrokerRemote } from "./broker_remote.js";
3
+ import { PiForwardClient } from "../transport/pi_forward_client.js";
4
+ import type { RelayClient } from "../transport/relay_client.js";
5
+ import type { Ed25519Keypair } from "../pairing/crypto.js";
6
+ /**
7
+ * Cross-PC mesh bridge composition — the single shared point for "turn a
8
+ * leader Broker + relay into a cross-PC router".
9
+ *
10
+ * Both consumers call this so the wiring lives in one place:
11
+ * - The Pi extension (`_ensureBrokerRemote`) injects its relay — the SAME
12
+ * RelayClient it also uses for app↔Pi pairing.
13
+ * - The MCP `MeshNode` creates its own RelayClient and passes it in.
14
+ *
15
+ * The function does NOT own the relay's lifecycle (the caller created it and
16
+ * tears it down). It only attaches the PiForwardClient + BrokerRemote on top.
17
+ *
18
+ * Sibling discovery is best-effort: on any failure we attach a BrokerRemote
19
+ * with an empty sibling set, and remote `peers_update` pushes fill the cache
20
+ * in later. Discovery warnings are routed to the silent `log` so they never
21
+ * bleed into a Pi TUI chat panel.
22
+ */
23
+ export interface AttachBridgeOptions {
24
+ /** The leader's local Broker (from SessionPeer.localBroker()). */
25
+ broker: Broker;
26
+ /** Live relay connection. Caller owns its lifecycle. */
27
+ relay: RelayClient;
28
+ /** Relay URL in http(s):// form — for MeshClient sibling discovery. */
29
+ relayUrl: string;
30
+ /** This host's Ed25519 identity (machine Pi-key). */
31
+ keypair: Ed25519Keypair;
32
+ /** Diagnostic logger. Defaults to a no-op (avoids TUI leaks). */
33
+ log?: (msg: string) => void;
34
+ }
35
+ export interface CrossPcBridge {
36
+ brokerRemote: BrokerRemote;
37
+ piForward: PiForwardClient;
38
+ }
39
+ export declare function attachCrossPcBridge(opts: AttachBridgeOptions): Promise<CrossPcBridge>;
@@ -0,0 +1,41 @@
1
+ import { BrokerRemote } from "./broker_remote.js";
2
+ import { PiForwardClient } from "../transport/pi_forward_client.js";
3
+ import { MeshClient } from "../mesh/client.js";
4
+ import { discoverSelfLabel, discoverSiblings, fallbackLabel } from "../mesh/siblings.js";
5
+ import { listOwnerPubkeys } from "../pairing/storage.js";
6
+ export async function attachCrossPcBridge(opts) {
7
+ const log = opts.log ?? (() => { });
8
+ const piForward = new PiForwardClient(opts.relay);
9
+ const selfPubkeyB64 = Buffer.from(opts.keypair.publicKey).toString("base64");
10
+ let selfPcLabel = fallbackLabel(selfPubkeyB64);
11
+ let siblings = [];
12
+ try {
13
+ const meshClient = new MeshClient(opts.relayUrl);
14
+ const owners = await listOwnerPubkeys();
15
+ if (owners.length > 0) {
16
+ // Silent log: per-Owner fetch failures (relay 404 before any Owner
17
+ // published, transient HTTP errors) must not surface in a TUI.
18
+ const silent = { warn: (_m) => { } };
19
+ const [labelRes, sibs] = await Promise.all([
20
+ discoverSelfLabel({ client: meshClient, ownerEpks: owners, myPubkey: opts.keypair.publicKey, log: silent }),
21
+ discoverSiblings({ client: meshClient, ownerEpks: owners, myPubkey: opts.keypair.publicKey, log: silent }),
22
+ ]);
23
+ selfPcLabel = labelRes.selfPcLabel;
24
+ siblings = sibs;
25
+ }
26
+ }
27
+ catch (err) {
28
+ // Best-effort — siblings populate later via remote peers_update push.
29
+ void err;
30
+ }
31
+ const brokerRemote = new BrokerRemote({
32
+ broker: opts.broker,
33
+ pi: piForward,
34
+ selfPcLabel,
35
+ selfPcPubkey: selfPubkeyB64,
36
+ siblings,
37
+ log,
38
+ });
39
+ return { brokerRemote, piForward };
40
+ }
41
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../src/session/bridge.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAuCzD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAyB;IACjE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAS,EAAE,GAAE,CAAC,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAElD,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC7E,IAAI,WAAW,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;IAC/C,IAAI,QAAQ,GAA4C,EAAE,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,MAAM,gBAAgB,EAAE,CAAC;QACxC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,mEAAmE;YACnE,+DAA+D;YAC/D,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,CAAC,EAAU,EAAQ,EAAE,GAAgB,CAAC,EAAE,CAAC;YAChE,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACzC,iBAAiB,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;gBAC3G,gBAAgB,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;aAC3G,CAAC,CAAC;YACH,WAAW,GAAG,QAAQ,CAAC,WAAW,CAAC;YACnC,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,sEAAsE;QACtE,KAAK,GAAG,CAAC;IACX,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,YAAY,CAAC;QACpC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,EAAE,EAAE,SAAS;QACb,WAAW;QACX,YAAY,EAAE,aAAa;QAC3B,QAAQ;QACR,GAAG;KACJ,CAAC,CAAC;IAEH,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;AACrC,CAAC"}
@@ -0,0 +1,123 @@
1
+ import { SessionPeer, type AckResult } from "./peer.js";
2
+ import type { Envelope } from "./envelope.js";
3
+ import type { Broker } from "./broker.js";
4
+ import { RelayClient } from "../transport/relay_client.js";
5
+ import type { Ed25519Keypair } from "./../pairing/crypto.js";
6
+ /**
7
+ * MeshNode — the single composition point for "join the agent mesh".
8
+ *
9
+ * Wraps the two layers every mesh participant needs, and nothing else
10
+ * (pairing is app↔Pi and stays OUT of here):
11
+ *
12
+ * 1. **Local UDS mesh** — always. A `SessionPeer` joins (or leads) the
13
+ * broker at `sockPath`: `send` / `sendWithAck` / `request` /
14
+ * `onMessage`, leader election + failover for free.
15
+ *
16
+ * 2. **Cross-PC relay bridge** — optional, only when the node leads (the
17
+ * leader hosts the Broker, so it owns the `BrokerRemote`). Two ways to
18
+ * supply the relay:
19
+ * - **Self-managed** (MCP): pass `bridge: { relayUrl, cwd }` and the
20
+ * node creates + owns the RelayClient on connect-if-leader, with
21
+ * its own machine Pi-key.
22
+ * - **Injected** (Pi extension): call `attachBridge({ relay, … })`
23
+ * with the RelayClient the host already owns (and also uses for
24
+ * app↔Pi pairing). MeshNode never closes an injected relay.
25
+ *
26
+ * Both the Pi extension and the MCP mesh server build on this so the mesh
27
+ * wiring lives in one place. A follower never brings the bridge up —
28
+ * cross-PC routing works transitively through whoever is leader (a Pi, the
29
+ * daemon, or another MeshNode). On UDS failover that promotes this node to
30
+ * leader, the bridge re-attaches automatically against the fresh broker.
31
+ */
32
+ /** Self-managed-relay bridge config (MCP path). */
33
+ export interface MeshSelfRelayBridge {
34
+ /** Relay URL in http(s):// form (converted to ws(s):// internally). */
35
+ relayUrl: string;
36
+ /** cwd — derives the relay room id and the room_meta. */
37
+ cwd: string;
38
+ /** Display name for room_meta. Defaults to the assigned mesh name. */
39
+ sessionName?: string;
40
+ }
41
+ export interface MeshNodeOptions {
42
+ /** UDS broker socket path (e.g. ~/.pi/remote/sessions/local/broker.sock). */
43
+ sockPath: string;
44
+ /** Requested mesh name (broker may add a #N collision suffix). */
45
+ name: string;
46
+ /** Optional audit log path passed through to SessionPeer. */
47
+ auditPath?: string;
48
+ /** Self-managed relay bridge — brought up if this node leads. */
49
+ bridge?: MeshSelfRelayBridge;
50
+ /** Diagnostic logger. Defaults to a no-op (avoids leaking into TUIs). */
51
+ log?: (msg: string) => void;
52
+ }
53
+ export type { AckResult } from "./peer.js";
54
+ interface SiblingInfo {
55
+ pcLabel: string;
56
+ pcPubkey: string;
57
+ }
58
+ export declare class MeshNode {
59
+ private readonly peer_;
60
+ private readonly log;
61
+ private relay;
62
+ private relayOwned;
63
+ private brokerRemote;
64
+ private piForward;
65
+ private keypair;
66
+ private bridgeParams;
67
+ private reconnectWired;
68
+ constructor(opts: MeshNodeOptions);
69
+ /** Join (or lead) the mesh. Resolves with the assigned name. */
70
+ connect(): Promise<string>;
71
+ /**
72
+ * Attach a cross-PC bridge on top of an EXTERNALLY-owned relay (Pi path).
73
+ * Idempotent; only attaches when this node is the leader. Remembers the
74
+ * params so the bridge re-attaches after a UDS failover. Call again with a
75
+ * fresh relay after a relay reconnect.
76
+ */
77
+ attachBridge(opts: {
78
+ relay: RelayClient;
79
+ relayUrl: string;
80
+ keypair?: Ed25519Keypair;
81
+ }): Promise<void>;
82
+ /**
83
+ * Tear down the bridge AND forget its params (no auto re-attach until the
84
+ * next `attachBridge`/`connect`). Closes the relay only if MeshNode created
85
+ * it — an injected relay belongs to the host. Use on stop / relay drop.
86
+ */
87
+ detachBridge(): void;
88
+ private _wireReconnect;
89
+ private _onReconnect;
90
+ private _maybeBridge;
91
+ private _detachBridgeKeepingParams;
92
+ /** Keep the cross-PC sibling set in sync (Pi SelfRevoke onMembersChanged). */
93
+ setSiblings(siblings: SiblingInfo[]): void;
94
+ /** Announce the local peer set to siblings (Pi broker peer_joined/left). */
95
+ onLocalPeersChanged(local: string[]): void;
96
+ /** True when the cross-PC relay bridge is active (this node is leader). */
97
+ hasBridge(): boolean;
98
+ /** The underlying SessionPeer — for consumers that need it directly (tools). */
99
+ peer(): SessionPeer;
100
+ /** Fire-and-forget send. `to` may be a name, `<pc>:<name>`, or "broadcast". */
101
+ send(to: string | string[], body: unknown, re?: string | null): Promise<void>;
102
+ /** Unicast send + await broker ACK (received/busy/denied/timeout). */
103
+ sendWithAck(to: string, body: unknown, re?: string | null, timeoutMs?: number): Promise<AckResult>;
104
+ /** Send + await the first reply whose `re` matches the outbound id. */
105
+ request(to: string, body: unknown, timeoutMs?: number): Promise<Envelope>;
106
+ /** Subscribe to inbound envelopes. Returns an unsubscribe fn. */
107
+ onMessage(handler: (env: Envelope) => void): () => void;
108
+ /** Subscribe to post-failover reconnects. Returns an unsubscribe fn. */
109
+ onReconnect(handler: () => void): () => void;
110
+ /** Assigned mesh name (after any #N collision suffix). */
111
+ name(): string;
112
+ /** "leader" | "follower". */
113
+ currentRole(): "leader" | "follower";
114
+ /** The locally-hosted Broker when leader, else null. */
115
+ localBroker(): Broker | null;
116
+ /**
117
+ * Aggregated mesh roster (local UDS peers + cross-PC `<pc>:<peer>`),
118
+ * excluding self. Asks the broker, which merges its remote router cache.
119
+ */
120
+ listPeers(timeoutMs?: number): Promise<string[]>;
121
+ /** Tear down the bridge (if any) and leave the mesh. */
122
+ close(): Promise<void>;
123
+ }
@@ -0,0 +1,203 @@
1
+ import { SessionPeer } from "./peer.js";
2
+ import { RelayClient } from "../transport/relay_client.js";
3
+ import { attachCrossPcBridge } from "./bridge.js";
4
+ import { getOrCreateEd25519Keypair } from "../pairing/storage.js";
5
+ import { roomIdForCwd } from "../rooms.js";
6
+ import { toWebSocketUrl } from "../config.js";
7
+ export class MeshNode {
8
+ peer_;
9
+ log;
10
+ relay = null;
11
+ relayOwned = false;
12
+ brokerRemote = null;
13
+ piForward = null;
14
+ keypair = null;
15
+ bridgeParams = null;
16
+ reconnectWired = false;
17
+ constructor(opts) {
18
+ this.log = opts.log ?? (() => { });
19
+ const peerOpts = { sockPath: opts.sockPath, name: opts.name };
20
+ if (opts.auditPath !== undefined)
21
+ peerOpts.auditPath = opts.auditPath;
22
+ this.peer_ = new SessionPeer(peerOpts);
23
+ if (opts.bridge) {
24
+ const p = { relayUrl: opts.bridge.relayUrl, cwd: opts.bridge.cwd };
25
+ if (opts.bridge.sessionName !== undefined)
26
+ p.sessionName = opts.bridge.sessionName;
27
+ this.bridgeParams = p;
28
+ }
29
+ }
30
+ /** Join (or lead) the mesh. Resolves with the assigned name. */
31
+ async connect() {
32
+ const name = await this.peer_.start();
33
+ this._wireReconnect();
34
+ if (this.bridgeParams)
35
+ await this._maybeBridge();
36
+ return name;
37
+ }
38
+ /**
39
+ * Attach a cross-PC bridge on top of an EXTERNALLY-owned relay (Pi path).
40
+ * Idempotent; only attaches when this node is the leader. Remembers the
41
+ * params so the bridge re-attaches after a UDS failover. Call again with a
42
+ * fresh relay after a relay reconnect.
43
+ */
44
+ async attachBridge(opts) {
45
+ const p = { relayUrl: opts.relayUrl, injectedRelay: opts.relay };
46
+ if (opts.keypair !== undefined)
47
+ p.keypair = opts.keypair;
48
+ this.bridgeParams = p;
49
+ this._wireReconnect();
50
+ await this._maybeBridge();
51
+ }
52
+ /**
53
+ * Tear down the bridge AND forget its params (no auto re-attach until the
54
+ * next `attachBridge`/`connect`). Closes the relay only if MeshNode created
55
+ * it — an injected relay belongs to the host. Use on stop / relay drop.
56
+ */
57
+ detachBridge() {
58
+ this._detachBridgeKeepingParams();
59
+ this.bridgeParams = null;
60
+ }
61
+ // ── Bridge internals ────────────────────────────────────────────────────────
62
+ _wireReconnect() {
63
+ if (this.reconnectWired)
64
+ return;
65
+ this.reconnectWired = true;
66
+ // SessionPeer.onReconnect fires only after a UDS re-election (failover),
67
+ // not on relay events. On failover the broker reference changes, so drop
68
+ // the stale bridge and re-attach against the fresh localBroker().
69
+ this.peer_.onReconnect(() => { void this._onReconnect(); });
70
+ }
71
+ async _onReconnect() {
72
+ if (!this.bridgeParams)
73
+ return;
74
+ this._detachBridgeKeepingParams();
75
+ await this._maybeBridge();
76
+ }
77
+ async _maybeBridge() {
78
+ if (this.brokerRemote)
79
+ return;
80
+ if (this.peer_.currentRole() !== "leader")
81
+ return;
82
+ const broker = this.peer_.localBroker();
83
+ if (!broker)
84
+ return;
85
+ const params = this.bridgeParams;
86
+ if (!params)
87
+ return;
88
+ let relay;
89
+ if (params.injectedRelay) {
90
+ relay = params.injectedRelay;
91
+ this.relayOwned = false;
92
+ }
93
+ else {
94
+ if (!this.keypair)
95
+ this.keypair = params.keypair ?? (await getOrCreateEd25519Keypair());
96
+ const roomId = roomIdForCwd(params.cwd);
97
+ const roomMeta = { name: params.sessionName ?? this.peer_.name(), cwd: params.cwd };
98
+ const r = new RelayClient(toWebSocketUrl(params.relayUrl), this.keypair);
99
+ try {
100
+ await r.connect({ roomId, roomMeta });
101
+ }
102
+ catch (err) {
103
+ // UDS mesh still works; cross-PC stays unavailable until a leader
104
+ // with a healthy relay appears.
105
+ this.log(`mesh bridge: relay connect failed: ${String(err)}`);
106
+ return;
107
+ }
108
+ relay = r;
109
+ this.relayOwned = true;
110
+ }
111
+ this.relay = relay;
112
+ if (!this.keypair)
113
+ this.keypair = params.keypair ?? (await getOrCreateEd25519Keypair());
114
+ const { brokerRemote, piForward } = await attachCrossPcBridge({
115
+ broker,
116
+ relay,
117
+ relayUrl: params.relayUrl,
118
+ keypair: this.keypair,
119
+ log: this.log,
120
+ });
121
+ this.brokerRemote = brokerRemote;
122
+ this.piForward = piForward;
123
+ }
124
+ _detachBridgeKeepingParams() {
125
+ this.brokerRemote?.detach();
126
+ this.brokerRemote = null;
127
+ this.piForward?.detach();
128
+ this.piForward = null;
129
+ if (this.relayOwned)
130
+ this.relay?.close();
131
+ this.relay = null;
132
+ this.relayOwned = false;
133
+ }
134
+ // ── Bridge passthroughs (no-op when no bridge / follower) ───────────────────
135
+ /** Keep the cross-PC sibling set in sync (Pi SelfRevoke onMembersChanged). */
136
+ setSiblings(siblings) {
137
+ this.brokerRemote?.setSiblings(siblings);
138
+ }
139
+ /** Announce the local peer set to siblings (Pi broker peer_joined/left). */
140
+ onLocalPeersChanged(local) {
141
+ this.brokerRemote?.onLocalPeersChanged(local);
142
+ }
143
+ /** True when the cross-PC relay bridge is active (this node is leader). */
144
+ hasBridge() {
145
+ return this.brokerRemote !== null;
146
+ }
147
+ // ── Mesh API (delegates to SessionPeer) ─────────────────────────────────────
148
+ /** The underlying SessionPeer — for consumers that need it directly (tools). */
149
+ peer() {
150
+ return this.peer_;
151
+ }
152
+ /** Fire-and-forget send. `to` may be a name, `<pc>:<name>`, or "broadcast". */
153
+ async send(to, body, re = null) {
154
+ return this.peer_.send(to, body, re);
155
+ }
156
+ /** Unicast send + await broker ACK (received/busy/denied/timeout). */
157
+ async sendWithAck(to, body, re = null, timeoutMs) {
158
+ return timeoutMs === undefined
159
+ ? this.peer_.sendWithAck(to, body, re)
160
+ : this.peer_.sendWithAck(to, body, re, timeoutMs);
161
+ }
162
+ /** Send + await the first reply whose `re` matches the outbound id. */
163
+ async request(to, body, timeoutMs) {
164
+ return timeoutMs === undefined
165
+ ? this.peer_.request(to, body)
166
+ : this.peer_.request(to, body, timeoutMs);
167
+ }
168
+ /** Subscribe to inbound envelopes. Returns an unsubscribe fn. */
169
+ onMessage(handler) {
170
+ return this.peer_.onMessage(handler);
171
+ }
172
+ /** Subscribe to post-failover reconnects. Returns an unsubscribe fn. */
173
+ onReconnect(handler) {
174
+ return this.peer_.onReconnect(handler);
175
+ }
176
+ /** Assigned mesh name (after any #N collision suffix). */
177
+ name() {
178
+ return this.peer_.name();
179
+ }
180
+ /** "leader" | "follower". */
181
+ currentRole() {
182
+ return this.peer_.currentRole();
183
+ }
184
+ /** The locally-hosted Broker when leader, else null. */
185
+ localBroker() {
186
+ return this.peer_.localBroker();
187
+ }
188
+ /**
189
+ * Aggregated mesh roster (local UDS peers + cross-PC `<pc>:<peer>`),
190
+ * excluding self. Asks the broker, which merges its remote router cache.
191
+ */
192
+ async listPeers(timeoutMs = 2_000) {
193
+ const reply = await this.peer_.request("broker", { type: "list_peers" }, timeoutMs);
194
+ const body = reply.body;
195
+ return (body?.peers ?? []).filter((p) => p !== this.peer_.name());
196
+ }
197
+ /** Tear down the bridge (if any) and leave the mesh. */
198
+ async close() {
199
+ this.detachBridge();
200
+ await this.peer_.leave();
201
+ }
202
+ }
203
+ //# sourceMappingURL=mesh_node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mesh_node.js","sourceRoot":"","sources":["../../src/session/mesh_node.ts"],"names":[],"mappings":"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"}
@@ -17,40 +17,23 @@ export interface WizardUI {
17
17
  export interface WizardDefaults {
18
18
  agent_name: string;
19
19
  use_relay: boolean;
20
- /**
21
- * Default for the "Enable daemon mode?" prompt. Doesn't persist in
22
- * `LocalConfig` — daemon enablement is OS-level state (service unit on
23
- * disk), not config. The wizard surfaces it so first-time users get
24
- * the option without having to discover `/remote-pi install` later.
25
- */
26
- enable_daemon?: boolean;
27
20
  }
28
21
  /**
29
- * Result of `runSetupWizard`. Extends `LocalConfig` with an out-of-band
30
- * `enable_daemon` flag the caller acts on (by invoking the install
31
- * command) — NOT a persisted config field.
32
- */
33
- export interface WizardResult extends LocalConfig {
34
- enable_daemon: boolean;
35
- }
36
- /**
37
- * Runs the 3-question setup wizard. Returns the chosen config + a
38
- * `enable_daemon` flag on confirm, or null when the user cancels any
39
- * prompt.
22
+ * Runs the 2-question setup wizard. Returns the chosen config on confirm, or
23
+ * null when the user cancels any prompt.
40
24
  *
41
25
  * Prompts:
42
26
  * 1. Agent name (default: parent/folder of cwd)
43
27
  * 2. Use the relay on this terminal? (yes/no) — gates connection to the
44
28
  * remote mesh (mobile devices + other PCs over the relay). "No" means
45
29
  * local-only: this Pi joins the UDS mesh but doesn't open WSS.
46
- * 3. Enable daemon mode? (yes/no) — installs the system service so
47
- * agents you `/remote-pi create` keep running 24/7 in the background.
48
- * Symlinks the `remote-pi` + `pi-supervisord` CLIs into
49
- * `~/.local/bin/` so the shell can address daemons by id.
50
30
  * Final: review + confirm "Save and activate?" yes/no
51
31
  *
32
+ * Daemon mode (run agents 24/7 via systemd/launchd) is intentionally NOT in
33
+ * the wizard — it's an explicit, separate opt-in via `/remote-pi install`.
34
+ *
52
35
  * The local UDS mesh is always single per machine ("local" session) — no
53
36
  * session question. All Pis on the same machine see each other through
54
37
  * the same broker.
55
38
  */
56
- export declare function runSetupWizard(ui: WizardUI, defaults: WizardDefaults): Promise<WizardResult | null>;
39
+ export declare function runSetupWizard(ui: WizardUI, defaults: WizardDefaults): Promise<LocalConfig | null>;
@@ -2,21 +2,19 @@ const YES = "Yes";
2
2
  const NO = "No";
3
3
  const CANCEL_TOKEN = "__cancel__";
4
4
  /**
5
- * Runs the 3-question setup wizard. Returns the chosen config + a
6
- * `enable_daemon` flag on confirm, or null when the user cancels any
7
- * prompt.
5
+ * Runs the 2-question setup wizard. Returns the chosen config on confirm, or
6
+ * null when the user cancels any prompt.
8
7
  *
9
8
  * Prompts:
10
9
  * 1. Agent name (default: parent/folder of cwd)
11
10
  * 2. Use the relay on this terminal? (yes/no) — gates connection to the
12
11
  * remote mesh (mobile devices + other PCs over the relay). "No" means
13
12
  * local-only: this Pi joins the UDS mesh but doesn't open WSS.
14
- * 3. Enable daemon mode? (yes/no) — installs the system service so
15
- * agents you `/remote-pi create` keep running 24/7 in the background.
16
- * Symlinks the `remote-pi` + `pi-supervisord` CLIs into
17
- * `~/.local/bin/` so the shell can address daemons by id.
18
13
  * Final: review + confirm "Save and activate?" yes/no
19
14
  *
15
+ * Daemon mode (run agents 24/7 via systemd/launchd) is intentionally NOT in
16
+ * the wizard — it's an explicit, separate opt-in via `/remote-pi install`.
17
+ *
20
18
  * The local UDS mesh is always single per machine ("local" session) — no
21
19
  * session question. All Pis on the same machine see each other through
22
20
  * the same broker.
@@ -30,23 +28,16 @@ export async function runSetupWizard(ui, defaults) {
30
28
  if (!useRelayChoice)
31
29
  return null;
32
30
  const auto_start_relay = useRelayChoice === YES;
33
- ui.notify?.("Daemon mode runs Pi agents 24/7 in the background (systemd on Linux, launchd on macOS) so they can answer your phone or sibling PCs while your terminal is closed. Also adds `remote-pi` and `pi-supervisord` to your $PATH so you can drive them from any shell.", "info");
34
- const enableDaemonDefault = defaults.enable_daemon ?? false;
35
- const enableDaemonChoice = await ui.select("Enable daemon mode? (run agents 24/7 in background)", enableDaemonDefault ? [YES, NO] : [NO, YES]);
36
- if (!enableDaemonChoice)
37
- return null;
38
- const enable_daemon = enableDaemonChoice === YES;
39
31
  // Review + confirm
40
32
  const summary = [
41
33
  ` Agent name: ${agent_name}`,
42
34
  ` Use relay: ${auto_start_relay ? YES : NO}`,
43
- ` Daemon mode: ${enable_daemon ? YES : NO}`,
44
35
  ].join("\n");
45
36
  ui.notify?.(`Summary:\n${summary}`, "info");
46
37
  const confirm = await ui.select("Save and activate?", [YES, NO]);
47
38
  if (confirm !== YES)
48
39
  return null;
49
- return { agent_name, auto_start_relay, enable_daemon };
40
+ return { agent_name, auto_start_relay };
50
41
  }
51
42
  /**
52
43
  * Asks the user for free text. The Pi SDK's `ui.input` does not pre-fill the