@geohar/un-bien 0.7.0 → 0.9.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 (89) hide show
  1. package/README.md +237 -268
  2. package/dist/bin/launcher.js +38 -0
  3. package/dist/bin/launcher.js.map +1 -0
  4. package/dist/config.d.ts +41 -1
  5. package/dist/config.js.map +1 -1
  6. package/dist/daemon/install.d.ts +33 -35
  7. package/dist/daemon/install.js +43 -73
  8. package/dist/daemon/install.js.map +1 -1
  9. package/dist/enrich_tool_args.d.ts +9 -0
  10. package/dist/enrich_tool_args.js +171 -0
  11. package/dist/enrich_tool_args.js.map +1 -0
  12. package/dist/image_codec.d.ts +22 -0
  13. package/dist/image_codec.js +149 -0
  14. package/dist/image_codec.js.map +1 -0
  15. package/dist/index.d.ts +0 -26
  16. package/dist/index.js +235 -1237
  17. package/dist/index.js.map +1 -1
  18. package/dist/launch.d.ts +34 -0
  19. package/dist/launch.js +171 -0
  20. package/dist/launch.js.map +1 -0
  21. package/dist/launcher/launcher.d.ts +6 -0
  22. package/dist/launcher/launcher.js +188 -0
  23. package/dist/launcher/launcher.js.map +1 -0
  24. package/dist/pairing/crypto.d.ts +4 -0
  25. package/dist/pairing/crypto.js +10 -0
  26. package/dist/pairing/crypto.js.map +1 -1
  27. package/dist/pairing/peer_trust.d.ts +20 -0
  28. package/dist/pairing/peer_trust.js +82 -0
  29. package/dist/pairing/peer_trust.js.map +1 -0
  30. package/dist/pairing/storage.d.ts +51 -26
  31. package/dist/pairing/storage.js +346 -136
  32. package/dist/pairing/storage.js.map +1 -1
  33. package/dist/panel_bridge.d.ts +3 -1
  34. package/dist/panel_bridge.js +10 -10
  35. package/dist/panel_bridge.js.map +1 -1
  36. package/dist/rooms.d.ts +16 -0
  37. package/dist/rooms.js +22 -0
  38. package/dist/rooms.js.map +1 -1
  39. package/dist/session/debug_log.js +2 -2
  40. package/dist/session/debug_log.js.map +1 -1
  41. package/dist/session/local_config.js +2 -0
  42. package/dist/session/local_config.js.map +1 -1
  43. package/dist/session/rpc_envelope.d.ts +18 -0
  44. package/dist/session/rpc_envelope.js +1 -1
  45. package/dist/session/rpc_envelope.js.map +1 -1
  46. package/dist/session/rpc_inbound.d.ts +2 -2
  47. package/dist/session/rpc_inbound.js +1 -1
  48. package/dist/session/rpc_inbound.js.map +1 -1
  49. package/dist/subagent_rooms.d.ts +49 -0
  50. package/dist/subagent_rooms.js +787 -0
  51. package/dist/subagent_rooms.js.map +1 -0
  52. package/dist/transport/peer_channel.d.ts +7 -1
  53. package/dist/transport/peer_channel.js +8 -1
  54. package/dist/transport/peer_channel.js.map +1 -1
  55. package/dist/transport/relay_client.d.ts +19 -0
  56. package/dist/transport/relay_client.js.map +1 -1
  57. package/docs/daemon.md +65 -64
  58. package/package.json +13 -11
  59. package/service-templates/launchd.plist.template +5 -5
  60. package/service-templates/systemd.service.template +2 -2
  61. package/service-templates/task-launcher.vbs.template +5 -5
  62. package/service-templates/task-scheduler.xml.template +1 -1
  63. package/dist/bin/supervisord.js +0 -86
  64. package/dist/bin/supervisord.js.map +0 -1
  65. package/dist/daemon/client.d.ts +0 -20
  66. package/dist/daemon/client.js +0 -131
  67. package/dist/daemon/client.js.map +0 -1
  68. package/dist/daemon/control_protocol.d.ts +0 -176
  69. package/dist/daemon/control_protocol.js +0 -63
  70. package/dist/daemon/control_protocol.js.map +0 -1
  71. package/dist/daemon/cron_log.d.ts +0 -45
  72. package/dist/daemon/cron_log.js +0 -71
  73. package/dist/daemon/cron_log.js.map +0 -1
  74. package/dist/daemon/cron_registry.d.ts +0 -80
  75. package/dist/daemon/cron_registry.js +0 -198
  76. package/dist/daemon/cron_registry.js.map +0 -1
  77. package/dist/daemon/id.d.ts +0 -24
  78. package/dist/daemon/id.js +0 -36
  79. package/dist/daemon/id.js.map +0 -1
  80. package/dist/daemon/registry.d.ts +0 -63
  81. package/dist/daemon/registry.js +0 -153
  82. package/dist/daemon/registry.js.map +0 -1
  83. package/dist/daemon/rpc_child.d.ts +0 -194
  84. package/dist/daemon/rpc_child.js +0 -419
  85. package/dist/daemon/rpc_child.js.map +0 -1
  86. package/dist/daemon/supervisor.d.ts +0 -93
  87. package/dist/daemon/supervisor.js +0 -666
  88. package/dist/daemon/supervisor.js.map +0 -1
  89. /package/dist/bin/{supervisord.d.ts → launcher.d.ts} +0 -0
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Launch backends for remote `session_launch` — shared by the extension (index.ts)
3
+ * and the regime-2 launcher daemon. PTY-hosted backends exec `pi` cleanly (no
4
+ * keystrokes): `tmux` = a WINDOW in one shared named session; `herdr` =
5
+ * `workspace create` then `agent start --kind pi`. `rpc` is a fast-follow.
6
+ */
7
+ /** Expand a leading `~`/`~/` to the extension machine's home dir. Node's `fs` does
8
+ * NOT expand `~`, so a launch cwd like `~/proj` would fail the existsSync
9
+ * check and silently abort the launch. Machine-side (the phone's `~` is
10
+ * meaningless here). Exported for tests. */
11
+ export declare function _expandTilde(p: string): string;
12
+ /** Build the argv for launching `pi` in `cwd` as a WINDOW of the shared tmux
13
+ * session `session` (one named session, a window per pi — clean `new-window`,
14
+ * NO keystrokes/prefix). First launch creates the detached session; later ones
15
+ * add a window. Array (never a shell string) so cwd/names can't inject.
16
+ * Exported for tests. */
17
+ export declare function _buildTmuxLaunchArgs(session: string, windowName: string, cwd: string, sessionExists: boolean): string[];
18
+ /** argv for creating a detached herdr workspace in `cwd` (JSON response).
19
+ * ARRAY (never a shell string) so cwd/label can't inject. Exported for tests. */
20
+ export declare function _buildHerdrWorkspaceArgs(label: string, cwd: string): string[];
21
+ /** argv for exec-launching `pi` as a named herdr agent in an existing pane —
22
+ * herdr's canonical-executable launch (NOT keystroke injection). */
23
+ export declare function _buildHerdrAgentStartArgs(agentName: string, paneId: string): string[];
24
+ /** Extract `.result.root_pane.pane_id` from `herdr workspace create --json`. */
25
+ export declare function _herdrPaneIdFromCreate(stdout: string): string | null;
26
+ /**
27
+ * Honor a `session_launch` request. The caller checked the per-cwd opt-in and
28
+ * picked `mode` from `launch.backend` (machine config). PTY-hosted backends
29
+ * exec `pi` cleanly (no keystrokes): `tmux` = detached `new-session … pi`;
30
+ * `herdr` = `workspace create` then `agent start --kind pi`. `rpc` is a
31
+ * fast-follow (stubbed). Returns null when the launch is initiated, else an
32
+ * error string.
33
+ */
34
+ export declare function _launchSession(mode: "tmux" | "herdr" | "rpc", cwd: string, name: string | undefined): string | null;
package/dist/launch.js ADDED
@@ -0,0 +1,171 @@
1
+ import { spawn, spawnSync, execFile } from "node:child_process";
2
+ import { basename, join } from "node:path";
3
+ import { existsSync, statSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { loadConfig } from "./config.js";
6
+ import { envLog } from "./session/debug_log.js";
7
+ /**
8
+ * Launch backends for remote `session_launch` — shared by the extension (index.ts)
9
+ * and the regime-2 launcher daemon. PTY-hosted backends exec `pi` cleanly (no
10
+ * keystrokes): `tmux` = a WINDOW in one shared named session; `herdr` =
11
+ * `workspace create` then `agent start --kind pi`. `rpc` is a fast-follow.
12
+ */
13
+ /** Expand a leading `~`/`~/` to the extension machine's home dir. Node's `fs` does
14
+ * NOT expand `~`, so a launch cwd like `~/proj` would fail the existsSync
15
+ * check and silently abort the launch. Machine-side (the phone's `~` is
16
+ * meaningless here). Exported for tests. */
17
+ export function _expandTilde(p) {
18
+ if (p === "~")
19
+ return homedir();
20
+ if (p.startsWith("~/"))
21
+ return join(homedir(), p.slice(2));
22
+ return p;
23
+ }
24
+ /** Sanitize a tmux session name: keep it shell-safe and tmux-legal. */
25
+ function _safeTmuxName(name, fallback) {
26
+ const base = (name ?? "").trim() || fallback;
27
+ // tmux disallows `.` and `:` in session names; strip anything risky.
28
+ const clean = base
29
+ .replace(/[^A-Za-z0-9_-]+/g, "-")
30
+ .replace(/-{2,}/g, "-")
31
+ .replace(/^-+|-+$/g, "");
32
+ return clean.slice(0, 40) || "pi";
33
+ }
34
+ /** Build the argv for launching `pi` in `cwd` as a WINDOW of the shared tmux
35
+ * session `session` (one named session, a window per pi — clean `new-window`,
36
+ * NO keystrokes/prefix). First launch creates the detached session; later ones
37
+ * add a window. Array (never a shell string) so cwd/names can't inject.
38
+ * Exported for tests. */
39
+ export function _buildTmuxLaunchArgs(session, windowName, cwd, sessionExists) {
40
+ return sessionExists
41
+ ? ["new-window", "-t", session, "-n", windowName, "-c", cwd, "pi"]
42
+ : ["new-session", "-d", "-s", session, "-n", windowName, "-c", cwd, "pi"];
43
+ }
44
+ /** Sanitize a herdr agent name: must match [a-z][a-z0-9_-]{0,31}. */
45
+ function _safeHerdrName(name, fallback) {
46
+ const base = (name ?? "").trim().toLowerCase() || fallback;
47
+ let clean = base
48
+ .replace(/[^a-z0-9_-]+/g, "-")
49
+ .replace(/-{2,}/g, "-")
50
+ .replace(/^-+|-+$/g, "");
51
+ if (!/^[a-z]/.test(clean))
52
+ clean = `pi-${clean}`;
53
+ return clean.slice(0, 32).replace(/-+$/g, "") || "pi";
54
+ }
55
+ /** argv for creating a detached herdr workspace in `cwd` (JSON response).
56
+ * ARRAY (never a shell string) so cwd/label can't inject. Exported for tests. */
57
+ export function _buildHerdrWorkspaceArgs(label, cwd) {
58
+ return [
59
+ "workspace",
60
+ "create",
61
+ "--cwd",
62
+ cwd,
63
+ "--label",
64
+ label,
65
+ "--no-focus",
66
+ "--json",
67
+ ];
68
+ }
69
+ /** argv for exec-launching `pi` as a named herdr agent in an existing pane —
70
+ * herdr's canonical-executable launch (NOT keystroke injection). */
71
+ export function _buildHerdrAgentStartArgs(agentName, paneId) {
72
+ return ["agent", "start", agentName, "--kind", "pi", "--pane", paneId];
73
+ }
74
+ /** Extract `.result.root_pane.pane_id` from `herdr workspace create --json`. */
75
+ export function _herdrPaneIdFromCreate(stdout) {
76
+ try {
77
+ const j = JSON.parse(stdout);
78
+ const id = j.result?.root_pane?.pane_id;
79
+ return typeof id === "string" && id.length > 0 ? id : null;
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ /** Is a launch backend's binary present on PATH? A `--version` probe: a missing
86
+ * binary (ENOENT) sets `error`; anything that runs counts as present. */
87
+ function _backendAvailable(backend) {
88
+ try {
89
+ return (spawnSync(backend, ["--version"], {
90
+ stdio: "ignore",
91
+ timeout: 5_000,
92
+ }).error === undefined);
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ }
98
+ function _execFileCapture(cmd, args) {
99
+ return new Promise((resolve, reject) => {
100
+ execFile(cmd, args, { timeout: 15_000 }, (err, stdout) => {
101
+ if (err)
102
+ reject(err);
103
+ else
104
+ resolve(stdout);
105
+ });
106
+ });
107
+ }
108
+ /**
109
+ * herdr launch (clean exec, no keystrokes): create a detached workspace in
110
+ * `cwd`, then start `pi` as its agent via herdr's canonical-executable path
111
+ * (`agent start --kind pi`). Fire-and-forget — the launched pi joins the relay
112
+ * and the app attaches there; create/start errors are logged, not returned.
113
+ */
114
+ async function _launchHerdr(cwd, agentName) {
115
+ try {
116
+ const created = await _execFileCapture("herdr", _buildHerdrWorkspaceArgs(agentName, cwd));
117
+ const paneId = _herdrPaneIdFromCreate(created);
118
+ if (!paneId) {
119
+ envLog("herdr launch: no root_pane_id in `workspace create` output");
120
+ return;
121
+ }
122
+ await _execFileCapture("herdr", _buildHerdrAgentStartArgs(agentName, paneId));
123
+ envLog(`herdr launch: agent '${agentName}' started in pane ${paneId}`);
124
+ }
125
+ catch (error) {
126
+ envLog(`herdr launch failed: ${error instanceof Error ? error.message : String(error)}`);
127
+ }
128
+ }
129
+ /**
130
+ * Honor a `session_launch` request. The caller checked the per-cwd opt-in and
131
+ * picked `mode` from `launch.backend` (machine config). PTY-hosted backends
132
+ * exec `pi` cleanly (no keystrokes): `tmux` = detached `new-session … pi`;
133
+ * `herdr` = `workspace create` then `agent start --kind pi`. `rpc` is a
134
+ * fast-follow (stubbed). Returns null when the launch is initiated, else an
135
+ * error string.
136
+ */
137
+ export function _launchSession(mode, cwd, name) {
138
+ if (mode === "rpc")
139
+ return "launch mode 'rpc' is not supported yet";
140
+ if (mode !== "tmux" && mode !== "herdr") {
141
+ return `unknown launch mode '${mode}'`;
142
+ }
143
+ if (!existsSync(cwd) || !statSync(cwd).isDirectory()) {
144
+ return `cwd does not exist or is not a directory: ${cwd}`;
145
+ }
146
+ if (!_backendAvailable(mode)) {
147
+ return `launch backend '${mode}' is not installed`;
148
+ }
149
+ if (mode === "herdr") {
150
+ const agentName = _safeHerdrName(name, `pi-${basename(cwd) || "session"}`);
151
+ void _launchHerdr(cwd, agentName);
152
+ return null;
153
+ }
154
+ // One shared, named tmux session; each launch is a WINDOW in it (single
155
+ // attach point). Clean `new-window` via the CLI — never a prefix keystroke.
156
+ const session = _safeTmuxName(loadConfig().launch?.tmux_session, "un-bien");
157
+ const windowName = _safeTmuxName(name, `pi-${basename(cwd) || "session"}`);
158
+ const sessionExists = spawnSync("tmux", ["has-session", "-t", session], {
159
+ stdio: "ignore",
160
+ timeout: 5_000,
161
+ }).status === 0;
162
+ try {
163
+ const child = spawn("tmux", _buildTmuxLaunchArgs(session, windowName, cwd, sessionExists), { detached: true, stdio: "ignore" });
164
+ child.unref();
165
+ return null;
166
+ }
167
+ catch (error) {
168
+ return `tmux launch failed: ${error instanceof Error ? error.message : String(error)}`;
169
+ }
170
+ }
171
+ //# sourceMappingURL=launch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch.js","sourceRoot":"","sources":["../src/launch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export interface LauncherHandle {
2
+ readonly roomId: string;
3
+ readonly epk: string;
4
+ stop(): void;
5
+ }
6
+ export declare function startLauncher(): Promise<LauncherHandle>;
@@ -0,0 +1,188 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { hostname, homedir } from "node:os";
3
+ import { RelayClient } from "../transport/relay_client.js";
4
+ import { PlainPeerChannel } from "../transport/peer_channel.js";
5
+ import { getOrCreateEd25519Keypair } from "../pairing/storage.js";
6
+ import { roomIdForControl } from "../rooms.js";
7
+ import { _findKnownPeer } from "../pairing/peer_trust.js";
8
+ import { _launchSession, _expandTilde } from "../launch.js";
9
+ import { helloEnvelope, isEnvelopeFrame, } from "../session/rpc_envelope.js";
10
+ import { loadConfig, resolveRelayUrl } from "../config.js";
11
+ import { loadLocalConfig, effectiveAllowRemoteLaunch, } from "../session/local_config.js";
12
+ import { envLog } from "../session/debug_log.js";
13
+ /**
14
+ * Regime-2 machine-launcher core (pi-INDEPENDENT — no pi SDK). A lightweight
15
+ * mesh peer that lets a paired app reach a machine with NO live pi session:
16
+ * it joins the machine-level control room (roomIdForControl), advertises the
17
+ * `remote_launch` capability, and on a `session_launch` frame spawns a
18
+ * tmux/herdr window via the shared launch module. Owner-auth reuses the extension's
19
+ * exact gate (relay verifies the peer's key; _findKnownPeer checks peers.json).
20
+ *
21
+ * NOT a pi process: no transcript/rpc/panels, no pairing (pairing happens once
22
+ * via any session extension's QR — the launcher only trusts already-paired owners).
23
+ */
24
+ const RECONNECT_DELAY_MS = 3_000;
25
+ /** Caps the launcher daemon advertises: `remote_launch` gates the app's launch
26
+ * control; `is_daemon` marks the control room so the app filters it. */
27
+ const DAEMON_CAPS = ["remote_launch", "is_daemon"];
28
+ export async function startLauncher() {
29
+ const kp = await getOrCreateEd25519Keypair();
30
+ const epk = Buffer.from(kp.publicKey).toString("base64url");
31
+ const roomId = roomIdForControl(epk);
32
+ const resolution = resolveRelayUrl();
33
+ if (!resolution.url) {
34
+ throw new Error("un-bien launcher: no relay configured (set UNBIEN_RELAY or the `relay` config key)");
35
+ }
36
+ const relayUrl = resolution.url;
37
+ let stopped = false;
38
+ let relay = null;
39
+ const channels = new Map();
40
+ /** The machine's configured launch backend (rpc is a fast-follow). */
41
+ function configuredBackend() {
42
+ return loadConfig().launch?.backend === "herdr" ? "herdr" : "tmux";
43
+ }
44
+ /** Liveness: answer a stock `ping` with `pong`, mirroring the extension's
45
+ * transport-control handler (index.ts) so a health check works against an
46
+ * idle machine too. Distinct from the `presence_status` caps PULL — this is
47
+ * the plain are-you-there reply, no caps. */
48
+ function handleStockMessage(msg, sender) {
49
+ if (msg.type === "ping") {
50
+ sender.send({
51
+ type: "pong",
52
+ in_reply_to: msg.id,
53
+ });
54
+ }
55
+ }
56
+ /** Handle a ub-frame from an attached owner. `presence_status` is the
57
+ * DAEMON-SPECIFIC caps PULL (design 01M1813Q) — reply with caps + hostname +
58
+ * backend. `session_launch` mirrors the extension's _routeUnBienPlaneFrom handler:
59
+ * per-cwd opt-in, machine-config backend, clean exec (no keystrokes). */
60
+ function handleUbFrame(env, sender) {
61
+ if (env.ub === undefined)
62
+ return;
63
+ const frame = env.ub;
64
+ if (frame.type === "presence_status") {
65
+ sender.sendEnvelope({
66
+ ub: {
67
+ type: "presence_status",
68
+ caps: [...DAEMON_CAPS],
69
+ hostname: hostname(),
70
+ backend: configuredBackend(),
71
+ ...(typeof frame.id === "string" ? { in_reply_to: frame.id } : {}),
72
+ },
73
+ });
74
+ return;
75
+ }
76
+ if (frame.type !== "session_launch")
77
+ return;
78
+ const cwd = _expandTilde(typeof frame.cwd === "string" && frame.cwd.length > 0
79
+ ? frame.cwd
80
+ : process.cwd());
81
+ if (!effectiveAllowRemoteLaunch(loadLocalConfig(cwd))) {
82
+ envLog("launcher session_launch: remote launch disabled on this machine");
83
+ return;
84
+ }
85
+ const launchError = _launchSession(configuredBackend(), cwd, typeof frame.name === "string" ? frame.name : undefined);
86
+ if (launchError)
87
+ envLog(`launcher session_launch error: ${launchError}`);
88
+ }
89
+ async function gateAndAttach(r, peer, firstInner) {
90
+ const known = await _findKnownPeer(peer);
91
+ if (!known) {
92
+ // Relay-verified but not a paired owner (never paired / revoked). Signal
93
+ // so the app can react, mirroring the extension's unknown-peer reply.
94
+ try {
95
+ const errCt = Buffer.from(JSON.stringify({
96
+ type: "error",
97
+ code: "unknown_peer",
98
+ message: "Peer not paired — re-scan QR",
99
+ })).toString("base64");
100
+ r.send(JSON.stringify({ peer, ct: errCt }));
101
+ }
102
+ catch {
103
+ /* relay down — nothing to signal */
104
+ }
105
+ return;
106
+ }
107
+ if (channels.has(peer))
108
+ return;
109
+ const channel = new PlainPeerChannel(r, peer, roomId, (msg) => handleStockMessage(msg, channel), // liveness ping->pong
110
+ () => channels.delete(peer), (env) => handleUbFrame(env, channel));
111
+ channels.set(peer, channel);
112
+ // Advertise machine caps up front so the app enables its launch control for
113
+ // this control room. No sessionId — the launcher has no pi session.
114
+ channel.sendEnvelope(helloEnvelope([...DAEMON_CAPS]));
115
+ envLog(`launcher: owner ${peer.slice(0, 8)} (${known.name}) attached; caps sent`);
116
+ // The channel didn't see the line that triggered the attach — route it.
117
+ if (isEnvelopeFrame(firstInner)) {
118
+ handleUbFrame(firstInner, channel);
119
+ }
120
+ }
121
+ function onMsg(r, line) {
122
+ let outer;
123
+ try {
124
+ outer = JSON.parse(line);
125
+ }
126
+ catch {
127
+ return;
128
+ }
129
+ if (!outer.peer || !outer.ct)
130
+ return;
131
+ if (channels.has(outer.peer))
132
+ return; // its PlainPeerChannel handles routing
133
+ let inner;
134
+ try {
135
+ inner = JSON.parse(Buffer.from(outer.ct, "base64").toString("utf8"));
136
+ }
137
+ catch {
138
+ return;
139
+ }
140
+ if (!inner || typeof inner !== "object")
141
+ return;
142
+ void gateAndAttach(r, outer.peer, inner);
143
+ }
144
+ function scheduleReconnect() {
145
+ if (stopped)
146
+ return;
147
+ setTimeout(() => {
148
+ if (stopped)
149
+ return;
150
+ connectOnce().catch(() => scheduleReconnect());
151
+ }, RECONNECT_DELAY_MS);
152
+ }
153
+ async function connectOnce() {
154
+ const r = new RelayClient(relayUrl, kp);
155
+ relay = r;
156
+ r.on("message", (line) => onMsg(r, line));
157
+ r.on("close", () => {
158
+ channels.clear();
159
+ if (!stopped)
160
+ scheduleReconnect();
161
+ });
162
+ await r.connect({
163
+ roomId,
164
+ // caps ride room_meta so the app filters the control room from the announce.
165
+ roomMeta: { name: hostname(), cwd: homedir(), caps: [...DAEMON_CAPS] },
166
+ });
167
+ envLog(`launcher: connected to control room ${roomId} (epk ${epk.slice(0, 12)}…)`);
168
+ }
169
+ await connectOnce();
170
+ return {
171
+ roomId,
172
+ epk,
173
+ stop() {
174
+ stopped = true;
175
+ for (const ch of channels.values()) {
176
+ try {
177
+ ch.detach();
178
+ }
179
+ catch {
180
+ /* best-effort */
181
+ }
182
+ }
183
+ channels.clear();
184
+ relay?.close();
185
+ },
186
+ };
187
+ }
188
+ //# sourceMappingURL=launcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.js","sourceRoot":"","sources":["../../src/launcher/launcher.ts"],"names":[],"mappings":"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"}
@@ -4,5 +4,9 @@ export interface Ed25519Keypair {
4
4
  }
5
5
  /** Generates an Ed25519 keypair for relay challenge-response auth. */
6
6
  export declare function generateEd25519Keypair(): Ed25519Keypair;
7
+ /** Derives an Ed25519 keypair from its 32-byte seed (the secret key material),
8
+ * so a seed supplied via the UNBIEN_IDENTITY_SEED override round-trips to the
9
+ * same public key the relay routes on. */
10
+ export declare function ed25519KeypairFromSeed(seed: Uint8Array): Ed25519Keypair;
7
11
  export declare function ed25519Sign(sk: Uint8Array, msg: Uint8Array): Uint8Array;
8
12
  export declare function ed25519Verify(pk: Uint8Array, msg: Uint8Array, sig: Uint8Array): boolean;
@@ -13,6 +13,16 @@ export function generateEd25519Keypair() {
13
13
  const publicKey = ed.getPublicKey(secretKey);
14
14
  return { secretKey, publicKey: Buffer.from(publicKey) };
15
15
  }
16
+ /** Derives an Ed25519 keypair from its 32-byte seed (the secret key material),
17
+ * so a seed supplied via the UNBIEN_IDENTITY_SEED override round-trips to the
18
+ * same public key the relay routes on. */
19
+ export function ed25519KeypairFromSeed(seed) {
20
+ if (seed.length !== 32) {
21
+ throw new Error(`Ed25519 seed must be 32 bytes (got ${seed.length}).`);
22
+ }
23
+ const publicKey = ed.getPublicKey(seed);
24
+ return { secretKey: Buffer.from(seed), publicKey: Buffer.from(publicKey) };
25
+ }
16
26
  export function ed25519Sign(sk, msg) {
17
27
  return Buffer.from(ed.sign(msg, sk));
18
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/pairing/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAErC,8DAA8D;AAC7D,EAAE,CAAC,MAAkC,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAkB,EAAE,EAAE;IAC3E,MAAM,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,IAAI;QAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAClC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AACrC,CAAC,CAAC;AAOF,sEAAsE;AACtE,MAAM,UAAU,sBAAsB;IACpC,MAAM,SAAS,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IAC7C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAc,EAAE,GAAe;IACzD,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,EAAc,EACd,GAAe,EACf,GAAe;IAEf,OAAO,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;AACjC,CAAC"}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/pairing/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAErC,8DAA8D;AAC7D,EAAE,CAAC,MAAkC,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAkB,EAAE,EAAE;IAC3E,MAAM,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,IAAI;QAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAClC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AACrC,CAAC,CAAC;AAOF,sEAAsE;AACtE,MAAM,UAAU,sBAAsB;IACpC,MAAM,SAAS,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IAC7C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED;;2CAE2C;AAC3C,MAAM,UAAU,sBAAsB,CAAC,IAAgB;IACrD,IAAI,IAAI,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,sCAAsC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACxC,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAc,EAAE,GAAe;IACzD,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,EAAc,EACd,GAAe,EACf,GAAe;IAEf,OAAO,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type PeerRecord } from "./storage.js";
2
+ /**
3
+ * Owner-trust lookups shared by the extension (index.ts) and the regime-2 launcher
4
+ * daemon. The trust model: the RELAY authenticates a peer's Ed25519 key
5
+ * ownership (challenge-response), so an inbound `peer` id IS the owner's
6
+ * cryptographic identity; authorization is then "is this key in peers.json?".
7
+ * Revocation is expressed by REMOVING the record, so a peers.json match is
8
+ * itself the not-revoked check — there is no separate revoked flag to honor.
9
+ */
10
+ export interface InspectedPeerRecord {
11
+ readonly record: PeerRecord;
12
+ readonly rawHandle: string;
13
+ readonly runtimeKey: string | null;
14
+ }
15
+ export declare function _runtimeOwnerFingerprint(runtimeKey: string): string;
16
+ export declare function _inspectPeerRecord(record: unknown): InspectedPeerRecord | null;
17
+ /** Resolve a relay-verified inbound `peer` id to its paired PeerRecord, or null
18
+ * if it isn't a known owner (never paired, or revoked → removed from
19
+ * peers.json). This IS the owner-authorization gate. */
20
+ export declare function _findKnownPeer(appPeerIdStd: string): Promise<PeerRecord | null>;
@@ -0,0 +1,82 @@
1
+ import { createHash } from "node:crypto";
2
+ import { canonicalizeEd25519PublicKey, decodeEd25519PublicKey, publicKeyFingerprint, } from "../mesh/encoding.js";
3
+ import { listPeers } from "./storage.js";
4
+ /** Metadata-only fingerprint of an UNTRUSTED/unparseable owner handle — used in
5
+ * diagnostics so a malformed record never puts key bytes in a log line. */
6
+ function _rawOwnerFingerprint(rawValue) {
7
+ let fingerprintInput;
8
+ if (typeof rawValue === "string") {
9
+ fingerprintInput = rawValue;
10
+ }
11
+ else {
12
+ try {
13
+ const serialized = JSON.stringify(rawValue);
14
+ const type = rawValue === null ? "null" : typeof rawValue;
15
+ fingerprintInput = `${type}:${serialized ?? ""}`;
16
+ }
17
+ catch {
18
+ fingerprintInput = `${typeof rawValue}:unserializable`;
19
+ }
20
+ }
21
+ return createHash("sha256")
22
+ .update(fingerprintInput, "utf8")
23
+ .digest("hex")
24
+ .slice(0, 8);
25
+ }
26
+ export function _runtimeOwnerFingerprint(runtimeKey) {
27
+ try {
28
+ return publicKeyFingerprint(decodeEd25519PublicKey(runtimeKey, "Owner runtime key"));
29
+ }
30
+ catch {
31
+ // Relay authentication guarantees canonical keys in production. This
32
+ // fallback keeps diagnostics metadata-only at defensive/test boundaries.
33
+ return _rawOwnerFingerprint(runtimeKey);
34
+ }
35
+ }
36
+ export function _inspectPeerRecord(record) {
37
+ if (!record || typeof record !== "object") {
38
+ const fingerprint = _rawOwnerFingerprint(record);
39
+ console.warn(`[un-bien] event=invalid_owner_record owner_fp=${fingerprint}`);
40
+ return null;
41
+ }
42
+ const candidate = record;
43
+ const rawHandle = candidate.remote_epk;
44
+ if (typeof rawHandle !== "string") {
45
+ const fingerprint = _rawOwnerFingerprint(rawHandle);
46
+ console.warn(`[un-bien] event=invalid_owner_record owner_fp=${fingerprint}`);
47
+ return null;
48
+ }
49
+ const safeRecord = {
50
+ name: typeof candidate.name === "string" ? candidate.name : "Unknown Owner",
51
+ remote_epk: rawHandle,
52
+ paired_at: typeof candidate.paired_at === "string" ? candidate.paired_at : "",
53
+ };
54
+ try {
55
+ const runtimeKey = canonicalizeEd25519PublicKey(rawHandle, "stored Owner public key");
56
+ return { record: safeRecord, rawHandle, runtimeKey };
57
+ }
58
+ catch {
59
+ const fingerprint = _rawOwnerFingerprint(rawHandle);
60
+ console.warn(`[un-bien] event=invalid_owner_record owner_fp=${fingerprint}`);
61
+ return { record: safeRecord, rawHandle, runtimeKey: null };
62
+ }
63
+ }
64
+ /** Resolve a relay-verified inbound `peer` id to its paired PeerRecord, or null
65
+ * if it isn't a known owner (never paired, or revoked → removed from
66
+ * peers.json). This IS the owner-authorization gate. */
67
+ export async function _findKnownPeer(appPeerIdStd) {
68
+ let runtimeKey;
69
+ try {
70
+ runtimeKey = canonicalizeEd25519PublicKey(appPeerIdStd, "Relay Owner key");
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ for (const record of await listPeers()) {
76
+ const inspected = _inspectPeerRecord(record);
77
+ if (inspected?.runtimeKey === runtimeKey)
78
+ return inspected.record;
79
+ }
80
+ return null;
81
+ }
82
+ //# sourceMappingURL=peer_trust.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer_trust.js","sourceRoot":"","sources":["../../src/pairing/peer_trust.ts"],"names":[],"mappings":"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"}