@geohar/un-bien 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +726 -0
  3. package/dist/actions/handlers.d.ts +77 -0
  4. package/dist/actions/handlers.js +26 -0
  5. package/dist/actions/handlers.js.map +1 -0
  6. package/dist/actions/registry.d.ts +39 -0
  7. package/dist/actions/registry.js +62 -0
  8. package/dist/actions/registry.js.map +1 -0
  9. package/dist/bin/supervisord.d.ts +2 -0
  10. package/dist/bin/supervisord.js +86 -0
  11. package/dist/bin/supervisord.js.map +1 -0
  12. package/dist/config.d.ts +84 -0
  13. package/dist/config.js +115 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/daemon/client.d.ts +20 -0
  16. package/dist/daemon/client.js +131 -0
  17. package/dist/daemon/client.js.map +1 -0
  18. package/dist/daemon/control_protocol.d.ts +176 -0
  19. package/dist/daemon/control_protocol.js +63 -0
  20. package/dist/daemon/control_protocol.js.map +1 -0
  21. package/dist/daemon/cron_log.d.ts +45 -0
  22. package/dist/daemon/cron_log.js +71 -0
  23. package/dist/daemon/cron_log.js.map +1 -0
  24. package/dist/daemon/cron_registry.d.ts +80 -0
  25. package/dist/daemon/cron_registry.js +198 -0
  26. package/dist/daemon/cron_registry.js.map +1 -0
  27. package/dist/daemon/id.d.ts +24 -0
  28. package/dist/daemon/id.js +36 -0
  29. package/dist/daemon/id.js.map +1 -0
  30. package/dist/daemon/install.d.ts +170 -0
  31. package/dist/daemon/install.js +603 -0
  32. package/dist/daemon/install.js.map +1 -0
  33. package/dist/daemon/registry.d.ts +63 -0
  34. package/dist/daemon/registry.js +153 -0
  35. package/dist/daemon/registry.js.map +1 -0
  36. package/dist/daemon/rpc_child.d.ts +194 -0
  37. package/dist/daemon/rpc_child.js +419 -0
  38. package/dist/daemon/rpc_child.js.map +1 -0
  39. package/dist/daemon/supervisor.d.ts +93 -0
  40. package/dist/daemon/supervisor.js +666 -0
  41. package/dist/daemon/supervisor.js.map +1 -0
  42. package/dist/extension_ui_bridge.d.ts +26 -0
  43. package/dist/extension_ui_bridge.js +341 -0
  44. package/dist/extension_ui_bridge.js.map +1 -0
  45. package/dist/index.d.ts +197 -0
  46. package/dist/index.js +5750 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/mcp/mesh_result.d.ts +10 -0
  49. package/dist/mcp/mesh_result.js +42 -0
  50. package/dist/mcp/mesh_result.js.map +1 -0
  51. package/dist/mcp/mesh_server.d.ts +16 -0
  52. package/dist/mcp/mesh_server.js +373 -0
  53. package/dist/mcp/mesh_server.js.map +1 -0
  54. package/dist/mesh/canonical.d.ts +30 -0
  55. package/dist/mesh/canonical.js +63 -0
  56. package/dist/mesh/canonical.js.map +1 -0
  57. package/dist/mesh/client.d.ts +18 -0
  58. package/dist/mesh/client.js +127 -0
  59. package/dist/mesh/client.js.map +1 -0
  60. package/dist/mesh/encoding.d.ts +38 -0
  61. package/dist/mesh/encoding.js +217 -0
  62. package/dist/mesh/encoding.js.map +1 -0
  63. package/dist/mesh/self_revoke.d.ts +73 -0
  64. package/dist/mesh/self_revoke.js +407 -0
  65. package/dist/mesh/self_revoke.js.map +1 -0
  66. package/dist/mesh/siblings.d.ts +49 -0
  67. package/dist/mesh/siblings.js +159 -0
  68. package/dist/mesh/siblings.js.map +1 -0
  69. package/dist/mesh/types.d.ts +34 -0
  70. package/dist/mesh/types.js +11 -0
  71. package/dist/mesh/types.js.map +1 -0
  72. package/dist/mesh/verify.d.ts +17 -0
  73. package/dist/mesh/verify.js +86 -0
  74. package/dist/mesh/verify.js.map +1 -0
  75. package/dist/pairing/crypto.d.ts +8 -0
  76. package/dist/pairing/crypto.js +22 -0
  77. package/dist/pairing/crypto.js.map +1 -0
  78. package/dist/pairing/qr.d.ts +59 -0
  79. package/dist/pairing/qr.js +127 -0
  80. package/dist/pairing/qr.js.map +1 -0
  81. package/dist/pairing/storage.d.ts +120 -0
  82. package/dist/pairing/storage.js +614 -0
  83. package/dist/pairing/storage.js.map +1 -0
  84. package/dist/panel_bridge.d.ts +15 -0
  85. package/dist/panel_bridge.js +409 -0
  86. package/dist/panel_bridge.js.map +1 -0
  87. package/dist/paths.d.ts +39 -0
  88. package/dist/paths.js +51 -0
  89. package/dist/paths.js.map +1 -0
  90. package/dist/protocol/codec.d.ts +7 -0
  91. package/dist/protocol/codec.js +48 -0
  92. package/dist/protocol/codec.js.map +1 -0
  93. package/dist/protocol/types.d.ts +404 -0
  94. package/dist/protocol/types.js +2 -0
  95. package/dist/protocol/types.js.map +1 -0
  96. package/dist/rooms.d.ts +42 -0
  97. package/dist/rooms.js +78 -0
  98. package/dist/rooms.js.map +1 -0
  99. package/dist/session/bridge.d.ts +37 -0
  100. package/dist/session/bridge.js +134 -0
  101. package/dist/session/bridge.js.map +1 -0
  102. package/dist/session/broker.d.ts +189 -0
  103. package/dist/session/broker.js +527 -0
  104. package/dist/session/broker.js.map +1 -0
  105. package/dist/session/broker_remote.d.ts +74 -0
  106. package/dist/session/broker_remote.js +581 -0
  107. package/dist/session/broker_remote.js.map +1 -0
  108. package/dist/session/cwd_lock.d.ts +33 -0
  109. package/dist/session/cwd_lock.js +127 -0
  110. package/dist/session/cwd_lock.js.map +1 -0
  111. package/dist/session/debug_log.d.ts +3 -0
  112. package/dist/session/debug_log.js +32 -0
  113. package/dist/session/debug_log.js.map +1 -0
  114. package/dist/session/envelope.d.ts +33 -0
  115. package/dist/session/envelope.js +117 -0
  116. package/dist/session/envelope.js.map +1 -0
  117. package/dist/session/global_config.d.ts +33 -0
  118. package/dist/session/global_config.js +73 -0
  119. package/dist/session/global_config.js.map +1 -0
  120. package/dist/session/ipc.d.ts +27 -0
  121. package/dist/session/ipc.js +22 -0
  122. package/dist/session/ipc.js.map +1 -0
  123. package/dist/session/leader_election.d.ts +32 -0
  124. package/dist/session/leader_election.js +108 -0
  125. package/dist/session/leader_election.js.map +1 -0
  126. package/dist/session/local_config.d.ts +67 -0
  127. package/dist/session/local_config.js +193 -0
  128. package/dist/session/local_config.js.map +1 -0
  129. package/dist/session/mesh_node.d.ts +116 -0
  130. package/dist/session/mesh_node.js +596 -0
  131. package/dist/session/mesh_node.js.map +1 -0
  132. package/dist/session/peer.d.ts +139 -0
  133. package/dist/session/peer.js +474 -0
  134. package/dist/session/peer.js.map +1 -0
  135. package/dist/session/peer_inventory.d.ts +13 -0
  136. package/dist/session/peer_inventory.js +48 -0
  137. package/dist/session/peer_inventory.js.map +1 -0
  138. package/dist/session/peer_limits.d.ts +9 -0
  139. package/dist/session/peer_limits.js +24 -0
  140. package/dist/session/peer_limits.js.map +1 -0
  141. package/dist/session/rpc_envelope.d.ts +108 -0
  142. package/dist/session/rpc_envelope.js +221 -0
  143. package/dist/session/rpc_envelope.js.map +1 -0
  144. package/dist/session/rpc_inbound.d.ts +55 -0
  145. package/dist/session/rpc_inbound.js +136 -0
  146. package/dist/session/rpc_inbound.js.map +1 -0
  147. package/dist/session/setup_wizard.d.ts +39 -0
  148. package/dist/session/setup_wizard.js +63 -0
  149. package/dist/session/setup_wizard.js.map +1 -0
  150. package/dist/session/tools.d.ts +24 -0
  151. package/dist/session/tools.js +272 -0
  152. package/dist/session/tools.js.map +1 -0
  153. package/dist/session/wizard.d.ts +13 -0
  154. package/dist/session/wizard.js +20 -0
  155. package/dist/session/wizard.js.map +1 -0
  156. package/dist/transport/peer_channel.d.ts +52 -0
  157. package/dist/transport/peer_channel.js +145 -0
  158. package/dist/transport/peer_channel.js.map +1 -0
  159. package/dist/transport/pi_forward_client.d.ts +29 -0
  160. package/dist/transport/pi_forward_client.js +68 -0
  161. package/dist/transport/pi_forward_client.js.map +1 -0
  162. package/dist/transport/relay_client.d.ts +96 -0
  163. package/dist/transport/relay_client.js +210 -0
  164. package/dist/transport/relay_client.js.map +1 -0
  165. package/dist/ui/footer.d.ts +32 -0
  166. package/dist/ui/footer.js +34 -0
  167. package/dist/ui/footer.js.map +1 -0
  168. package/docs/daemon.md +289 -0
  169. package/package.json +92 -0
  170. package/service-templates/launchd.plist.template +35 -0
  171. package/service-templates/systemd.service.template +19 -0
  172. package/service-templates/task-launcher.vbs.template +10 -0
  173. package/service-templates/task-scheduler.xml.template +38 -0
  174. package/skills/agent-network/SKILL.md +310 -0
@@ -0,0 +1,127 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { roomIdFor } from "../rooms.js";
4
+ import { removeStaleSock, tryBind, tryConnect } from "./leader_election.js";
5
+ import { ipcAddress, usesNamedPipe } from "./ipc.js";
6
+ import { unbienStateHome } from "../paths.js";
7
+ /**
8
+ * Per-cwd singleton lock for `/unbien`. At most one Pi process per
9
+ * working directory may hold the lock; the second attempt is refused.
10
+ *
11
+ * Why a UDS bind instead of a PID lock file:
12
+ * - The OS releases the socket handle the instant the process dies, even
13
+ * on `kill -9` or hard crash. No stale-lock cleanup needed.
14
+ * - The next Pi to try in the same cwd detects the dead socket via
15
+ * `ECONNREFUSED` on `tryConnect`, unlinks the leftover file, and
16
+ * binds successfully. Fully self-healing.
17
+ * - Kernel-enforced — there is no race window between "check if held"
18
+ * and "claim", which an explicit PID file would have.
19
+ *
20
+ * Lock files live in `<root>/.pi/un-bien/locks/<roomId>.sock` (where `roomId`
21
+ * is `sha256(realpath(cwd))[:12]` and `<root>` is `$UNBIEN_HOME` or the
22
+ * home dir), NOT inside the cwd itself, to dodge:
23
+ * - The 104/108-char path-length limit on UDS sockets on macOS/Linux.
24
+ * - Symlinked cwds (realpath canonicalization happens in `roomIdFor`).
25
+ * - Read-only cwds (the home directory is always writable).
26
+ *
27
+ * Caller workflow:
28
+ * const lock = await acquireCwdLock(cwd);
29
+ * if (!lock.ok) { ui.notify("Já tem um agente rodando nessa pasta."); return; }
30
+ * // …run /unbien normally; lock auto-releases on process exit
31
+ */
32
+ /** Resolved at call time (not module load) so tests can redirect the lock
33
+ * dir away from the developer's real `~/.pi/un-bien/locks` via
34
+ * `UNBIEN_DIR` / `UNBIEN_HOME` — the shared override every site honors. */
35
+ function locksDir() {
36
+ return join(unbienStateHome(), "locks");
37
+ }
38
+ /**
39
+ * Lock id for an agent. Keyed by **(cwd, name)** so several agents can run in
40
+ * the SAME folder as long as their names differ — the per-folder singleton is
41
+ * now a per-(folder,name) singleton.
42
+ *
43
+ * plan/41: this is the SAME derivation as the App↔Pi `room_id` — `lockIdFor`
44
+ * delegates to the shared `roomIdFor(cwd, name)`. So the per-(folder,name) lock
45
+ * and the announced room stay in lockstep: a default/unnamed agent locks the
46
+ * legacy cwd id (and owns the legacy room); a custom or `#N`-suffixed name gets
47
+ * a name-scoped id for both. Symlinks canonicalize via `realpath` inside
48
+ * `roomIdFor`.
49
+ */
50
+ function lockIdFor(cwd, name) {
51
+ return roomIdFor(cwd, name);
52
+ }
53
+ /**
54
+ * Local-IPC address of the lock for a given (cwd, name). Pure helper (no IO).
55
+ * POSIX → a `.sock` file under `locksDir()`; Windows → a per-user named pipe.
56
+ */
57
+ export function lockPathFor(cwd, name) {
58
+ const id = lockIdFor(cwd, name);
59
+ return ipcAddress(`lock-${id}`, join(locksDir(), `${id}.sock`));
60
+ }
61
+ /** Back-compat alias: the cwd-only lock path (no name component). */
62
+ export function lockPathForCwd(cwd) {
63
+ return lockPathFor(cwd);
64
+ }
65
+ /**
66
+ * Attempts to acquire the cwd lock. Resolves with either:
67
+ * - `{ ok: true, release }` when we own it (server bound + retained).
68
+ * - `{ ok: false, lockPath }` when a live Pi already holds the lock.
69
+ *
70
+ * Self-healing path: if the prior holder crashed, the leftover socket file
71
+ * fails `tryConnect` (ECONNREFUSED). We unlink it and retry the bind — the
72
+ * second attempt then succeeds.
73
+ *
74
+ * The server holds no actual listener logic; its only job is to keep the
75
+ * UDS endpoint pinned by the OS. No incoming connection ever does anything
76
+ * useful — the next-Pi-attempt's `tryConnect` succeeding is the entire
77
+ * signal we care about.
78
+ */
79
+ export async function acquireCwdLock(cwd, name) {
80
+ const lockPath = lockPathFor(cwd, name);
81
+ // POSIX: the lock socket is a file under locksDir → ensure the dir exists.
82
+ // Windows: lockPath is a named pipe (`\\.\pipe\…`) — no parent dir to create.
83
+ if (!usesNamedPipe())
84
+ mkdirSync(dirname(lockPath), { recursive: true });
85
+ // First attempt: bind directly.
86
+ let server = await tryBind(lockPath);
87
+ if (server)
88
+ return _acquired(server, lockPath);
89
+ // Bind failed — check whether the existing socket has a live listener.
90
+ const probe = await tryConnect(lockPath);
91
+ if (probe) {
92
+ probe.destroy();
93
+ return { ok: false, lockPath };
94
+ }
95
+ // POSIX: a leftover stale `.sock` file blocks the bind → clean it + retry.
96
+ // Windows: a named pipe auto-disappears when its owner exits, so there is
97
+ // never a stale pipe to remove.
98
+ if (!usesNamedPipe())
99
+ removeStaleSock(lockPath);
100
+ server = await tryBind(lockPath);
101
+ if (server)
102
+ return _acquired(server, lockPath);
103
+ // Race: someone else bound between our unlink and retry. Treat as held.
104
+ return { ok: false, lockPath };
105
+ }
106
+ function _acquired(server, _lockPath) {
107
+ let released = false;
108
+ // Don't keep the event loop alive just to hold the socket — the Pi
109
+ // process has its own reasons to stay up (relay WS, broker, etc.).
110
+ // When those are gone, the OS will tear the socket down with us.
111
+ server.unref();
112
+ return {
113
+ ok: true,
114
+ release: () => {
115
+ if (released)
116
+ return;
117
+ released = true;
118
+ try {
119
+ server.close();
120
+ }
121
+ catch {
122
+ /* ignored */
123
+ }
124
+ },
125
+ };
126
+ }
127
+ //# sourceMappingURL=cwd_lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cwd_lock.js","sourceRoot":"","sources":["../../src/session/cwd_lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Append a timestamped line to `<state>/envelope-debug.log` when the
2
+ * `debug.envelope` config pref is set. Safe inside SDK callbacks. */
3
+ export declare function envLog(msg: string): void;
@@ -0,0 +1,32 @@
1
+ // File-based envelope debug log. The fork usually runs detached (no visible
2
+ // stderr), so gated diagnostics go to a file the user can `tail -f`.
3
+ // Enabled by the `debug.envelope` pref in the global config
4
+ // (`extensions/un-bien.json`) — NOT an env var, because the detached fork
5
+ // doesn't inherit a shell's env. Best-effort, never throws.
6
+ import { appendFileSync, mkdirSync } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
+ import { loadConfig } from "../config.js";
9
+ import { unbienStateHome } from "../paths.js";
10
+ const LOG_PATH = join(unbienStateHome(), "envelope-debug.log");
11
+ // Resolved once: the debug pref is a dev switch, not something that flips
12
+ // mid-process, so we read the config file a single time on first use.
13
+ let enabled;
14
+ function isEnabled() {
15
+ if (enabled === undefined)
16
+ enabled = loadConfig().debug?.envelope === true;
17
+ return enabled;
18
+ }
19
+ /** Append a timestamped line to `<state>/envelope-debug.log` when the
20
+ * `debug.envelope` config pref is set. Safe inside SDK callbacks. */
21
+ export function envLog(msg) {
22
+ if (!isEnabled())
23
+ return;
24
+ try {
25
+ mkdirSync(dirname(LOG_PATH), { recursive: true });
26
+ appendFileSync(LOG_PATH, `${new Date().toISOString()} ${msg}\n`);
27
+ }
28
+ catch {
29
+ /* best-effort */
30
+ }
31
+ }
32
+ //# sourceMappingURL=debug_log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug_log.js","sourceRoot":"","sources":["../../src/session/debug_log.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,qEAAqE;AACrE,4DAA4D;AAC5D,0EAA0E;AAC1E,4DAA4D;AAE5D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,EAAE,EAAE,oBAAoB,CAAC,CAAC;AAE/D,0EAA0E;AAC1E,sEAAsE;AACtE,IAAI,OAA4B,CAAC;AACjC,SAAS,SAAS;IAChB,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,GAAG,UAAU,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC3E,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;sEACsE;AACtE,MAAM,UAAU,MAAM,CAAC,GAAW;IAChC,IAAI,CAAC,SAAS,EAAE;QAAE,OAAO;IACzB,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,cAAc,CAAC,QAAQ,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,iBAAiB;IACnB,CAAC;AACH,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 5-field envelope for the agent-network local protocol (plano 19).
3
+ * Serialized as JSONL (one JSON object per line) over UDS streams.
4
+ */
5
+ export interface Envelope {
6
+ from: string;
7
+ to: string | string[];
8
+ id: string;
9
+ re: string | null;
10
+ body: unknown;
11
+ }
12
+ export declare const TRANSPORT_ERROR_REASONS: readonly ["offline", "not_authorized", "bad_envelope"];
13
+ export type TransportErrorReason = (typeof TRANSPORT_ERROR_REASONS)[number];
14
+ export interface TransportErrorBody {
15
+ type: "transport_error";
16
+ reason: TransportErrorReason;
17
+ }
18
+ export declare function isUuid(value: unknown): value is string;
19
+ export declare function hasTransportErrorType(value: unknown): boolean;
20
+ export declare function asTransportErrorBody(value: unknown): TransportErrorBody | null;
21
+ /**
22
+ * Generates a UUID v7 — time-ordered, monotonically increasing within the
23
+ * same millisecond. Format:
24
+ * <48-bit ts ms><4-bit ver=7><12-bit rand><2-bit var=10><62-bit rand>
25
+ */
26
+ export declare function uuidv7(): string;
27
+ export declare class EnvelopeError extends Error {
28
+ constructor(message: string);
29
+ }
30
+ export declare function serialize(env: Envelope): string;
31
+ export declare function parse(line: string): Envelope;
32
+ /** Convenience: builds an envelope with id auto-generated. */
33
+ export declare function envelope(from: string, to: string | string[], body: unknown, re?: string | null): Envelope;
@@ -0,0 +1,117 @@
1
+ import { randomBytes } from "node:crypto";
2
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3
+ export const TRANSPORT_ERROR_REASONS = [
4
+ "offline",
5
+ "not_authorized",
6
+ "bad_envelope",
7
+ ];
8
+ export function isUuid(value) {
9
+ return typeof value === "string" && UUID_RE.test(value);
10
+ }
11
+ export function hasTransportErrorType(value) {
12
+ return (value !== null &&
13
+ typeof value === "object" &&
14
+ !Array.isArray(value) &&
15
+ Object.hasOwn(value, "type") &&
16
+ value["type"] === "transport_error");
17
+ }
18
+ export function asTransportErrorBody(value) {
19
+ if (!hasTransportErrorType(value) ||
20
+ !Object.hasOwn(value, "reason")) {
21
+ return null;
22
+ }
23
+ const reason = value["reason"];
24
+ if (reason !== "offline" &&
25
+ reason !== "not_authorized" &&
26
+ reason !== "bad_envelope") {
27
+ return null;
28
+ }
29
+ return { type: "transport_error", reason };
30
+ }
31
+ /**
32
+ * Generates a UUID v7 — time-ordered, monotonically increasing within the
33
+ * same millisecond. Format:
34
+ * <48-bit ts ms><4-bit ver=7><12-bit rand><2-bit var=10><62-bit rand>
35
+ */
36
+ export function uuidv7() {
37
+ const ts = Date.now();
38
+ const rand = randomBytes(10);
39
+ // Encode 48-bit timestamp into bytes 0-5.
40
+ const buf = Buffer.alloc(16);
41
+ buf[0] = (ts / 2 ** 40) & 0xff;
42
+ buf[1] = (ts / 2 ** 32) & 0xff;
43
+ buf[2] = (ts / 2 ** 24) & 0xff;
44
+ buf[3] = (ts / 2 ** 16) & 0xff;
45
+ buf[4] = (ts / 2 ** 8) & 0xff;
46
+ buf[5] = ts & 0xff;
47
+ rand.copy(buf, 6);
48
+ // Set version (7) in upper nibble of byte 6.
49
+ buf[6] = (buf[6] & 0x0f) | 0x70;
50
+ // Set variant (10) in upper two bits of byte 8.
51
+ buf[8] = (buf[8] & 0x3f) | 0x80;
52
+ const hex = buf.toString("hex");
53
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
54
+ }
55
+ export class EnvelopeError extends Error {
56
+ constructor(message) {
57
+ super(message);
58
+ this.name = "EnvelopeError";
59
+ }
60
+ }
61
+ export function serialize(env) {
62
+ return JSON.stringify(env) + "\n";
63
+ }
64
+ export function parse(line) {
65
+ let raw;
66
+ try {
67
+ raw = JSON.parse(line);
68
+ }
69
+ catch (e) {
70
+ throw new EnvelopeError(`not JSON: ${e.message}`);
71
+ }
72
+ if (!raw || typeof raw !== "object") {
73
+ throw new EnvelopeError("not an object");
74
+ }
75
+ const o = raw;
76
+ if (typeof o["from"] !== "string" || o["from"].length === 0) {
77
+ throw new EnvelopeError("from must be non-empty string");
78
+ }
79
+ const to = o["to"];
80
+ if (typeof to !== "string" && !Array.isArray(to)) {
81
+ throw new EnvelopeError("to must be string or array");
82
+ }
83
+ if (typeof to === "string" && to.length === 0) {
84
+ throw new EnvelopeError("to must be non-empty");
85
+ }
86
+ if (Array.isArray(to)) {
87
+ if (to.length === 0)
88
+ throw new EnvelopeError("to[] must not be empty");
89
+ for (const t of to) {
90
+ if (typeof t !== "string" || t.length === 0) {
91
+ throw new EnvelopeError("to[] entries must be non-empty strings");
92
+ }
93
+ }
94
+ }
95
+ if (!isUuid(o["id"])) {
96
+ throw new EnvelopeError("id must be UUID");
97
+ }
98
+ const re = o["re"];
99
+ if (re !== null && !isUuid(re)) {
100
+ throw new EnvelopeError("re must be null or UUID");
101
+ }
102
+ if (!("body" in o)) {
103
+ throw new EnvelopeError("body required");
104
+ }
105
+ return {
106
+ from: o["from"],
107
+ to: to,
108
+ id: o["id"],
109
+ re: re,
110
+ body: o["body"],
111
+ };
112
+ }
113
+ /** Convenience: builds an envelope with id auto-generated. */
114
+ export function envelope(from, to, body, re = null) {
115
+ return { from, to, id: uuidv7(), re, body };
116
+ }
117
+ //# sourceMappingURL=envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/session/envelope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Fixed UDS session name. The local mesh is single per machine — every Pi
3
+ * process on the host shares this broker. Previous versions exposed
4
+ * multi-session support (named sessions, leave/rename/sessions commands);
5
+ * those were removed in the 2026-05-23 simplification because in practice
6
+ * every install converged on one session anyway and multi-session UX added
7
+ * friction without value.
8
+ */
9
+ export declare const LOCAL_SESSION_NAME = "local";
10
+ /** Ensures the new subdirs exist inside the existing ~/.pi/un-bien/. */
11
+ export declare function ensureGlobalDirs(): void;
12
+ /**
13
+ * Local-IPC address for a session's broker. POSIX → a `.sock` file under the
14
+ * session dir; Windows → a per-user named pipe (plan/40). The `net` API treats
15
+ * both the same; only the address string differs.
16
+ */
17
+ export declare function sessionSockPath(name: string): string;
18
+ /** Path to the audit log for a named session. */
19
+ export declare function sessionAuditPath(name: string): string;
20
+ /** Path to the session metadata JSON. */
21
+ export declare function sessionMetaPath(name: string): string;
22
+ export declare function sessionsDir(): string;
23
+ export declare function skillsDir(): string;
24
+ /** Lists discovered session names from disk. */
25
+ export declare function listSessions(): string[];
26
+ /**
27
+ * Heuristic: a session has an existing broker socket FILE (POSIX only). On
28
+ * Windows the broker is a named pipe with no file to stat, so this returns
29
+ * false — the authoritative liveness check there is a connect-probe
30
+ * (`leader_election.tryConnect` / `client.supervisorOnline`), not this. Only
31
+ * legacy `session/wizard.ts` consumes this.
32
+ */
33
+ export declare function sessionHasSock(name: string): boolean;
@@ -0,0 +1,73 @@
1
+ import { existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { ipcAddress, usesNamedPipe } from "./ipc.js";
4
+ import { unbienStateHome } from "../paths.js";
5
+ const HOME_PI_REMOTE = unbienStateHome();
6
+ const SESSIONS_DIR = join(HOME_PI_REMOTE, "sessions");
7
+ const SKILLS_DIR = join(HOME_PI_REMOTE, "skills");
8
+ /**
9
+ * Fixed UDS session name. The local mesh is single per machine — every Pi
10
+ * process on the host shares this broker. Previous versions exposed
11
+ * multi-session support (named sessions, leave/rename/sessions commands);
12
+ * those were removed in the 2026-05-23 simplification because in practice
13
+ * every install converged on one session anyway and multi-session UX added
14
+ * friction without value.
15
+ */
16
+ export const LOCAL_SESSION_NAME = "local";
17
+ /** Ensures the new subdirs exist inside the existing ~/.pi/un-bien/. */
18
+ export function ensureGlobalDirs() {
19
+ mkdirSync(SESSIONS_DIR, { recursive: true });
20
+ mkdirSync(SKILLS_DIR, { recursive: true });
21
+ }
22
+ /**
23
+ * Local-IPC address for a session's broker. POSIX → a `.sock` file under the
24
+ * session dir; Windows → a per-user named pipe (plan/40). The `net` API treats
25
+ * both the same; only the address string differs.
26
+ */
27
+ export function sessionSockPath(name) {
28
+ return ipcAddress(`broker-${name}`, join(SESSIONS_DIR, name, "broker.sock"));
29
+ }
30
+ /** Path to the audit log for a named session. */
31
+ export function sessionAuditPath(name) {
32
+ return join(SESSIONS_DIR, name, "audit.jsonl");
33
+ }
34
+ /** Path to the session metadata JSON. */
35
+ export function sessionMetaPath(name) {
36
+ return join(SESSIONS_DIR, name, "session.json");
37
+ }
38
+ export function sessionsDir() {
39
+ return SESSIONS_DIR;
40
+ }
41
+ export function skillsDir() {
42
+ return SKILLS_DIR;
43
+ }
44
+ /** Lists discovered session names from disk. */
45
+ export function listSessions() {
46
+ ensureGlobalDirs();
47
+ try {
48
+ return readdirSync(SESSIONS_DIR).filter((entry) => {
49
+ try {
50
+ return statSync(join(SESSIONS_DIR, entry)).isDirectory();
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ });
56
+ }
57
+ catch {
58
+ return [];
59
+ }
60
+ }
61
+ /**
62
+ * Heuristic: a session has an existing broker socket FILE (POSIX only). On
63
+ * Windows the broker is a named pipe with no file to stat, so this returns
64
+ * false — the authoritative liveness check there is a connect-probe
65
+ * (`leader_election.tryConnect` / `client.supervisorOnline`), not this. Only
66
+ * legacy `session/wizard.ts` consumes this.
67
+ */
68
+ export function sessionHasSock(name) {
69
+ if (usesNamedPipe())
70
+ return false;
71
+ return existsSync(sessionSockPath(name));
72
+ }
73
+ //# sourceMappingURL=global_config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global_config.js","sourceRoot":"","sources":["../../src/session/global_config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,cAAc,GAAG,eAAe,EAAE,CAAC;AACzC,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;AACtD,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;AAClD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAE1C,wEAAwE;AACxE,MAAM,UAAU,gBAAgB;IAC9B,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,UAAU,CAAC,UAAU,IAAI,EAAE,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;AACjD,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,YAAY;IAC1B,gBAAgB,EAAE,CAAC;IACnB,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YAChD,IAAI,CAAC;gBACH,OAAO,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YAC3D,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,aAAa,EAAE;QAAE,OAAO,KAAK,CAAC;IAClC,OAAO,UAAU,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Cross-platform local-IPC address resolution (plan/40).
3
+ *
4
+ * Node's `net` implements local IPC differently per OS:
5
+ * - **POSIX**: a filesystem Unix-domain socket — a `.sock` path under
6
+ * `~/.pi/un-bien/`. The file lingers if the owner crashes (stale-socket
7
+ * cleanup via `unlink` matters).
8
+ * - **Windows**: a **named pipe** (`\\.\pipe\<name>`). There is NO file —
9
+ * `existsSync`/`unlink` don't apply, and the pipe vanishes automatically
10
+ * when the owning process exits (so there's never a stale pipe to clean).
11
+ * Pipes are machine-global, so the name embeds the user to avoid collisions
12
+ * between two accounts on the same host.
13
+ *
14
+ * The `net` API itself (`createServer().listen(addr)`, `createConnection({ path:
15
+ * addr })`, framing) is identical — only the address STRING and the lifecycle
16
+ * (skip file cleanup on Windows) change. `platform`/`user` are injectable so
17
+ * tests can exercise the win32 branch on a POSIX dev host.
18
+ */
19
+ export type Plat = NodeJS.Platform;
20
+ /** True when local IPC uses named pipes (no socket files to manage). */
21
+ export declare function usesNamedPipe(plat?: Plat): boolean;
22
+ /**
23
+ * Resolve a local-IPC address. On Windows returns a per-user named pipe
24
+ * (`\\.\pipe\un-bien-<suffix>-<user>`); on POSIX returns `filePath` (the
25
+ * filesystem UDS path) unchanged.
26
+ */
27
+ export declare function ipcAddress(suffix: string, filePath: string, plat?: Plat, user?: string): string;
@@ -0,0 +1,22 @@
1
+ import { platform as osPlatform, userInfo } from "node:os";
2
+ /** True when local IPC uses named pipes (no socket files to manage). */
3
+ export function usesNamedPipe(plat = osPlatform()) {
4
+ return plat === "win32";
5
+ }
6
+ /** Keep a name component safe for a Windows pipe path. */
7
+ function safe(s) {
8
+ return s.replace(/[^A-Za-z0-9_.-]/g, "_");
9
+ }
10
+ /**
11
+ * Resolve a local-IPC address. On Windows returns a per-user named pipe
12
+ * (`\\.\pipe\un-bien-<suffix>-<user>`); on POSIX returns `filePath` (the
13
+ * filesystem UDS path) unchanged.
14
+ */
15
+ export function ipcAddress(suffix, filePath, plat = osPlatform(), user) {
16
+ if (plat === "win32") {
17
+ const u = safe((user ?? userInfo().username) || "user");
18
+ return `\\\\.\\pipe\\un-bien-${safe(suffix)}-${u}`;
19
+ }
20
+ return filePath;
21
+ }
22
+ //# sourceMappingURL=ipc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipc.js","sourceRoot":"","sources":["../../src/session/ipc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAuB3D,wEAAwE;AACxE,MAAM,UAAU,aAAa,CAAC,OAAa,UAAU,EAAE;IACrD,OAAO,IAAI,KAAK,OAAO,CAAC;AAC1B,CAAC;AAED,0DAA0D;AAC1D,SAAS,IAAI,CAAC,CAAS;IACrB,OAAO,CAAC,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,MAAc,EACd,QAAgB,EAChB,OAAa,UAAU,EAAE,EACzB,IAAa;IAEb,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC;QACxD,OAAO,wBAAwB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;IACrD,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { type Server, type Socket } from "node:net";
2
+ export type ElectionResult = {
3
+ role: "leader";
4
+ server: Server;
5
+ } | {
6
+ role: "follower";
7
+ socket: Socket;
8
+ };
9
+ /**
10
+ * UDS-based leader election. Tries to connect to `sockPath`; on failure, tries
11
+ * to bind. If both lose the race (EADDRINUSE on bind, ECONNREFUSED on connect),
12
+ * cleans up stale sock file and retries with jittered backoff.
13
+ *
14
+ * Returns the role + the live handle (server for leader, socket for follower).
15
+ */
16
+ export declare function joinOrLead(sockPath: string): Promise<ElectionResult>;
17
+ /**
18
+ * Probes a UDS path: if a live listener responds, returns the connected
19
+ * socket; otherwise returns null (timeout or ECONNREFUSED). Exported so
20
+ * the cwd-lock primitive can reuse it without duplicating the OS dance.
21
+ */
22
+ export declare function tryConnect(sockPath: string): Promise<Socket | null>;
23
+ /**
24
+ * Attempts to bind a UDS server on `sockPath`. Returns the live server on
25
+ * success, null when the path is already held (EADDRINUSE) or any other
26
+ * bind error. Caller is responsible for `unlink`ing a stale sock file
27
+ * before retrying.
28
+ */
29
+ export declare function tryBind(sockPath: string): Promise<Server | null>;
30
+ /** Unlinks the sock file if it exists and is actually a socket. No-op
31
+ * otherwise. Used to clear stale sockets left behind by a crashed peer. */
32
+ export declare function removeStaleSock(sockPath: string): void;
@@ -0,0 +1,108 @@
1
+ import { createConnection, createServer, } from "node:net";
2
+ import { existsSync, lstatSync, unlinkSync } from "node:fs";
3
+ import { setTimeout as delay } from "node:timers/promises";
4
+ import { usesNamedPipe } from "./ipc.js";
5
+ const MAX_ATTEMPTS = 20;
6
+ const BASE_BACKOFF_MS = 30;
7
+ const JITTER_MS = 70;
8
+ const PROBE_TIMEOUT_MS = 500;
9
+ /**
10
+ * UDS-based leader election. Tries to connect to `sockPath`; on failure, tries
11
+ * to bind. If both lose the race (EADDRINUSE on bind, ECONNREFUSED on connect),
12
+ * cleans up stale sock file and retries with jittered backoff.
13
+ *
14
+ * Returns the role + the live handle (server for leader, socket for follower).
15
+ */
16
+ export async function joinOrLead(sockPath) {
17
+ const pipe = usesNamedPipe();
18
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
19
+ // Windows named pipes have no file to `existsSync`; always connect-probe
20
+ // first and never `unlink` (the pipe auto-disappears when its owner exits).
21
+ // POSIX keeps the existsSync fast-path + stale-socket cleanup.
22
+ if (pipe || existsSync(sockPath)) {
23
+ const sock = await _tryConnect(sockPath);
24
+ if (sock)
25
+ return { role: "follower", socket: sock };
26
+ if (!pipe)
27
+ _removeStaleSock(sockPath);
28
+ }
29
+ const server = await _tryBind(sockPath);
30
+ if (server)
31
+ return { role: "leader", server };
32
+ await delay(BASE_BACKOFF_MS + Math.random() * JITTER_MS);
33
+ }
34
+ throw new Error(`leader election failed after ${MAX_ATTEMPTS} attempts: ${sockPath}`);
35
+ }
36
+ /**
37
+ * Probes a UDS path: if a live listener responds, returns the connected
38
+ * socket; otherwise returns null (timeout or ECONNREFUSED). Exported so
39
+ * the cwd-lock primitive can reuse it without duplicating the OS dance.
40
+ */
41
+ export function tryConnect(sockPath) {
42
+ return _tryConnect(sockPath);
43
+ }
44
+ /**
45
+ * Attempts to bind a UDS server on `sockPath`. Returns the live server on
46
+ * success, null when the path is already held (EADDRINUSE) or any other
47
+ * bind error. Caller is responsible for `unlink`ing a stale sock file
48
+ * before retrying.
49
+ */
50
+ export function tryBind(sockPath) {
51
+ return _tryBind(sockPath);
52
+ }
53
+ /** Unlinks the sock file if it exists and is actually a socket. No-op
54
+ * otherwise. Used to clear stale sockets left behind by a crashed peer. */
55
+ export function removeStaleSock(sockPath) {
56
+ _removeStaleSock(sockPath);
57
+ }
58
+ function _tryConnect(sockPath) {
59
+ return new Promise((resolve) => {
60
+ const sock = createConnection({ path: sockPath });
61
+ let settled = false;
62
+ const settle = (val) => {
63
+ if (settled)
64
+ return;
65
+ settled = true;
66
+ clearTimeout(timer);
67
+ if (!val)
68
+ sock.destroy();
69
+ resolve(val);
70
+ };
71
+ const timer = setTimeout(() => settle(null), PROBE_TIMEOUT_MS);
72
+ sock.once("connect", () => settle(sock));
73
+ sock.once("error", () => settle(null));
74
+ });
75
+ }
76
+ function _tryBind(sockPath) {
77
+ return new Promise((resolve) => {
78
+ const server = createServer();
79
+ let settled = false;
80
+ const settle = (val) => {
81
+ if (settled)
82
+ return;
83
+ settled = true;
84
+ if (!val) {
85
+ try {
86
+ server.close();
87
+ }
88
+ catch {
89
+ /* ignored */
90
+ }
91
+ }
92
+ resolve(val);
93
+ };
94
+ server.once("error", () => settle(null));
95
+ server.listen(sockPath, () => settle(server));
96
+ });
97
+ }
98
+ function _removeStaleSock(sockPath) {
99
+ try {
100
+ const stat = lstatSync(sockPath);
101
+ if (stat.isSocket())
102
+ unlinkSync(sockPath);
103
+ }
104
+ catch {
105
+ // ENOENT or not-a-socket — nothing to clean up.
106
+ }
107
+ }
108
+ //# sourceMappingURL=leader_election.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leader_election.js","sourceRoot":"","sources":["../../src/session/leader_election.ts"],"names":[],"mappings":"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"}