@gtc6244/immediacy-daemon 0.2.2 → 0.2.4

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 (57) hide show
  1. package/dist/src/crypto.d.ts +18 -39
  2. package/dist/src/crypto.js +33 -152
  3. package/dist/src/crypto.js.map +1 -1
  4. package/dist/src/devices.d.ts +46 -0
  5. package/dist/src/devices.js +114 -0
  6. package/dist/src/devices.js.map +1 -0
  7. package/dist/src/envelope.d.ts +3 -3
  8. package/dist/src/envelope.js +3 -3
  9. package/dist/src/forward.d.ts +4 -4
  10. package/dist/src/forward.js +1 -1
  11. package/dist/src/forward.js.map +1 -1
  12. package/dist/src/framing.d.ts +76 -0
  13. package/dist/src/framing.js +133 -0
  14. package/dist/src/framing.js.map +1 -0
  15. package/dist/src/hub.d.ts +21 -9
  16. package/dist/src/hub.js +6 -5
  17. package/dist/src/hub.js.map +1 -1
  18. package/dist/src/index.d.ts +3 -3
  19. package/dist/src/index.js +79 -17
  20. package/dist/src/index.js.map +1 -1
  21. package/dist/src/lan.d.ts +103 -0
  22. package/dist/src/lan.js +182 -0
  23. package/dist/src/lan.js.map +1 -0
  24. package/dist/src/pairing.d.ts +69 -0
  25. package/dist/src/pairing.js +125 -0
  26. package/dist/src/pairing.js.map +1 -0
  27. package/dist/src/protocol.d.ts +75 -21
  28. package/dist/src/relay-transport.d.ts +62 -0
  29. package/dist/src/relay-transport.js +180 -0
  30. package/dist/src/relay-transport.js.map +1 -0
  31. package/dist/src/relay-wire.d.ts +25 -0
  32. package/dist/src/relay-wire.js +37 -0
  33. package/dist/src/relay-wire.js.map +1 -0
  34. package/dist/src/rendezvous.d.ts +57 -0
  35. package/dist/src/rendezvous.js +152 -0
  36. package/dist/src/rendezvous.js.map +1 -0
  37. package/dist/src/server.d.ts +19 -7
  38. package/dist/src/server.js +181 -61
  39. package/dist/src/server.js.map +1 -1
  40. package/dist/src/session-socket.d.ts +34 -0
  41. package/dist/src/session-socket.js +84 -0
  42. package/dist/src/session-socket.js.map +1 -0
  43. package/dist/src/session.d.ts +151 -0
  44. package/dist/src/session.js +241 -0
  45. package/dist/src/session.js.map +1 -0
  46. package/dist/src/tcp-transport.d.ts +37 -0
  47. package/dist/src/tcp-transport.js +106 -0
  48. package/dist/src/tcp-transport.js.map +1 -0
  49. package/dist/src/telemetry.d.ts +69 -0
  50. package/dist/src/telemetry.js +64 -0
  51. package/dist/src/telemetry.js.map +1 -0
  52. package/dist/src/transport.d.ts +75 -0
  53. package/dist/src/transport.js +136 -0
  54. package/dist/src/transport.js.map +1 -0
  55. package/dist/src/workspace.d.ts +4 -4
  56. package/dist/src/workspace.js.map +1 -1
  57. package/package.json +16 -4
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Frame codec (docs/WEBRTC_MIGRATION.md §5.4 / §3.7, Phase 2).
3
+ *
4
+ * A transport-independent binary framing layer that sits UNDER the session
5
+ * sealer and OVER any byte transport (loopback, LAN TCP, DO relay, WebRTC data
6
+ * channel). One codec serves them all, so a payload chunked here is reassembled
7
+ * identically everywhere.
8
+ *
9
+ * ┌───────────┬──────┬───────┬────────────────┐
10
+ * │ channelId │ seq │ flags │ payload │
11
+ * │ u16 (BE) │ u32 │ u8 │ bytes │
12
+ * └───────────┴──────┴───────┴────────────────┘
13
+ *
14
+ * Channels multiplex independent logical streams over one transport:
15
+ * - control (ordered/reliable)
16
+ * - rpc (ordered/reliable)
17
+ * - terminal (coalesced firehose)
18
+ * - events (ordered/reliable)
19
+ *
20
+ * Chunking lives HERE (not at call sites): any message larger than
21
+ * MAX_CHUNK_BYTES is split across frames with the MORE flag set on every frame
22
+ * but the last, and reassembled on receipt. WebRTC (SCTP ~256 KB practical cap)
23
+ * therefore inherits large-message support for free. The codec is mirrored in
24
+ * the Flutter and macOS clients; parity is pinned by a shared known-answer vector
25
+ * (scripts/framing-vector.ts).
26
+ */
27
+ /** Channel ids — stable wire constants. */
28
+ export declare const Channel: {
29
+ readonly control: 0;
30
+ readonly rpc: 1;
31
+ readonly terminal: 2;
32
+ readonly events: 3;
33
+ };
34
+ export type ChannelId = (typeof Channel)[keyof typeof Channel];
35
+ /** Frame flag bits. */
36
+ export declare const Flag: {
37
+ /** More chunks of this logical message follow on the same channel. */
38
+ readonly MORE: 1;
39
+ };
40
+ /** Fixed header size: u16 channelId + u32 seq + u8 flags. */
41
+ export declare const HEADER_BYTES = 7;
42
+ /** Max payload bytes per frame. Anything larger is chunked. */
43
+ export declare const MAX_CHUNK_BYTES: number;
44
+ /** Upper bound on a reassembled message (guards an unbounded-memory peer). */
45
+ export declare const MAX_MESSAGE_BYTES: number;
46
+ export interface Frame {
47
+ channelId: number;
48
+ seq: number;
49
+ flags: number;
50
+ payload: Buffer;
51
+ }
52
+ /** Encode one frame to bytes (header || payload). */
53
+ export declare function encodeFrame(frame: Frame): Buffer;
54
+ /** Decode one frame from bytes. Throws on a short/oversized buffer. */
55
+ export declare function decodeFrame(buf: Buffer): Frame;
56
+ /**
57
+ * Split a logical message into ordered frames on `channelId`, starting at `seq`.
58
+ * Each frame but the last carries the MORE flag. An empty message yields a
59
+ * single empty frame (so zero-length messages still round-trip).
60
+ */
61
+ export declare function splitMessage(channelId: number, seq: number, message: Buffer, maxChunk?: number): Frame[];
62
+ /**
63
+ * Reassembles chunked messages per channel. Feed decoded frames in receipt
64
+ * order; `push` returns the complete message when a frame without the MORE flag
65
+ * arrives, or null while more chunks are pending. Ordered delivery per channel
66
+ * is assumed (true for every transport we run this over).
67
+ */
68
+ export declare class Reassembler {
69
+ private readonly buffers;
70
+ private readonly sizes;
71
+ /** Feed one frame; returns `{channelId, message}` when complete, else null. */
72
+ push(frame: Frame): {
73
+ channelId: number;
74
+ message: Buffer;
75
+ } | null;
76
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Frame codec (docs/WEBRTC_MIGRATION.md §5.4 / §3.7, Phase 2).
3
+ *
4
+ * A transport-independent binary framing layer that sits UNDER the session
5
+ * sealer and OVER any byte transport (loopback, LAN TCP, DO relay, WebRTC data
6
+ * channel). One codec serves them all, so a payload chunked here is reassembled
7
+ * identically everywhere.
8
+ *
9
+ * ┌───────────┬──────┬───────┬────────────────┐
10
+ * │ channelId │ seq │ flags │ payload │
11
+ * │ u16 (BE) │ u32 │ u8 │ bytes │
12
+ * └───────────┴──────┴───────┴────────────────┘
13
+ *
14
+ * Channels multiplex independent logical streams over one transport:
15
+ * - control (ordered/reliable)
16
+ * - rpc (ordered/reliable)
17
+ * - terminal (coalesced firehose)
18
+ * - events (ordered/reliable)
19
+ *
20
+ * Chunking lives HERE (not at call sites): any message larger than
21
+ * MAX_CHUNK_BYTES is split across frames with the MORE flag set on every frame
22
+ * but the last, and reassembled on receipt. WebRTC (SCTP ~256 KB practical cap)
23
+ * therefore inherits large-message support for free. The codec is mirrored in
24
+ * the Flutter and macOS clients; parity is pinned by a shared known-answer vector
25
+ * (scripts/framing-vector.ts).
26
+ */
27
+ /** Channel ids — stable wire constants. */
28
+ export const Channel = {
29
+ control: 0,
30
+ rpc: 1,
31
+ terminal: 2,
32
+ events: 3,
33
+ };
34
+ /** Frame flag bits. */
35
+ export const Flag = {
36
+ /** More chunks of this logical message follow on the same channel. */
37
+ MORE: 0x01,
38
+ };
39
+ /** Fixed header size: u16 channelId + u32 seq + u8 flags. */
40
+ export const HEADER_BYTES = 7;
41
+ /** Max payload bytes per frame. Anything larger is chunked. */
42
+ export const MAX_CHUNK_BYTES = 64 * 1024;
43
+ /** Upper bound on a reassembled message (guards an unbounded-memory peer). */
44
+ export const MAX_MESSAGE_BYTES = 32 * 1024 * 1024;
45
+ /** Encode one frame to bytes (header || payload). */
46
+ export function encodeFrame(frame) {
47
+ const out = Buffer.allocUnsafe(HEADER_BYTES + frame.payload.length);
48
+ out.writeUInt16BE(frame.channelId & 0xffff, 0);
49
+ out.writeUInt32BE(frame.seq >>> 0, 2);
50
+ out.writeUInt8(frame.flags & 0xff, 6);
51
+ frame.payload.copy(out, HEADER_BYTES);
52
+ return out;
53
+ }
54
+ /** Decode one frame from bytes. Throws on a short/oversized buffer. */
55
+ export function decodeFrame(buf) {
56
+ if (buf.length < HEADER_BYTES) {
57
+ throw new RangeError("frame shorter than the header");
58
+ }
59
+ const payload = buf.subarray(HEADER_BYTES);
60
+ if (payload.length > MAX_CHUNK_BYTES) {
61
+ throw new RangeError("frame payload exceeds MAX_CHUNK_BYTES");
62
+ }
63
+ return {
64
+ channelId: buf.readUInt16BE(0),
65
+ seq: buf.readUInt32BE(2),
66
+ flags: buf.readUInt8(6),
67
+ payload,
68
+ };
69
+ }
70
+ /**
71
+ * Split a logical message into ordered frames on `channelId`, starting at `seq`.
72
+ * Each frame but the last carries the MORE flag. An empty message yields a
73
+ * single empty frame (so zero-length messages still round-trip).
74
+ */
75
+ export function splitMessage(channelId, seq, message, maxChunk = MAX_CHUNK_BYTES) {
76
+ const frames = [];
77
+ let offset = 0;
78
+ let s = seq >>> 0;
79
+ do {
80
+ const end = Math.min(offset + maxChunk, message.length);
81
+ const isLast = end >= message.length;
82
+ frames.push({
83
+ channelId,
84
+ seq: s,
85
+ flags: isLast ? 0 : Flag.MORE,
86
+ payload: message.subarray(offset, end),
87
+ });
88
+ offset = end;
89
+ s = (s + 1) >>> 0;
90
+ } while (offset < message.length);
91
+ return frames;
92
+ }
93
+ /**
94
+ * Reassembles chunked messages per channel. Feed decoded frames in receipt
95
+ * order; `push` returns the complete message when a frame without the MORE flag
96
+ * arrives, or null while more chunks are pending. Ordered delivery per channel
97
+ * is assumed (true for every transport we run this over).
98
+ */
99
+ export class Reassembler {
100
+ buffers = new Map();
101
+ sizes = new Map();
102
+ /** Feed one frame; returns `{channelId, message}` when complete, else null. */
103
+ push(frame) {
104
+ const prior = this.buffers.get(frame.channelId);
105
+ const size = (this.sizes.get(frame.channelId) ?? 0) + frame.payload.length;
106
+ if (size > MAX_MESSAGE_BYTES) {
107
+ this.buffers.delete(frame.channelId);
108
+ this.sizes.delete(frame.channelId);
109
+ throw new RangeError("reassembled message exceeds MAX_MESSAGE_BYTES");
110
+ }
111
+ if (frame.flags & Flag.MORE) {
112
+ // Accumulate; more to come.
113
+ const chunks = prior ?? [];
114
+ chunks.push(Buffer.from(frame.payload));
115
+ this.buffers.set(frame.channelId, chunks);
116
+ this.sizes.set(frame.channelId, size);
117
+ return null;
118
+ }
119
+ // Final chunk: assemble and reset this channel.
120
+ let message;
121
+ if (prior && prior.length > 0) {
122
+ prior.push(Buffer.from(frame.payload));
123
+ message = Buffer.concat(prior);
124
+ }
125
+ else {
126
+ message = Buffer.from(frame.payload);
127
+ }
128
+ this.buffers.delete(frame.channelId);
129
+ this.sizes.delete(frame.channelId);
130
+ return { channelId: frame.channelId, message };
131
+ }
132
+ }
133
+ //# sourceMappingURL=framing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framing.js","sourceRoot":"","sources":["../../src/framing.ts"],"names":[],"mappings":"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"}
package/dist/src/hub.d.ts CHANGED
@@ -8,9 +8,21 @@
8
8
  * (cleartext until it completes the `hello` handshake, then a NaCl box sealed to
9
9
  * that device's key). Sends and broadcasts therefore encode per-socket.
10
10
  */
11
- import { WebSocket } from "ws";
12
11
  import type { Envelope, MessageType, PayloadFor } from "./protocol.js";
13
12
  import { type Codec } from "./crypto.js";
13
+ /**
14
+ * The minimal socket surface the hub/dispatch need — satisfied by both a `ws`
15
+ * WebSocket (the live WS path) and a `SessionSocket` (a LAN/relay session
16
+ * adapter, see session-socket.ts). Keeping the dispatch on this structural type
17
+ * lets one `handleFrame` serve every transport.
18
+ */
19
+ export interface SocketLike {
20
+ /** WebSocket.readyState; OPEN === 1. */
21
+ readonly readyState: number;
22
+ /** Bytes queued but not yet flushed (for forward backpressure). */
23
+ readonly bufferedAmount: number;
24
+ send(data: string, cb?: (err?: Error) => void): void;
25
+ }
14
26
  export declare class Hub {
15
27
  private requireSealed;
16
28
  /** Each attached socket and the codec its frames are (de)coded with. */
@@ -31,10 +43,10 @@ export declare class Hub {
31
43
  constructor(requireSealed?: boolean);
32
44
  /** A socket whose channel is sealed to its device key (post-`hello`). */
33
45
  private sealed;
34
- add(socket: WebSocket): void;
35
- remove(socket: WebSocket): void;
46
+ add(socket: SocketLike): void;
47
+ remove(socket: SocketLike): void;
36
48
  /** Follow the live daemon log on this socket (idempotent). */
37
- subscribeLog(socket: WebSocket): void;
49
+ subscribeLog(socket: SocketLike): void;
38
50
  /** Push a daemon→client message to just the log subscribers. */
39
51
  broadcastLog<T extends MessageType>(type: T, ws: string | null, payload: PayloadFor<T>): void;
40
52
  /**
@@ -46,11 +58,11 @@ export declare class Hub {
46
58
  */
47
59
  hasLiveClients(): boolean;
48
60
  /** The codec bound to a socket (cleartext if unknown/unpaired). */
49
- codecFor(socket: WebSocket): Codec;
61
+ codecFor(socket: SocketLike): Codec;
50
62
  /** Bind a socket to a codec (called once the device's key is known). */
51
- setCodec(socket: WebSocket, codec: Codec): void;
63
+ setCodec(socket: SocketLike, codec: Codec): void;
52
64
  /** Send to one socket, sealed with that socket's own codec. */
53
- send(socket: WebSocket, env: Envelope): void;
65
+ send(socket: SocketLike, env: Envelope): void;
54
66
  /**
55
67
  * Like `send`, but invoke `after` once the frame has been handed to the OS
56
68
  * socket buffer (or immediately if the socket is already closed). Lets a
@@ -61,9 +73,9 @@ export declare class Hub {
61
73
  * the side effect when the frame never made it to the socket. A closed socket
62
74
  * reports a synthetic error rather than pretending the frame was delivered.
63
75
  */
64
- sendThen(socket: WebSocket, env: Envelope, after: (err?: Error) => void): void;
76
+ sendThen(socket: SocketLike, env: Envelope, after: (err?: Error) => void): void;
65
77
  /** Build + send a daemon→client message to ONE socket (per-device streams). */
66
- sendTo<T extends MessageType>(socket: WebSocket, type: T, ws: string | null, payload: PayloadFor<T>): void;
78
+ sendTo<T extends MessageType>(socket: SocketLike, type: T, ws: string | null, payload: PayloadFor<T>): void;
67
79
  /** Build + broadcast a daemon→client message to every attached socket. */
68
80
  broadcast<T extends MessageType>(type: T, ws: string | null, payload: PayloadFor<T>): void;
69
81
  }
package/dist/src/hub.js CHANGED
@@ -8,9 +8,10 @@
8
8
  * (cleartext until it completes the `hello` handshake, then a NaCl box sealed to
9
9
  * that device's key). Sends and broadcasts therefore encode per-socket.
10
10
  */
11
- import { WebSocket } from "ws";
12
11
  import { make } from "./envelope.js";
13
12
  import { clearCodec } from "./crypto.js";
13
+ /** WebSocket.OPEN — avoids importing `ws` just for the constant. */
14
+ const OPEN = 1;
14
15
  export class Hub {
15
16
  requireSealed;
16
17
  /** Each attached socket and the codec its frames are (de)coded with. */
@@ -56,7 +57,7 @@ export class Hub {
56
57
  const env = make(type, ws, payload);
57
58
  for (const socket of this.logSubscribers) {
58
59
  const codec = this.conns.get(socket);
59
- if (codec && socket.readyState === WebSocket.OPEN && this.sealed(socket)) {
60
+ if (codec && socket.readyState === OPEN && this.sealed(socket)) {
60
61
  socket.send(codec.encode(env));
61
62
  }
62
63
  }
@@ -86,7 +87,7 @@ export class Hub {
86
87
  }
87
88
  /** Send to one socket, sealed with that socket's own codec. */
88
89
  send(socket, env) {
89
- if (socket.readyState === WebSocket.OPEN)
90
+ if (socket.readyState === OPEN)
90
91
  socket.send(this.codecFor(socket).encode(env));
91
92
  }
92
93
  /**
@@ -100,7 +101,7 @@ export class Hub {
100
101
  * reports a synthetic error rather than pretending the frame was delivered.
101
102
  */
102
103
  sendThen(socket, env, after) {
103
- if (socket.readyState === WebSocket.OPEN) {
104
+ if (socket.readyState === OPEN) {
104
105
  socket.send(this.codecFor(socket).encode(env), (err) => after(err ?? undefined));
105
106
  }
106
107
  else {
@@ -115,7 +116,7 @@ export class Hub {
115
116
  broadcast(type, ws, payload) {
116
117
  const env = make(type, ws, payload);
117
118
  for (const [socket, codec] of this.conns) {
118
- if (socket.readyState === WebSocket.OPEN && this.sealed(socket)) {
119
+ if (socket.readyState === OPEN && this.sealed(socket)) {
119
120
  socket.send(codec.encode(env));
120
121
  }
121
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"hub.js","sourceRoot":"","sources":["../../src/hub.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hub.js","sourceRoot":"","sources":["../../src/hub.ts"],"names":[],"mappings":"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"}
@@ -18,8 +18,8 @@
18
18
  * OLLAMA_BASE_URL default http://localhost:11434 — local Ollama endpoint for
19
19
  * the Ollama backend (keyless; OLLAMA_API_KEY only for a
20
20
  * protected/remote instance).
21
- * E2E 1 to enable the NaCl box layer (needs a paired peer key)
22
- * PEER_PUBLIC_KEY base64 of the client's public key (dev pairing shortcut)
23
- * WORKTREE_ROOT, STATE_FILE, KEYS_FILE, PEER_FILE — storage overrides
21
+ * PAIRING 1 to render a pairing QR on startup until a device pairs
22
+ * IDENTITY_FILE, DEVICES_FILE — session-identity storage overrides
23
+ * WORKTREE_ROOT, STATE_FILE — storage overrides
24
24
  */
25
25
  export {};
package/dist/src/index.js CHANGED
@@ -18,31 +18,91 @@
18
18
  * OLLAMA_BASE_URL default http://localhost:11434 — local Ollama endpoint for
19
19
  * the Ollama backend (keyless; OLLAMA_API_KEY only for a
20
20
  * protected/remote instance).
21
- * E2E 1 to enable the NaCl box layer (needs a paired peer key)
22
- * PEER_PUBLIC_KEY base64 of the client's public key (dev pairing shortcut)
23
- * WORKTREE_ROOT, STATE_FILE, KEYS_FILE, PEER_FILE — storage overrides
21
+ * PAIRING 1 to render a pairing QR on startup until a device pairs
22
+ * IDENTITY_FILE, DEVICES_FILE — session-identity storage overrides
23
+ * WORKTREE_ROOT, STATE_FILE — storage overrides
24
24
  */
25
25
  import { startServer } from "./server.js";
26
- import { loadOrCreateKeypair, loadPeers } from "./crypto.js";
26
+ import { RendezvousIdentity } from "./rendezvous.js";
27
+ import { DeviceTable } from "./devices.js";
28
+ import { PairingCodes, buildPairingPayload, renderPairingQr } from "./pairing.js";
29
+ import { LanServer } from "./lan.js";
30
+ import { RelayDaemon } from "./relay-transport.js";
27
31
  import { OLLAMA_BASE_URL, refreshOllamaModels } from "./agent/ollama-probe.js";
28
32
  const port = Number(process.env.DAEMON_PORT ?? 8787);
29
33
  const host = process.env.DAEMON_HOST ?? "127.0.0.1";
30
34
  const agentMode = (process.env.AGENT_MODE ?? "claude").toLowerCase();
31
- const e2e = process.env.E2E === "1";
32
- async function resolveCrypto() {
33
- if (!e2e)
34
- return undefined;
35
- const self = await loadOrCreateKeypair();
36
- console.log(`[daemon] E2E on. Daemon public key (encode this into the pairing QR):\n ${self.publicKey.toString("base64")}`);
37
- // Devices pair themselves on connect (the `hello` handshake, gated by the
38
- // Cloudflare Access token). Pre-existing pairings are just logged here.
39
- const peers = await loadPeers();
40
- console.log(`[daemon] ${peers.length} paired device(s); each socket seals to its own key on hello.`);
41
- return { self };
35
+ /**
36
+ * Multi-transport session handshake (docs/WEBRTC_MIGRATION.md). Mints/loads the
37
+ * daemon's Ed25519 identity + rendezvous id and the paired-device table. Since
38
+ * the Phase-5 cutover this is the only E2E path (the NaCl box + Cloudflare Access
39
+ * gate are gone).
40
+ */
41
+ async function resolveRendezvous() {
42
+ const identity = new RendezvousIdentity();
43
+ await identity.load();
44
+ const devices = new DeviceTable();
45
+ await devices.load();
46
+ const pairing = new PairingCodes();
47
+ console.log(`[daemon] rendezvous ${identity.rendezvousId()} · ${devices.list().length} paired device(s)`);
48
+ return { identity, devices, pairing };
49
+ }
50
+ const rendezvous = await resolveRendezvous();
51
+ // LAN direct (Phase 3): advertise `_immediacy._tcp` + accept TCP handshakes, so a
52
+ // device on the same network reaches this daemon with no relay. On by default;
53
+ // LAN=0 disables (e.g. locked-down networks).
54
+ const lan = process.env.LAN === "0" ? undefined : new LanServer(rendezvous);
55
+ // Relay (Phase 4): keep a persistent socket to the Durable Object for this
56
+ // daemon's rendezvous id, so a device on cellular reaches it behind NAT. Enabled
57
+ // by pointing RELAY_URL at the signal server (e.g. wss://signal.example.com).
58
+ const relayUrl = (process.env.RELAY_URL ?? "").trim();
59
+ const relay = relayUrl
60
+ ? new RelayDaemon(rendezvous, relayUrl, { license: process.env.LICENSE_TOKEN })
61
+ : undefined;
62
+ const daemon = await startServer({ port, host, rendezvous, lan, relay });
63
+ // startServer wired each server's onSession into the shared dispatch; now bring
64
+ // the transports up.
65
+ if (lan) {
66
+ try {
67
+ const lanPort = await lan.start(0, { host: "0.0.0.0" });
68
+ console.log(`[daemon] LAN direct on tcp/${lanPort} (mDNS _immediacy._tcp)`);
69
+ }
70
+ catch (err) {
71
+ console.log(`[daemon] LAN direct unavailable: ${err instanceof Error ? err.message : String(err)}`);
72
+ }
73
+ }
74
+ if (relay) {
75
+ relay.connect();
76
+ console.log(`[daemon] relay: connecting to ${relayUrl} for ${rendezvous.identity.rendezvousId()}`);
42
77
  }
43
- const crypto = await resolveCrypto();
44
- const daemon = await startServer({ port, host, crypto });
45
78
  console.log(`[daemon] agent mode: ${agentMode}`);
79
+ // Pairing QR for a new device (PAIRING=1). Issues a fresh single-use code and
80
+ // renders a scannable QR, refreshing before the 60s TTL lapses, until the first
81
+ // device pairs. Off by default so existing daemons don't print a QR on restart.
82
+ if (process.env.PAIRING === "1") {
83
+ const pairingBaseline = rendezvous.devices.list().length;
84
+ const showQr = async () => {
85
+ const { code } = rendezvous.pairing.issue();
86
+ console.log("\n[daemon] Scan to pair a new device:");
87
+ await renderPairingQr(buildPairingPayload({
88
+ rendezvousId: rendezvous.identity.rendezvousId(),
89
+ daemonPublicKeyB64: rendezvous.identity.publicKeyB64(),
90
+ code,
91
+ signalUrl: process.env.SIGNAL_URL,
92
+ }));
93
+ };
94
+ await showQr();
95
+ const pairingTimer = setInterval(() => {
96
+ // Stop once a new device has paired.
97
+ if (rendezvous.devices.list().length > pairingBaseline) {
98
+ clearInterval(pairingTimer);
99
+ console.log("[daemon] device paired — stopping pairing QR.");
100
+ return;
101
+ }
102
+ void showQr();
103
+ }, 55_000);
104
+ pairingTimer.unref();
105
+ }
46
106
  // Probe for a local Ollama and, if present, load the models it serves so they
47
107
  // show up in the picker. Best-effort: an unreachable Ollama just yields none.
48
108
  const ollamaModelList = await refreshOllamaModels(true);
@@ -77,6 +137,8 @@ function shutdown(signal) {
77
137
  console.log(`\n[daemon] ${signal} received, closing…`);
78
138
  clearInterval(mediaSweepTimer);
79
139
  daemon.jobs.stop(); // cancel cron timers so nothing fires mid-teardown
140
+ relay?.close();
141
+ void lan?.stop();
80
142
  void daemon.workspaces.shutdown().finally(() => {
81
143
  daemon.wss.close();
82
144
  daemon.http.close(() => process.exit(0));
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * LAN direct transport (docs/WEBRTC_MIGRATION.md Phase 3).
3
+ *
4
+ * Priority-2 path: when a client and daemon share a network segment they talk
5
+ * directly over TCP — no Cloudflare, no relay, works with the router's WAN link
6
+ * unplugged. The daemon:
7
+ * - advertises `_immediacy._tcp` over mDNS (TXT `rid=<rendezvous id>`) so a
8
+ * client can discover it without any typed address;
9
+ * - listens on TCP and, per connection, runs the §5.3 session handshake over
10
+ * the frame `Mux` (tcp-transport.ts + transport.ts), then hands the caller a
11
+ * `SecureChannel` for that peer.
12
+ *
13
+ * The handshake and frame codec are the SAME modules the loopback and (future)
14
+ * relay/WebRTC paths use — only the byte transport differs.
15
+ */
16
+ import { Mux } from "./transport.js";
17
+ import { type SecureChannel, type InitiatorOpts } from "./session.js";
18
+ import type { RendezvousIdentity } from "./rendezvous.js";
19
+ import type { DeviceTable } from "./devices.js";
20
+ /** bonjour "type" (it prepends `_` and appends `._tcp`) ⇒ `_immediacy._tcp`. */
21
+ export declare const LAN_SERVICE_TYPE = "immediacy";
22
+ /** A peer session established over LAN. */
23
+ export interface LanSession {
24
+ mux: Mux;
25
+ channel: SecureChannel;
26
+ /** The peer device's Ed25519 pubkey (base64), from its handshake msg1. */
27
+ devicePub: string;
28
+ close(): void;
29
+ /** Register a callback for when the peer/transport drops. */
30
+ onClose(cb: () => void): void;
31
+ }
32
+ /**
33
+ * Run the responder (daemon) side of the handshake over `mux`'s control channel:
34
+ * receive msg1, verify the device against `isPaired`, reply msg2, and resolve
35
+ * with the established `SecureChannel`. Rejects on an unknown/revoked device or a
36
+ * malformed/failed handshake.
37
+ */
38
+ export declare function respondHandshakeOverMux(mux: Mux, opts: {
39
+ daemonSignSecret: Buffer;
40
+ rendezvousId: string;
41
+ isPaired: (devicePubB64: string) => boolean;
42
+ /** First-pairing hook (see `ResponderOpts.redeemCode`). */
43
+ redeemCode?: (devicePubB64: string, code: string) => boolean;
44
+ }, timeoutMs?: number): Promise<{
45
+ channel: SecureChannel;
46
+ devicePub: string;
47
+ paired: boolean;
48
+ label?: string;
49
+ }>;
50
+ /**
51
+ * Run the initiator (client) side over `mux`'s control channel: send msg1, wait
52
+ * for msg2, and resolve with the `SecureChannel`. Used by the LAN test and
53
+ * mirrored by the Flutter/macOS LAN clients.
54
+ */
55
+ export declare function initiateHandshakeOverMux(mux: Mux, opts: InitiatorOpts, timeoutMs?: number): Promise<SecureChannel>;
56
+ export interface RendezvousLike {
57
+ identity: RendezvousIdentity;
58
+ devices: DeviceTable;
59
+ /**
60
+ * Single-use pairing codes. When present, a brand-new device can pair inline
61
+ * during the handshake (msg1 carries a code). Omit to disable LAN pairing.
62
+ */
63
+ pairing?: {
64
+ consume(code: string): boolean;
65
+ };
66
+ }
67
+ export declare class LanServer {
68
+ private readonly rendezvous;
69
+ private server?;
70
+ private bonjour?;
71
+ private service?;
72
+ constructor(rendezvous: RendezvousLike);
73
+ /** Called for each peer that completes the handshake over LAN. */
74
+ onSession?: (session: LanSession) => void;
75
+ /**
76
+ * Start the TCP listener and advertise it over mDNS. Returns the bound port.
77
+ * `advertise: false` skips mDNS (useful in tests / restricted networks).
78
+ */
79
+ start(port?: number, opts?: {
80
+ advertise?: boolean;
81
+ host?: string;
82
+ }): Promise<number>;
83
+ private advertise;
84
+ private onConnection;
85
+ stop(): Promise<void>;
86
+ }
87
+ /** Discovered daemon on the LAN. */
88
+ export interface LanPeer {
89
+ rendezvousId: string;
90
+ host: string;
91
+ port: number;
92
+ name: string;
93
+ }
94
+ /**
95
+ * Browse the LAN for advertised daemons. Calls `onPeer` for each service that
96
+ * carries a rendezvous-id TXT record. Returns a stop function.
97
+ */
98
+ export declare function browseLan(onPeer: (peer: LanPeer) => void): () => void;
99
+ /** Open a LAN TCP connection to `host:port` and wrap it in a `Mux`. */
100
+ export declare function lanConnect(host: string, port: number): Promise<{
101
+ mux: Mux;
102
+ close(): void;
103
+ }>;