aibroker 0.35.2 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/core/hybrid.d.ts.map +1 -1
  2. package/dist/core/hybrid.js +6 -0
  3. package/dist/core/hybrid.js.map +1 -1
  4. package/dist/daemon/cli.js +78 -0
  5. package/dist/daemon/cli.js.map +1 -1
  6. package/dist/daemon/core-handlers.d.ts.map +1 -1
  7. package/dist/daemon/core-handlers.js +66 -1
  8. package/dist/daemon/core-handlers.js.map +1 -1
  9. package/dist/daemon/index.d.ts.map +1 -1
  10. package/dist/daemon/index.js +13 -0
  11. package/dist/daemon/index.js.map +1 -1
  12. package/dist/daemon/machine.d.ts +63 -0
  13. package/dist/daemon/machine.d.ts.map +1 -0
  14. package/dist/daemon/machine.js +153 -0
  15. package/dist/daemon/machine.js.map +1 -0
  16. package/dist/daemon/manage.d.ts +77 -0
  17. package/dist/daemon/manage.d.ts.map +1 -0
  18. package/dist/daemon/manage.js +942 -0
  19. package/dist/daemon/manage.js.map +1 -0
  20. package/dist/daemon/peer-cli.d.ts +9 -0
  21. package/dist/daemon/peer-cli.d.ts.map +1 -0
  22. package/dist/daemon/peer-cli.js +164 -0
  23. package/dist/daemon/peer-cli.js.map +1 -0
  24. package/dist/daemon/peer-handlers.d.ts +35 -0
  25. package/dist/daemon/peer-handlers.d.ts.map +1 -0
  26. package/dist/daemon/peer-handlers.js +165 -0
  27. package/dist/daemon/peer-handlers.js.map +1 -0
  28. package/dist/daemon/standup.d.ts +69 -0
  29. package/dist/daemon/standup.d.ts.map +1 -0
  30. package/dist/daemon/standup.js +141 -0
  31. package/dist/daemon/standup.js.map +1 -0
  32. package/dist/ipc/peering.d.ts +103 -0
  33. package/dist/ipc/peering.d.ts.map +1 -0
  34. package/dist/ipc/peering.js +185 -0
  35. package/dist/ipc/peering.js.map +1 -0
  36. package/dist/ipc/server.d.ts +16 -0
  37. package/dist/ipc/server.d.ts.map +1 -1
  38. package/dist/ipc/server.js +76 -0
  39. package/dist/ipc/server.js.map +1 -1
  40. package/dist/mcp/index.js +45 -0
  41. package/dist/mcp/index.js.map +1 -1
  42. package/hooks/manage-hook.mjs +204 -0
  43. package/package.json +1 -1
@@ -0,0 +1,141 @@
1
+ /**
2
+ * daemon/standup.ts — what everyone did, is doing, and is stuck on.
3
+ *
4
+ * THE OLDEST MEETING IN SOFTWARE, and it survives because it answers the three
5
+ * questions a lead cannot get from a repository: what moved, what is moving,
6
+ * and what is blocked. Two of those are already visible here — the objective a
7
+ * machine is working to, and the state of its branch. The third is the one that
8
+ * needs the agent to say something, so the standup asks for it rather than
9
+ * inferring it, exactly as it would of a person.
10
+ *
11
+ * WHY IT IS NOT A MEETING. Nobody attends. It is assembled on a schedule and
12
+ * delivered wherever the lead is — phone, terminal, task list — because the
13
+ * whole reason for giving each developer their own machine was that the lead
14
+ * stopped having to sit and watch. A standup that requires attendance would put
15
+ * that back.
16
+ *
17
+ * WHAT IT DELIBERATELY DOES NOT DO. It does not decide anything. No agent is
18
+ * appointed to run it, chase people or reprioritise. The scrum master's job
19
+ * splits cleanly in two: the process half is a schedule and a template, which is
20
+ * this file; the judgement half is deciding what matters when two things
21
+ * conflict, which stays with the person. Automating the first is a saving.
22
+ * Automating the second recreates the supervisor problem this whole design
23
+ * exists to remove — an agent watching agents, costing a context, becoming the
24
+ * bottleneck it was meant to relieve.
25
+ */
26
+ import { loadPeering, peerCall } from "../ipc/peering.js";
27
+ import { machineFacts, branchState } from "./machine.js";
28
+ import { log } from "../core/log.js";
29
+ /**
30
+ * Collect from every machine, including this one.
31
+ *
32
+ * An unreachable machine is REPORTED as unreachable rather than dropped. A
33
+ * developer missing from a standup and a developer with nothing to say look
34
+ * identical in a list that omits them, and they want opposite reactions — one
35
+ * needs chasing, the other does not.
36
+ */
37
+ export async function collectStandup(repo) {
38
+ const rows = [];
39
+ const here = machineFacts();
40
+ const local = { machine: here.name, reachable: true };
41
+ if (repo ?? here.workRoot) {
42
+ const st = branchState((repo ?? here.workRoot));
43
+ Object.assign(local, {
44
+ branch: st.branch, head: st.head, dirty: st.dirty, ahead: st.ahead, behind: st.behind, detail: st.error,
45
+ });
46
+ }
47
+ rows.push(local);
48
+ const { peers } = loadPeering();
49
+ await Promise.all(peers.map(async (p) => {
50
+ const r = await peerCall(p, "where", repo ? { repo } : {}, 10_000);
51
+ if (!r.ok) {
52
+ rows.push({ machine: p.name, reachable: false, detail: r.error });
53
+ return;
54
+ }
55
+ const c = r.result?.checkout ?? {};
56
+ rows.push({
57
+ machine: p.name,
58
+ reachable: true,
59
+ branch: c.branch, head: c.head, dirty: c.dirty, ahead: c.ahead, behind: c.behind,
60
+ detail: c.error,
61
+ });
62
+ }));
63
+ return rows;
64
+ }
65
+ /**
66
+ * The report, in the shape a person reads at a glance.
67
+ *
68
+ * Ordered by what needs attention rather than by machine name: anything
69
+ * unreachable or blocked first, then work ready to look at, then the rest. A
70
+ * list sorted alphabetically makes the reader do the triage that the report
71
+ * exists to save them.
72
+ */
73
+ export function renderStandup(rows) {
74
+ const needsAttention = (r) => !r.reachable || !!r.blocked || !!r.detail;
75
+ const readyToReview = (r) => (r.ahead ?? 0) > 0 && (r.dirty ?? 0) === 0;
76
+ const sorted = [...rows].sort((a, b) => {
77
+ const rank = (r) => (needsAttention(r) ? 0 : readyToReview(r) ? 1 : 2);
78
+ return rank(a) - rank(b);
79
+ });
80
+ const lines = [];
81
+ for (const r of sorted) {
82
+ if (!r.reachable) {
83
+ lines.push(` ✗ ${r.machine} — cannot be reached: ${r.detail ?? "no reason given"}`);
84
+ continue;
85
+ }
86
+ const bits = [];
87
+ if (r.branch)
88
+ bits.push(`on ${r.branch}${r.head ? ` at ${r.head}` : ""}`);
89
+ if (r.dirty)
90
+ bits.push(`${r.dirty} uncommitted`);
91
+ if (r.ahead)
92
+ bits.push(`${r.ahead} to review`);
93
+ if (r.behind)
94
+ bits.push(`${r.behind} behind`);
95
+ if (r.detail)
96
+ bits.push(r.detail);
97
+ const mark = readyToReview(r) ? "▲" : "·";
98
+ lines.push(` ${mark} ${r.machine}${bits.length ? ` — ${bits.join(", ")}` : " — nothing reported"}`);
99
+ if (r.objective)
100
+ lines.push(` working to: ${r.objective.slice(0, 100)}`);
101
+ if (r.blocked)
102
+ lines.push(` BLOCKED: ${r.blocked}`);
103
+ }
104
+ const ready = sorted.filter(readyToReview).length;
105
+ const stuck = sorted.filter((r) => !r.reachable).length;
106
+ const head = `standup — ${rows.length} machine${rows.length === 1 ? "" : "s"}` +
107
+ (ready ? `, ${ready} with work to review` : "") +
108
+ (stuck ? `, ${stuck} unreachable` : "");
109
+ return `${head}\n${lines.join("\n")}`;
110
+ }
111
+ let timer = null;
112
+ /**
113
+ * Run it on a schedule and deliver it.
114
+ *
115
+ * Off unless asked for. A report that arrives whether or not anybody wanted it
116
+ * teaches its reader to skip it, and a standup nobody reads is worse than none
117
+ * because it looks like oversight.
118
+ */
119
+ export function startStandup(everyMinutes, deliver, repo) {
120
+ if (timer)
121
+ clearInterval(timer);
122
+ if (everyMinutes <= 0)
123
+ return;
124
+ const run = async () => {
125
+ try {
126
+ deliver(renderStandup(await collectStandup(repo)));
127
+ }
128
+ catch (e) {
129
+ log(`[standup] could not assemble — ${e.message}`);
130
+ }
131
+ };
132
+ timer = setInterval(() => void run(), everyMinutes * 60_000);
133
+ timer.unref?.();
134
+ log(`[standup] every ${everyMinutes} min`);
135
+ }
136
+ export function stopStandup() {
137
+ if (timer)
138
+ clearInterval(timer);
139
+ timer = null;
140
+ }
141
+ //# sourceMappingURL=standup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standup.js","sourceRoot":"","sources":["../../src/daemon/standup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * ipc/peering.ts — one hub talking to another, across machines.
3
+ *
4
+ * WHY PEERS AND NOT REMOTE CLIENTS. The obvious design is to let a session on
5
+ * another machine dial this hub directly. It does not work: the hub finds
6
+ * sessions by enumerating terminal panes on its own machine, and a pane on
7
+ * another machine is not enumerable from here at all. So each machine runs its
8
+ * own hub, which knows its own panes, and the hubs talk to each other. The
9
+ * guest is then a first-class participant — its sessions appear in listings,
10
+ * take messages and can be managed — rather than a special case threaded
11
+ * through every call site.
12
+ *
13
+ * WHY THIS IS DANGEROUS AND WHAT IS DONE ABOUT IT. The local hub listens on a
14
+ * unix socket, which is protected by file permissions and by not existing
15
+ * anywhere else. This is a TCP port carrying commands that type into terminals
16
+ * and drive screens. So:
17
+ *
18
+ * - it is OFF unless configured. There is no default port.
19
+ * - it binds one named address. Never 0.0.0.0, which is refused outright
20
+ * rather than warned about, because a warning nobody reads is not a control.
21
+ * - every request must carry a shared secret, checked before dispatch. A
22
+ * connection that can reach the port is not thereby trusted.
23
+ * - the secret is generated, never chosen, and stored readable only by its
24
+ * owner.
25
+ *
26
+ * The intended network is a private overlay — a Tailscale address or a host-only
27
+ * link to a virtual machine. It is not intended to face anything else, and the
28
+ * bind check is what keeps that from being a matter of memory.
29
+ */
30
+ export interface PeerRecord {
31
+ /** What a person calls it: "guest", "laptop". Used to address its sessions. */
32
+ name: string;
33
+ host: string;
34
+ port: number;
35
+ /** Shared secret. Presented on every call in both directions. */
36
+ token: string;
37
+ lastSeenAt?: number;
38
+ lastError?: string;
39
+ }
40
+ export interface PeeringConfig {
41
+ /** This hub's own listener. Absent means it accepts no peers. */
42
+ listen?: {
43
+ host: string;
44
+ port: number;
45
+ token: string;
46
+ };
47
+ /** Hubs this one reaches out to. */
48
+ peers: PeerRecord[];
49
+ }
50
+ export declare function loadPeering(): PeeringConfig;
51
+ export declare function savePeering(c: PeeringConfig): void;
52
+ export declare function newToken(): string;
53
+ /**
54
+ * Compare secrets without leaking their contents through timing.
55
+ *
56
+ * Overkill for a home network and exactly the sort of thing that is never added
57
+ * later. Lengths are compared first because timingSafeEqual throws on a
58
+ * mismatch, and a thrown comparison is a failed comparison here.
59
+ */
60
+ export declare function tokenMatches(given: unknown, expected: string): boolean;
61
+ /**
62
+ * Addresses this hub may bind to.
63
+ *
64
+ * A wildcard bind is refused rather than discouraged. The port accepts commands
65
+ * that drive a machine's screen and keyboard; "we meant to change that later"
66
+ * is how such a thing ends up facing a café network. If someone genuinely wants
67
+ * it wide they can say so with an explicit address of their own choosing.
68
+ */
69
+ export declare function rejectWildcard(host: string): string | null;
70
+ /**
71
+ * The single string that pairs two machines.
72
+ *
73
+ * Everything needed to connect, in one blob a person can copy once: where to
74
+ * call, what secret to present, and what the other side calls itself. Pairing
75
+ * that takes four fields typed into two machines is pairing that gets done
76
+ * wrong at least once.
77
+ */
78
+ export declare function makeInvite(name: string, host: string, port: number, token: string): string;
79
+ export declare function readInvite(blob: string): {
80
+ name: string;
81
+ host: string;
82
+ port: number;
83
+ token: string;
84
+ };
85
+ /**
86
+ * Call a method on a peer hub.
87
+ *
88
+ * Same NDJSON request/response as the local socket, so a handler cannot tell
89
+ * where a call came from and does not need to. The token rides in the envelope
90
+ * rather than a header because there is no header — the protocol is one line of
91
+ * JSON, and adding a framing layer for one field would be its own bug surface.
92
+ */
93
+ export declare function peerCall(peer: PeerRecord, method: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<{
94
+ ok: boolean;
95
+ result?: any;
96
+ error?: string;
97
+ }>;
98
+ /** Address a session as `peer/session`, so a name cannot be ambiguous across machines. */
99
+ export declare function splitRemote(target: string): {
100
+ peer: string;
101
+ session: string;
102
+ } | null;
103
+ //# sourceMappingURL=peering.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peering.d.ts","sourceRoot":"","sources":["../../src/ipc/peering.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAWH,MAAM,WAAW,UAAU;IACzB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,oCAAoC;IACpC,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAOD,wBAAgB,WAAW,IAAI,aAAa,CAU3C;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAMlD;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAOtE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU1D;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1F;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAMpG;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,SAAS,SAAS,GACjB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgDxD;AAED,0FAA0F;AAC1F,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAIpF"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * ipc/peering.ts — one hub talking to another, across machines.
3
+ *
4
+ * WHY PEERS AND NOT REMOTE CLIENTS. The obvious design is to let a session on
5
+ * another machine dial this hub directly. It does not work: the hub finds
6
+ * sessions by enumerating terminal panes on its own machine, and a pane on
7
+ * another machine is not enumerable from here at all. So each machine runs its
8
+ * own hub, which knows its own panes, and the hubs talk to each other. The
9
+ * guest is then a first-class participant — its sessions appear in listings,
10
+ * take messages and can be managed — rather than a special case threaded
11
+ * through every call site.
12
+ *
13
+ * WHY THIS IS DANGEROUS AND WHAT IS DONE ABOUT IT. The local hub listens on a
14
+ * unix socket, which is protected by file permissions and by not existing
15
+ * anywhere else. This is a TCP port carrying commands that type into terminals
16
+ * and drive screens. So:
17
+ *
18
+ * - it is OFF unless configured. There is no default port.
19
+ * - it binds one named address. Never 0.0.0.0, which is refused outright
20
+ * rather than warned about, because a warning nobody reads is not a control.
21
+ * - every request must carry a shared secret, checked before dispatch. A
22
+ * connection that can reach the port is not thereby trusted.
23
+ * - the secret is generated, never chosen, and stored readable only by its
24
+ * owner.
25
+ *
26
+ * The intended network is a private overlay — a Tailscale address or a host-only
27
+ * link to a virtual machine. It is not intended to face anything else, and the
28
+ * bind check is what keeps that from being a matter of memory.
29
+ */
30
+ import { connect } from "node:net";
31
+ import { randomBytes, timingSafeEqual } from "node:crypto";
32
+ import { readFileSync, writeFileSync, existsSync, chmodSync, mkdirSync } from "node:fs";
33
+ import { join } from "node:path";
34
+ import { homedir } from "node:os";
35
+ import { log } from "../core/log.js";
36
+ const CONFIG_FILE = join(homedir(), ".aibroker", "peering.json");
37
+ function ensureDir() {
38
+ const dir = join(homedir(), ".aibroker");
39
+ if (!existsSync(dir))
40
+ mkdirSync(dir, { recursive: true });
41
+ }
42
+ export function loadPeering() {
43
+ try {
44
+ if (existsSync(CONFIG_FILE)) {
45
+ const c = JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
46
+ return { listen: c.listen, peers: c.peers ?? [] };
47
+ }
48
+ }
49
+ catch (e) {
50
+ log(`[peer] config unreadable, treating as empty — ${e.message}`);
51
+ }
52
+ return { peers: [] };
53
+ }
54
+ export function savePeering(c) {
55
+ ensureDir();
56
+ writeFileSync(CONFIG_FILE, JSON.stringify(c, null, 2));
57
+ // The file holds shared secrets. Anyone who can read it can type into these
58
+ // machines' terminals, so the permission is part of the design and not tidiness.
59
+ try {
60
+ chmodSync(CONFIG_FILE, 0o600);
61
+ }
62
+ catch { /* best effort on odd filesystems */ }
63
+ }
64
+ export function newToken() {
65
+ return randomBytes(32).toString("hex");
66
+ }
67
+ /**
68
+ * Compare secrets without leaking their contents through timing.
69
+ *
70
+ * Overkill for a home network and exactly the sort of thing that is never added
71
+ * later. Lengths are compared first because timingSafeEqual throws on a
72
+ * mismatch, and a thrown comparison is a failed comparison here.
73
+ */
74
+ export function tokenMatches(given, expected) {
75
+ if (typeof given !== "string" || given.length !== expected.length)
76
+ return false;
77
+ try {
78
+ return timingSafeEqual(Buffer.from(given), Buffer.from(expected));
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
84
+ /**
85
+ * Addresses this hub may bind to.
86
+ *
87
+ * A wildcard bind is refused rather than discouraged. The port accepts commands
88
+ * that drive a machine's screen and keyboard; "we meant to change that later"
89
+ * is how such a thing ends up facing a café network. If someone genuinely wants
90
+ * it wide they can say so with an explicit address of their own choosing.
91
+ */
92
+ export function rejectWildcard(host) {
93
+ const wild = ["0.0.0.0", "::", "*", ""];
94
+ if (wild.includes(host.trim())) {
95
+ return ("refusing to bind a wildcard address. This port accepts commands that type into " +
96
+ "terminals and drive the screen, so it must be bound to one named address — " +
97
+ "a Tailscale address, or the host-only address of a virtual machine.");
98
+ }
99
+ return null;
100
+ }
101
+ /**
102
+ * The single string that pairs two machines.
103
+ *
104
+ * Everything needed to connect, in one blob a person can copy once: where to
105
+ * call, what secret to present, and what the other side calls itself. Pairing
106
+ * that takes four fields typed into two machines is pairing that gets done
107
+ * wrong at least once.
108
+ */
109
+ export function makeInvite(name, host, port, token) {
110
+ return Buffer.from(JSON.stringify({ v: 1, name, host, port, token })).toString("base64");
111
+ }
112
+ export function readInvite(blob) {
113
+ const raw = JSON.parse(Buffer.from(blob.trim(), "base64").toString("utf8"));
114
+ if (raw?.v !== 1 || !raw.host || !raw.port || !raw.token || !raw.name) {
115
+ throw new Error("that invite is not readable — copy the whole line the other machine printed");
116
+ }
117
+ return { name: String(raw.name), host: String(raw.host), port: Number(raw.port), token: String(raw.token) };
118
+ }
119
+ /**
120
+ * Call a method on a peer hub.
121
+ *
122
+ * Same NDJSON request/response as the local socket, so a handler cannot tell
123
+ * where a call came from and does not need to. The token rides in the envelope
124
+ * rather than a header because there is no header — the protocol is one line of
125
+ * JSON, and adding a framing layer for one field would be its own bug surface.
126
+ */
127
+ export function peerCall(peer, method, params = {}, timeoutMs = 12_000) {
128
+ return new Promise((resolve) => {
129
+ const sock = connect({ host: peer.host, port: peer.port });
130
+ let buf = "";
131
+ let settled = false;
132
+ const done = (v) => {
133
+ if (settled)
134
+ return;
135
+ settled = true;
136
+ try {
137
+ sock.destroy();
138
+ }
139
+ catch { /* already gone */ }
140
+ resolve(v);
141
+ };
142
+ sock.on("connect", () => {
143
+ sock.write(JSON.stringify({
144
+ id: `peer-${Date.now()}`,
145
+ sessionId: "peer",
146
+ method,
147
+ params,
148
+ peerToken: peer.token,
149
+ }) + "\n");
150
+ });
151
+ sock.on("data", (d) => {
152
+ buf += d.toString();
153
+ const nl = buf.indexOf("\n");
154
+ if (nl === -1)
155
+ return;
156
+ try {
157
+ done(JSON.parse(buf.slice(0, nl)));
158
+ }
159
+ catch {
160
+ done({ ok: false, error: "unreadable answer from peer" });
161
+ }
162
+ });
163
+ sock.on("error", (e) => {
164
+ // Name the common causes rather than surfacing a bare code, because the
165
+ // three that actually happen have three different fixes.
166
+ const why = e.code === "ECONNREFUSED"
167
+ ? "nothing is listening there — is the other machine's daemon running, and is peering switched on?"
168
+ : e.code === "EHOSTUNREACH" || e.code === "ENETUNREACH"
169
+ ? "no route to that address — check the network between the machines"
170
+ : e.code === "ETIMEDOUT"
171
+ ? "timed out reaching it"
172
+ : (e.code ?? e.message);
173
+ done({ ok: false, error: `${peer.name}: ${why}` });
174
+ });
175
+ setTimeout(() => done({ ok: false, error: `${peer.name}: no answer within ${Math.round(timeoutMs / 1000)}s` }), timeoutMs);
176
+ });
177
+ }
178
+ /** Address a session as `peer/session`, so a name cannot be ambiguous across machines. */
179
+ export function splitRemote(target) {
180
+ const i = target.indexOf("/");
181
+ if (i <= 0 || i === target.length - 1)
182
+ return null;
183
+ return { peer: target.slice(0, i), session: target.slice(i + 1) };
184
+ }
185
+ //# sourceMappingURL=peering.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peering.js","sourceRoot":"","sources":["../../src/ipc/peering.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,13 @@ export type IpcHandler = (req: IpcRequest) => Promise<{
14
14
  }>;
15
15
  export declare class IpcServer {
16
16
  private server;
17
+ /**
18
+ * The peer listener, if peering is configured. A second server object rather
19
+ * than a second address on the first, because the two have different trust:
20
+ * the unix socket is protected by file permissions and everything on it is
21
+ * already local, while this one must authenticate every request.
22
+ */
23
+ private peerServer;
17
24
  private readonly handlers;
18
25
  private readonly socketPath;
19
26
  constructor(socketPath: string);
@@ -25,6 +32,15 @@ export declare class IpcServer {
25
32
  * Start listening on the Unix Domain Socket.
26
33
  */
27
34
  start(): void;
35
+ /**
36
+ * The cross-machine door. Closed unless somebody configured it.
37
+ *
38
+ * Everything that makes this safe is here rather than spread out: no default
39
+ * port, no wildcard bind, and a secret checked on every single request before
40
+ * the method is even looked up. A caller that reached the port has proved
41
+ * nothing except that it reached the port.
42
+ */
43
+ private startPeerListener;
28
44
  /**
29
45
  * Stop the IPC server.
30
46
  */
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/ipc/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,iBAAiB,CAAC;AAK/D,MAAM,MAAM,UAAU,GAAG,CACvB,GAAG,EAAE,UAAU,KACZ,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE3F,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,UAAU,EAAE,MAAM;IAI9B;;OAEG;IACH,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAI7C;;OAEG;IACH,KAAK,IAAI,IAAI;IAqDb;;OAEG;IACH,IAAI,IAAI,IAAI;YAOE,QAAQ;CA0CvB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/ipc/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,iBAAiB,CAAC;AAM/D,MAAM,MAAM,UAAU,GAAG,CACvB,GAAG,EAAE,UAAU,KACZ,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE3F,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAuB;IACrC;;;;;OAKG;IACH,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,UAAU,EAAE,MAAM;IAI9B;;OAEG;IACH,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAI7C;;OAEG;IACH,KAAK,IAAI,IAAI;IAuDb;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IA8DzB;;OAEG;IACH,IAAI,IAAI,IAAI;YAQE,QAAQ;CA0CvB"}
@@ -9,8 +9,16 @@ import { existsSync, unlinkSync } from "node:fs";
9
9
  import { log } from "../core/log.js";
10
10
  import { sessionRegistry, clientQueues } from "../core/state.js";
11
11
  import { aibrokerIdForPane } from "../transport/sync-facade.js";
12
+ import { loadPeering, rejectWildcard, tokenMatches } from "./peering.js";
12
13
  export class IpcServer {
13
14
  server = null;
15
+ /**
16
+ * The peer listener, if peering is configured. A second server object rather
17
+ * than a second address on the first, because the two have different trust:
18
+ * the unix socket is protected by file permissions and everything on it is
19
+ * already local, while this one must authenticate every request.
20
+ */
21
+ peerServer = null;
14
22
  handlers = new Map();
15
23
  socketPath;
16
24
  constructor(socketPath) {
@@ -73,12 +81,80 @@ export class IpcServer {
73
81
  this.server.on("error", (err) => {
74
82
  log(`IPC server error: ${err}`);
75
83
  });
84
+ this.startPeerListener();
85
+ }
86
+ /**
87
+ * The cross-machine door. Closed unless somebody configured it.
88
+ *
89
+ * Everything that makes this safe is here rather than spread out: no default
90
+ * port, no wildcard bind, and a secret checked on every single request before
91
+ * the method is even looked up. A caller that reached the port has proved
92
+ * nothing except that it reached the port.
93
+ */
94
+ startPeerListener() {
95
+ let cfg;
96
+ try {
97
+ cfg = loadPeering().listen;
98
+ }
99
+ catch {
100
+ return;
101
+ }
102
+ if (!cfg?.port || !cfg.host || !cfg.token)
103
+ return;
104
+ const refusal = rejectWildcard(cfg.host);
105
+ if (refusal) {
106
+ log(`[peer] NOT listening: ${refusal}`);
107
+ return;
108
+ }
109
+ this.peerServer = createServer((socket) => {
110
+ let buffer = "";
111
+ socket.on("data", (chunk) => {
112
+ buffer += chunk.toString();
113
+ const nl = buffer.indexOf("\n");
114
+ if (nl === -1)
115
+ return;
116
+ const line = buffer.slice(0, nl);
117
+ buffer = buffer.slice(nl + 1);
118
+ const deny = (error) => {
119
+ socket.write(JSON.stringify({ id: "unknown", ok: false, error }) + "\n");
120
+ socket.end();
121
+ };
122
+ let req;
123
+ try {
124
+ req = JSON.parse(line);
125
+ }
126
+ catch {
127
+ return deny("Invalid JSON");
128
+ }
129
+ // Authenticate BEFORE dispatch, and say as little as possible about why.
130
+ // A caller learning which half of the credential was wrong is a caller
131
+ // being helped to guess.
132
+ if (!tokenMatches(req.peerToken, cfg.token)) {
133
+ log(`[peer] rejected an unauthenticated request from ${socket.remoteAddress ?? "?"}`);
134
+ return deny("not paired with this hub");
135
+ }
136
+ this.dispatch(req)
137
+ .then((resp) => { socket.write(JSON.stringify(resp) + "\n"); socket.end(); })
138
+ .catch((err) => {
139
+ socket.write(JSON.stringify({ id: req.id, ok: false, error: err instanceof Error ? err.message : String(err) }) + "\n");
140
+ socket.end();
141
+ });
142
+ });
143
+ socket.on("error", () => { });
144
+ });
145
+ this.peerServer.listen(cfg.port, cfg.host, () => {
146
+ log(`[peer] listening on ${cfg.host}:${cfg.port} — paired hubs only`);
147
+ });
148
+ this.peerServer.on("error", (err) => {
149
+ log(`[peer] listener error: ${err}`);
150
+ });
76
151
  }
77
152
  /**
78
153
  * Stop the IPC server.
79
154
  */
80
155
  stop() {
81
156
  this.server?.close();
157
+ this.peerServer?.close();
82
158
  if (existsSync(this.socketPath)) {
83
159
  try {
84
160
  unlinkSync(this.socketPath);
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/ipc/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/ipc/server.ts"],"names":[],"mappings":"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"}