tesseract-agent 0.11.0 → 0.11.2

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.
@@ -79502,7 +79502,7 @@ function alphabet(letters) {
79502
79502
  };
79503
79503
  }
79504
79504
  // @__NO_SIDE_EFFECTS__
79505
- function join68(separator = "") {
79505
+ function join67(separator = "") {
79506
79506
  astr("join", separator);
79507
79507
  return {
79508
79508
  encode: (from) => {
@@ -79707,7 +79707,7 @@ var init_esm = __esm({
79707
79707
  convertRadix2,
79708
79708
  radix,
79709
79709
  radix2,
79710
- join: join68,
79710
+ join: join67,
79711
79711
  padding
79712
79712
  };
79713
79713
  }
@@ -81254,7 +81254,7 @@ var mesh_ca_exports = {};
81254
81254
  __export(mesh_ca_exports, {
81255
81255
  netCaCommand: () => netCaCommand
81256
81256
  });
81257
- import { chmodSync as chmodSync8, existsSync as existsSync48, mkdirSync as mkdirSync45, readFileSync as readFileSync53, writeFileSync as writeFileSync46 } from "node:fs";
81257
+ import { chmodSync as chmodSync8, existsSync as existsSync49, mkdirSync as mkdirSync45, readFileSync as readFileSync54, writeFileSync as writeFileSync46 } from "node:fs";
81258
81258
  import { join as join87, resolve as resolve13 } from "node:path";
81259
81259
  import { createInterface as createInterface2 } from "node:readline";
81260
81260
  function die(msg) {
@@ -81302,12 +81302,12 @@ function ensureDir(p) {
81302
81302
  async function openCa(dir2) {
81303
81303
  const gPath = join87(dir2, "genesis.bin");
81304
81304
  const sPath = join87(dir2, "ca.sealed");
81305
- if (!existsSync48(gPath) || !existsSync48(sPath)) {
81305
+ if (!existsSync49(gPath) || !existsSync49(sPath)) {
81306
81306
  die(`No mesh CA in ${dir2}. Run: tesseract-agent net init --org NAME --ca-dir ${dir2}`);
81307
81307
  }
81308
- const genesisBytes = new Uint8Array(readFileSync53(gPath));
81308
+ const genesisBytes = new Uint8Array(readFileSync54(gPath));
81309
81309
  const genesis = decodeGenesis(genesisBytes);
81310
- const sealed = decodeRecoveryBundle(new Uint8Array(readFileSync53(sPath)));
81310
+ const sealed = decodeRecoveryBundle(new Uint8Array(readFileSync54(sPath)));
81311
81311
  const pass = await readPassphrase("CA passphrase: ");
81312
81312
  let seed;
81313
81313
  try {
@@ -81323,7 +81323,7 @@ async function netCaCommand(rest, flags) {
81323
81323
  if (sub === "init") {
81324
81324
  const org = typeof flags.org === "string" ? flags.org.trim() : "";
81325
81325
  if (!org) die("Usage: tesseract-agent net init --org NAME [--ca-dir DIR]");
81326
- if (existsSync48(join87(caDir, "ca.sealed"))) {
81326
+ if (existsSync49(join87(caDir, "ca.sealed"))) {
81327
81327
  die(`A mesh CA already exists in ${caDir}. Refusing to overwrite it.`);
81328
81328
  }
81329
81329
  const pass = await readPassphrase("New CA passphrase: ");
@@ -81481,7 +81481,7 @@ var init_mesh_ca = __esm({
81481
81481
 
81482
81482
  // src/cli.ts
81483
81483
  init_base_paths();
81484
- import { existsSync as existsSync49, mkdtempSync as mkdtempSync4, readFileSync as readFileSync55, rmSync as rmSync13 } from "node:fs";
81484
+ import { existsSync as existsSync50, mkdtempSync as mkdtempSync4, readFileSync as readFileSync56, rmSync as rmSync13 } from "node:fs";
81485
81485
  import { createHash as createHash9 } from "node:crypto";
81486
81486
  import { execFileSync as execFileSync6, spawn as spawn14 } from "node:child_process";
81487
81487
  import { fileURLToPath as fileURLToPath8 } from "node:url";
@@ -109557,7 +109557,7 @@ async function startLocalServer(opts) {
109557
109557
 
109558
109558
  // ../../packages/host-core/dist/daemon-runtime.js
109559
109559
  init_base_paths();
109560
- import { readFileSync as readFileSync49, writeFileSync as writeFileSync40, existsSync as existsSync38, mkdirSync as mkdirSync41, unlinkSync as unlinkSync6, chmodSync as chmodSync7, statSync as statSync14 } from "node:fs";
109560
+ import { readFileSync as readFileSync49, writeFileSync as writeFileSync40, existsSync as existsSync39, mkdirSync as mkdirSync41, unlinkSync as unlinkSync6, chmodSync as chmodSync7, statSync as statSync14 } from "node:fs";
109561
109561
  import { execFileSync as execFileSync5 } from "node:child_process";
109562
109562
  import { join as join77 } from "node:path";
109563
109563
  import { randomBytes as randomBytes10 } from "node:crypto";
@@ -109819,16 +109819,455 @@ async function getPeerIdentityPub(opts) {
109819
109819
  // ../../packages/host-core/dist/mesh/node.js
109820
109820
  import { createSocket as createSocket2 } from "node:dgram";
109821
109821
  import { createRequire as createRequire4 } from "node:module";
109822
+ init_credentials();
109823
+
109824
+ // ../../packages/host-core/dist/mesh/bundle-gossip.js
109825
+ var BundleOfferLedger = class {
109826
+ #offered = /* @__PURE__ */ new Set();
109827
+ /**
109828
+ * Should we offer epoch `mine` to this peer, and record that we did?
109829
+ *
109830
+ * Returns false when we are not ahead (including equal — an offer would tell them
109831
+ * nothing), and false for a peer already offered THIS epoch. Keyed by epoch as well as
109832
+ * peer, so a newer bundle is offered afresh to someone we have already talked to.
109833
+ */
109834
+ claim(peerHex, peerEpoch, mine) {
109835
+ if (!(mine > peerEpoch))
109836
+ return false;
109837
+ const key2 = `${peerHex}:${mine}`;
109838
+ if (this.#offered.has(key2))
109839
+ return false;
109840
+ this.#offered.add(key2);
109841
+ return true;
109842
+ }
109843
+ /**
109844
+ * Forget everything, because we just adopted a newer bundle and are now the node that is
109845
+ * ahead. Without this the ledger would still be keyed to the epoch we no longer hold —
109846
+ * harmless for correctness (the key carries the epoch) but it would grow without bound on
109847
+ * a long-lived node, one entry per peer per epoch forever.
109848
+ */
109849
+ reset() {
109850
+ this.#offered.clear();
109851
+ }
109852
+ };
109853
+
109854
+ // ../../packages/host-core/dist/mesh/netd-engine.js
109855
+ init_dist6();
109856
+
109857
+ // ../../packages/host-core/dist/mesh/netd-client.js
109858
+ import { connect as connect2 } from "node:net";
109859
+ import { platform as platform3 } from "node:process";
109860
+ var NETD_WIRE_VERSION = 2;
109861
+ var MAX_LINE = 1024 * 1024;
109862
+ var TIMEOUT_MS3 = 15e3;
109863
+ function netdEndpoint() {
109864
+ if (platform3 === "win32")
109865
+ return "\\\\.\\pipe\\tesseract-netd";
109866
+ if (platform3 === "darwin")
109867
+ return "/var/run/tesseract-netd.sock";
109868
+ return "/run/tesseract-netd.sock";
109869
+ }
109870
+ function endpointOverride() {
109871
+ return process.env.CCB_NETD_SOCKET ?? null;
109872
+ }
109873
+ function fail2(code, detail) {
109874
+ return { ok: false, code, detail };
109875
+ }
109876
+ async function call(line) {
109877
+ const endpoint2 = endpointOverride() ?? netdEndpoint();
109878
+ return new Promise((resolve16) => {
109879
+ let settled = false;
109880
+ const done = (r) => {
109881
+ if (settled)
109882
+ return;
109883
+ settled = true;
109884
+ try {
109885
+ sock.destroy();
109886
+ } catch {
109887
+ }
109888
+ resolve16(r);
109889
+ };
109890
+ let sock;
109891
+ try {
109892
+ sock = connect2(endpoint2);
109893
+ } catch (e) {
109894
+ return resolve16(fail2("not-installed", e instanceof Error ? e.message : String(e)));
109895
+ }
109896
+ const timer2 = setTimeout(() => {
109897
+ done(fail2("timeout", `no answer from the network helper in ${TIMEOUT_MS3}ms`));
109898
+ }, TIMEOUT_MS3);
109899
+ timer2.unref?.();
109900
+ sock.setTimeout(TIMEOUT_MS3);
109901
+ sock.on("timeout", () => {
109902
+ clearTimeout(timer2);
109903
+ done(fail2("timeout", "socket idle"));
109904
+ });
109905
+ sock.on("error", (e) => {
109906
+ clearTimeout(timer2);
109907
+ const absent = e.code === "ENOENT" || e.code === "ECONNREFUSED";
109908
+ done(fail2(absent ? "not-installed" : "io", e.message));
109909
+ });
109910
+ let buf = "";
109911
+ sock.on("data", (chunk) => {
109912
+ buf += chunk.toString("utf8");
109913
+ if (buf.length > MAX_LINE) {
109914
+ clearTimeout(timer2);
109915
+ done(fail2("protocol", "the helper sent an oversized line"));
109916
+ return;
109917
+ }
109918
+ const nl = buf.indexOf("\n");
109919
+ if (nl === -1)
109920
+ return;
109921
+ clearTimeout(timer2);
109922
+ done({ ok: true, line: buf.slice(0, nl) });
109923
+ });
109924
+ sock.on("close", () => {
109925
+ clearTimeout(timer2);
109926
+ done(fail2("protocol", "the helper closed the connection without answering"));
109927
+ });
109928
+ sock.on("connect", () => {
109929
+ sock.write(`${line}
109930
+ `);
109931
+ });
109932
+ });
109933
+ }
109934
+ function parse3(line) {
109935
+ let body;
109936
+ try {
109937
+ body = JSON.parse(line);
109938
+ } catch {
109939
+ return fail2("protocol", "the helper sent something that is not JSON");
109940
+ }
109941
+ if (typeof body !== "object" || body === null) {
109942
+ return fail2("protocol", "the helper sent something that is not an object");
109943
+ }
109944
+ const o = body;
109945
+ if (o.ok === false) {
109946
+ const code = typeof o.code === "string" ? o.code : "refused";
109947
+ const detail = typeof o.detail === "string" ? o.detail : "the helper refused";
109948
+ return fail2(code, detail);
109949
+ }
109950
+ if (o.ok !== true)
109951
+ return fail2("protocol", "the helper did not say whether it succeeded");
109952
+ return { ok: true, body: o };
109953
+ }
109954
+ async function request(line) {
109955
+ const r = await call(line);
109956
+ return r.ok ? parse3(r.line) : r;
109957
+ }
109958
+ function hex4(b) {
109959
+ return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
109960
+ }
109961
+ async function netdHello() {
109962
+ const r = await request(JSON.stringify({ op: "hello", wireVersion: NETD_WIRE_VERSION }));
109963
+ if (!r.ok)
109964
+ return r;
109965
+ const v = r.body.wireVersion;
109966
+ if (typeof v !== "number")
109967
+ return fail2("protocol", "the helper did not report a wire version");
109968
+ if (v !== NETD_WIRE_VERSION) {
109969
+ return fail2("wire-version", `the helper speaks ${v}, this build speaks ${NETD_WIRE_VERSION}`);
109970
+ }
109971
+ return { ok: true, wireVersion: v };
109972
+ }
109973
+ async function netdStatus() {
109974
+ const r = await request(JSON.stringify({ op: "status" }));
109975
+ if (!r.ok)
109976
+ return r;
109977
+ const meshes = Array.isArray(r.body.meshes) ? r.body.meshes.filter((m) => typeof m === "string") : [];
109978
+ const iface = typeof r.body.interface === "string" ? r.body.interface : null;
109979
+ return { ok: true, meshes, interface: iface };
109980
+ }
109981
+ async function netdUp(args) {
109982
+ const r = await request(JSON.stringify({
109983
+ op: "up",
109984
+ meshId: args.meshId,
109985
+ genesis: hex4(args.genesis),
109986
+ cert: hex4(args.cert),
109987
+ fullTunnel: args.fullTunnel === true,
109988
+ ...args.staticPrivate ? { staticPrivate: hex4(args.staticPrivate) } : {},
109989
+ ...args.bundle ? { bundle: hex4(args.bundle) } : {}
109990
+ }));
109991
+ return r.ok ? { ok: true } : r;
109992
+ }
109993
+ async function netdHint(args) {
109994
+ const r = await request(JSON.stringify({
109995
+ op: "hint",
109996
+ meshId: args.meshId,
109997
+ staticPub: hex4(args.staticPub),
109998
+ endpoints: args.endpoints,
109999
+ relayed: args.relayed === true
110000
+ }));
110001
+ return r.ok ? { ok: true } : r;
110002
+ }
110003
+ async function netdBundle(meshId, bundle) {
110004
+ const r = await request(JSON.stringify({ op: "bundle", meshId, bundle: hex4(bundle) }));
110005
+ return r.ok ? { ok: true } : r;
110006
+ }
110007
+ async function netdSetExitNode(meshId, staticPub) {
110008
+ const r = await request(JSON.stringify({
110009
+ op: "setExitNode",
110010
+ meshId,
110011
+ staticPub: staticPub ? hex4(staticPub) : null
110012
+ }));
110013
+ return r.ok ? { ok: true } : r;
110014
+ }
110015
+ async function netdNodeStatus(meshId) {
110016
+ const r = await request(JSON.stringify({ op: "nodeStatus", meshId }));
110017
+ if (!r.ok)
110018
+ return r;
110019
+ const b = r.body;
110020
+ const num2 = (k) => typeof b[k] === "number" ? b[k] : 0;
110021
+ const strs = (k) => Array.isArray(b[k]) ? b[k].filter((x) => typeof x === "string") : [];
110022
+ if (b.node !== true)
110023
+ return fail2("protocol", "the helper did not answer with a node status");
110024
+ const pubHex = typeof b.localStaticPub === "string" ? b.localStaticPub : "";
110025
+ return {
110026
+ ok: true,
110027
+ node: {
110028
+ running: b.running === true,
110029
+ port: num2("port"),
110030
+ peers: num2("peers"),
110031
+ established: strs("established"),
110032
+ relayed: strs("relayed"),
110033
+ policyEpoch: num2("policyEpoch"),
110034
+ revoked: b.revoked === true,
110035
+ lastError: typeof b.lastError === "string" ? b.lastError : null,
110036
+ localStaticPub: Uint8Array.from(Buffer.from(pubHex, "hex")),
110037
+ meter: {
110038
+ txDirectBytes: num2("txDirectBytes"),
110039
+ txDirectPackets: num2("txDirectPackets"),
110040
+ txRelayedBytes: num2("txRelayedBytes"),
110041
+ txRelayedPackets: num2("txRelayedPackets"),
110042
+ rxDirectBytes: num2("rxDirectBytes"),
110043
+ rxDirectPackets: num2("rxDirectPackets"),
110044
+ rxRelayedBytes: num2("rxRelayedBytes"),
110045
+ rxRelayedPackets: num2("rxRelayedPackets"),
110046
+ forwardedBytes: num2("forwardedBytes"),
110047
+ forwardedPackets: num2("forwardedPackets")
110048
+ }
110049
+ }
110050
+ };
110051
+ }
110052
+ async function netdDown(meshId) {
110053
+ const r = await request(JSON.stringify({ op: "down", meshId }));
110054
+ return r.ok ? { ok: true } : r;
110055
+ }
110056
+ async function netdAvailable() {
110057
+ return (await netdHello()).ok;
110058
+ }
110059
+
110060
+ // ../../packages/host-core/dist/mesh/netd-engine.js
110061
+ var NETD_POLL_MS = 1e3;
110062
+ var NetdEngine = class {
110063
+ #meshDir;
110064
+ #meshId;
110065
+ #localStaticPub;
110066
+ #hooks;
110067
+ #snapshot = null;
110068
+ #pendingEndpoints = /* @__PURE__ */ new Map();
110069
+ #events = [];
110070
+ #seenEstablished = /* @__PURE__ */ new Set();
110071
+ #seenRelayed = /* @__PURE__ */ new Set();
110072
+ #timer = null;
110073
+ #announced = false;
110074
+ #lastError = null;
110075
+ #stopped = false;
110076
+ constructor(meshDir2, cert, hooks2) {
110077
+ this.#meshDir = meshDir2;
110078
+ this.#hooks = hooks2;
110079
+ const d = decodeCert(cert).details;
110080
+ this.#meshId = hex3(d.genesisFp);
110081
+ this.#localStaticPub = Buffer.from(d.staticPub);
110082
+ this.#timer = setInterval(() => void this.#poll(), NETD_POLL_MS);
110083
+ this.#timer.unref?.();
110084
+ void this.#poll();
110085
+ }
110086
+ /** Read netd's view now rather than waiting for the timer. Exposed for tests and for
110087
+ * the moment right after `up` so status does not lag a full interval. */
110088
+ async refresh() {
110089
+ await this.#poll();
110090
+ }
110091
+ async #poll() {
110092
+ if (this.#stopped)
110093
+ return;
110094
+ const r = await netdNodeStatus(this.#meshDir);
110095
+ if (!r.ok) {
110096
+ if (r.code !== "not-running" && r.code !== "not-installed") {
110097
+ this.#lastError = `netd: ${r.detail}`;
110098
+ }
110099
+ return;
110100
+ }
110101
+ const s = r.node;
110102
+ const prev = this.#snapshot;
110103
+ this.#snapshot = s;
110104
+ if (s.lastError)
110105
+ this.#lastError = s.lastError;
110106
+ for (const k of s.established) {
110107
+ if (!this.#seenEstablished.has(k)) {
110108
+ this.#seenEstablished.add(k);
110109
+ this.#events.push({ kind: "peerEstablished", staticPub: Buffer.from(k, "hex") });
110110
+ }
110111
+ }
110112
+ const relayedNow = new Set(s.relayed);
110113
+ for (const k of relayedNow) {
110114
+ if (!this.#seenRelayed.has(k)) {
110115
+ this.#seenRelayed.add(k);
110116
+ this.#events.push({ kind: "pathChanged", staticPub: Buffer.from(k, "hex"), detail: "relay" });
110117
+ }
110118
+ }
110119
+ for (const k of [...this.#seenRelayed]) {
110120
+ if (!relayedNow.has(k)) {
110121
+ this.#seenRelayed.delete(k);
110122
+ this.#events.push({ kind: "pathChanged", staticPub: Buffer.from(k, "hex"), detail: "direct" });
110123
+ }
110124
+ }
110125
+ if (s.running && s.port > 0 && !this.#announced) {
110126
+ this.#announced = true;
110127
+ this.#hooks.log?.(`mesh: node running in the network helper on UDP port ${s.port}`);
110128
+ this.#hooks.onUp(s.port);
110129
+ }
110130
+ void prev;
110131
+ }
110132
+ // ── MeshEngineNative ────────────────────────────────────────────────────────────
110133
+ setBundle(b, _nowMs) {
110134
+ void netdBundle(this.#meshDir, b).then((r) => {
110135
+ if (!r.ok)
110136
+ this.#lastError = `bundle: ${r.detail}`;
110137
+ });
110138
+ }
110139
+ addPeer(staticPub, _overlayV4, _overlayV6) {
110140
+ const k = staticPub.toString("hex");
110141
+ if (!this.#pendingEndpoints.has(k))
110142
+ this.#pendingEndpoints.set(k, { endpoints: [], relayed: false });
110143
+ }
110144
+ addPath(staticPub, endpoint2, relayed) {
110145
+ const k = staticPub.toString("hex");
110146
+ const p = this.#pendingEndpoints.get(k) ?? { endpoints: [], relayed: false };
110147
+ if (!p.endpoints.includes(endpoint2))
110148
+ p.endpoints.push(endpoint2);
110149
+ p.relayed = p.relayed || relayed;
110150
+ this.#pendingEndpoints.set(k, p);
110151
+ }
110152
+ startHandshake(staticPub, _nowMs) {
110153
+ const k = staticPub.toString("hex");
110154
+ const p = this.#pendingEndpoints.get(k) ?? { endpoints: [], relayed: false };
110155
+ this.#pendingEndpoints.delete(k);
110156
+ void netdHint({ meshId: this.#meshDir, staticPub, endpoints: p.endpoints, relayed: p.relayed }).then((r) => {
110157
+ if (!r.ok)
110158
+ this.#lastError = `hint: ${r.detail}`;
110159
+ });
110160
+ }
110161
+ /** No UDP here — netd owns the socket. Never called in practice; kept for the interface. */
110162
+ injectUdp(_pkt, _from, _nowMs) {
110163
+ }
110164
+ tick(_nowMs) {
110165
+ }
110166
+ pollSend() {
110167
+ return null;
110168
+ }
110169
+ pollEvent() {
110170
+ return this.#events.shift() ?? null;
110171
+ }
110172
+ meter() {
110173
+ const m = this.#snapshot?.meter;
110174
+ const tx = (m?.txDirectBytes ?? 0) + (m?.txRelayedBytes ?? 0);
110175
+ return {
110176
+ txDirectBytes: m?.txDirectBytes ?? 0,
110177
+ txRelayedBytes: m?.txRelayedBytes ?? 0,
110178
+ rxDirectBytes: m?.rxDirectBytes ?? 0,
110179
+ rxRelayedBytes: m?.rxRelayedBytes ?? 0,
110180
+ forwardedBytes: m?.forwardedBytes ?? 0,
110181
+ forwardedPackets: m?.forwardedPackets ?? 0,
110182
+ relayedTxFraction: tx === 0 ? 0 : (m?.txRelayedBytes ?? 0) / tx
110183
+ };
110184
+ }
110185
+ localStaticPub() {
110186
+ return this.#localStaticPub;
110187
+ }
110188
+ meshId() {
110189
+ return this.#meshId;
110190
+ }
110191
+ policyEpoch() {
110192
+ return this.#snapshot?.policyEpoch ?? 0;
110193
+ }
110194
+ selfRevoked() {
110195
+ return this.#snapshot?.revoked === true;
110196
+ }
110197
+ peerCount() {
110198
+ return this.#snapshot?.peers ?? 0;
110199
+ }
110200
+ setExitNode(staticPub) {
110201
+ void netdSetExitNode(this.#meshDir, staticPub).then((r) => {
110202
+ if (!r.ok)
110203
+ this.#lastError = `exit node: ${r.detail}`;
110204
+ });
110205
+ }
110206
+ /** Is netd running the node right now, and on which port? */
110207
+ get running() {
110208
+ return this.#snapshot?.running === true;
110209
+ }
110210
+ get port() {
110211
+ return this.#snapshot && this.#snapshot.port > 0 ? this.#snapshot.port : null;
110212
+ }
110213
+ get lastError() {
110214
+ return this.#lastError;
110215
+ }
110216
+ stop() {
110217
+ this.#stopped = true;
110218
+ if (this.#timer) {
110219
+ clearInterval(this.#timer);
110220
+ this.#timer = null;
110221
+ }
110222
+ }
110223
+ };
110224
+
110225
+ // ../../packages/host-core/dist/mesh/node.js
110226
+ import { existsSync as existsSync34 } from "node:fs";
110227
+
110228
+ // ../../packages/host-core/dist/mesh/bundle-refresh.js
110229
+ init_dist6();
110230
+ var CONTEXT = "tsr-bundle-fetch-v1";
110231
+ var REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
110232
+ async function fetchBundle(args) {
110233
+ const none = { bundle: null, epoch: null };
110234
+ try {
110235
+ const ts = Date.now();
110236
+ const { privateKey } = await signingKeyFromSeed(args.sigPrivate);
110237
+ const message = new TextEncoder().encode(`${CONTEXT}
110238
+ ${args.genesisFp.toLowerCase()}
110239
+ ${args.staticPub.toLowerCase()}
110240
+ ${ts}`);
110241
+ const sig = await sign2(privateKey, message);
110242
+ const doFetch = args.fetchImpl ?? fetch;
110243
+ const res = await doFetch(`${args.origin.replace(/\/$/, "")}/api/core/tesseract/mesh/meshes/by-genesis/${encodeURIComponent(args.genesisFp.toLowerCase())}/bundle`, {
110244
+ method: "GET",
110245
+ headers: {
110246
+ "x-tsr-node": args.staticPub.toLowerCase(),
110247
+ "x-tsr-ts": String(ts),
110248
+ "x-tsr-sig": Buffer.from(sig).toString("base64")
110249
+ }
110250
+ });
110251
+ if (!res.ok)
110252
+ return none;
110253
+ const body = await res.json();
110254
+ if (!body.bundle)
110255
+ return none;
110256
+ return { bundle: Uint8Array.from(Buffer.from(body.bundle, "hex")), epoch: body.epoch ?? null };
110257
+ } catch {
110258
+ return none;
110259
+ }
110260
+ }
109822
110261
 
109823
110262
  // ../../packages/host-core/dist/mesh/config.js
109824
110263
  init_credentials();
109825
110264
  import { chmodSync as chmodSync5, existsSync as existsSync33, mkdirSync as mkdirSync36, readdirSync as readdirSync12, readFileSync as readFileSync47, renameSync as renameSync11, rmSync as rmSync7, writeFileSync as writeFileSync35 } from "node:fs";
109826
- import { dirname as dirname31, join as join67 } from "node:path";
110265
+ import { dirname as dirname31, join as join68 } from "node:path";
109827
110266
  function meshDir() {
109828
- return join67(daemonDir2(), "mesh");
110267
+ return join68(daemonDir2(), "mesh");
109829
110268
  }
109830
110269
  function meshesDir() {
109831
- return join67(meshDir(), "meshes");
110270
+ return join68(meshDir(), "meshes");
109832
110271
  }
109833
110272
  var MESH_DIR_NAME_BYTES = 8;
109834
110273
  function meshDirName(genesisFp) {
@@ -109846,7 +110285,7 @@ function oneMeshDir(meshId) {
109846
110285
  if (!isMeshId(meshId)) {
109847
110286
  throw new Error(`not a mesh id: ${JSON.stringify(meshId.slice(0, 64))}`);
109848
110287
  }
109849
- return join67(meshesDir(), meshId);
110288
+ return join68(meshesDir(), meshId);
109850
110289
  }
109851
110290
  var LEGACY_MESH_ID = "legacy";
109852
110291
  var CERT_GENESIS_FP_OFFSET = 19;
@@ -109864,7 +110303,7 @@ var FILES = {
109864
110303
  local: "local.json"
109865
110304
  };
109866
110305
  function readIfPresent(name, dir2 = meshDir()) {
109867
- const p = join67(dir2, name);
110306
+ const p = join68(dir2, name);
109868
110307
  try {
109869
110308
  return existsSync33(p) ? new Uint8Array(readFileSync47(p)) : void 0;
109870
110309
  } catch {
@@ -109872,7 +110311,7 @@ function readIfPresent(name, dir2 = meshDir()) {
109872
110311
  }
109873
110312
  }
109874
110313
  function migrateSingleMesh() {
109875
- const legacyGenesis = join67(meshDir(), FILES.genesis);
110314
+ const legacyGenesis = join68(meshDir(), FILES.genesis);
109876
110315
  if (!existsSync33(legacyGenesis))
109877
110316
  return null;
109878
110317
  const id = meshIdFromCert(readIfPresent(FILES.cert)) ?? LEGACY_MESH_ID;
@@ -109881,11 +110320,11 @@ function migrateSingleMesh() {
109881
110320
  mkdirSync36(dest, { recursive: true, mode: 448 });
109882
110321
  chmodSync5(dest, 448);
109883
110322
  for (const f of Object.values(FILES)) {
109884
- const from = join67(meshDir(), f);
110323
+ const from = join68(meshDir(), f);
109885
110324
  if (existsSync33(from))
109886
- renameSync11(from, join67(dest, f));
110325
+ renameSync11(from, join68(dest, f));
109887
110326
  }
109888
- const key2 = join67(dest, FILES.key);
110327
+ const key2 = join68(dest, FILES.key);
109889
110328
  if (existsSync33(key2))
109890
110329
  chmodSync5(key2, 384);
109891
110330
  } catch {
@@ -109909,7 +110348,7 @@ function listMeshIds() {
109909
110348
  }
109910
110349
  function readLocal(meshId) {
109911
110350
  try {
109912
- return JSON.parse(readFileSync47(join67(oneMeshDir(meshId), FILES.local), "utf8"));
110351
+ return JSON.parse(readFileSync47(join68(oneMeshDir(meshId), FILES.local), "utf8"));
109913
110352
  } catch {
109914
110353
  return null;
109915
110354
  }
@@ -109917,7 +110356,7 @@ function readLocal(meshId) {
109917
110356
  function writeLocal(meshId, state3) {
109918
110357
  try {
109919
110358
  mkdirSync36(oneMeshDir(meshId), { recursive: true, mode: 448 });
109920
- writeFileSync35(join67(oneMeshDir(meshId), FILES.local), JSON.stringify(state3, null, 2), { mode: 384 });
110359
+ writeFileSync35(join68(oneMeshDir(meshId), FILES.local), JSON.stringify(state3, null, 2), { mode: 384 });
109921
110360
  } catch {
109922
110361
  }
109923
110362
  }
@@ -109950,7 +110389,7 @@ function loadMeshIdentity() {
109950
110389
  return first === void 0 ? null : loadMesh(first);
109951
110390
  }
109952
110391
  function writeSecret(name, bytes2, dir2 = meshDir()) {
109953
- const p = join67(dir2, name);
110392
+ const p = join68(dir2, name);
109954
110393
  writeFileSync35(p, bytes2, { mode: 384 });
109955
110394
  try {
109956
110395
  chmodSync5(p, 384);
@@ -109968,11 +110407,11 @@ function saveMeshIdentity(id) {
109968
110407
  chmodSync5(meshDir(), 448);
109969
110408
  } catch {
109970
110409
  }
109971
- writeFileSync35(join67(meshDir(), FILES.genesis), id.genesis);
109972
- writeFileSync35(join67(meshDir(), FILES.cert), id.cert);
110410
+ writeFileSync35(join68(meshDir(), FILES.genesis), id.genesis);
110411
+ writeFileSync35(join68(meshDir(), FILES.cert), id.cert);
109973
110412
  writeSecret(FILES.key, id.staticPrivate);
109974
110413
  if (id.bundle)
109975
- writeFileSync35(join67(meshDir(), FILES.bundle), id.bundle);
110414
+ writeFileSync35(join68(meshDir(), FILES.bundle), id.bundle);
109976
110415
  }
109977
110416
  function saveMesh(meshId, id) {
109978
110417
  const dir2 = oneMeshDir(meshId);
@@ -109989,13 +110428,13 @@ function saveMesh(meshId, id) {
109989
110428
  chmodSync5(dir2, 448);
109990
110429
  } catch {
109991
110430
  }
109992
- writeFileSync35(join67(dir2, FILES.genesis), id.genesis);
109993
- writeFileSync35(join67(dir2, FILES.cert), id.cert);
110431
+ writeFileSync35(join68(dir2, FILES.genesis), id.genesis);
110432
+ writeFileSync35(join68(dir2, FILES.cert), id.cert);
109994
110433
  writeSecret(FILES.key, id.staticPrivate, dir2);
109995
110434
  if (id.sigPrivate)
109996
110435
  writeSecret(FILES.sigKey, id.sigPrivate, dir2);
109997
110436
  if (id.bundle)
109998
- writeFileSync35(join67(dir2, FILES.bundle), id.bundle);
110437
+ writeFileSync35(join68(dir2, FILES.bundle), id.bundle);
109999
110438
  if (readLocal(meshId) === null)
110000
110439
  writeLocal(meshId, { joinedAt: Date.now() });
110001
110440
  }
@@ -110005,7 +110444,7 @@ function removeMesh(meshId) {
110005
110444
  var STATUS_REFRESH_MS = 5e3;
110006
110445
  var STATUS_STALE_MS = 2e4;
110007
110446
  function statusPath(meshId) {
110008
- return meshId === void 0 ? join67(meshDir(), "status.json") : join67(oneMeshDir(meshId), "status.json");
110447
+ return meshId === void 0 ? join68(meshDir(), "status.json") : join68(oneMeshDir(meshId), "status.json");
110009
110448
  }
110010
110449
  function writeStatus(status2, meshId) {
110011
110450
  try {
@@ -110036,7 +110475,7 @@ function cacheBundle(bundle, meshId) {
110036
110475
  const id = meshId ?? listMeshIds()[0];
110037
110476
  const dir2 = id === void 0 ? meshDir() : oneMeshDir(id);
110038
110477
  mkdirSync36(dir2, { recursive: true, mode: 448 });
110039
- writeFileSync35(join67(dir2, FILES.bundle), bundle);
110478
+ writeFileSync35(join68(dir2, FILES.bundle), bundle);
110040
110479
  } catch {
110041
110480
  }
110042
110481
  }
@@ -110192,6 +110631,9 @@ function loadMeshCore() {
110192
110631
  }
110193
110632
  var meshCore = loadMeshCore();
110194
110633
  function meshEngineStatus() {
110634
+ if (process.env.CCB_MESH_IN_DAEMON !== "1" && existsSync34(netdEndpoint())) {
110635
+ return { available: true, reason: null };
110636
+ }
110195
110637
  return { available: meshCore.available === true, reason: meshCore.loadError ?? null };
110196
110638
  }
110197
110639
  var TICK_MS2 = 1e3;
@@ -110287,8 +110729,16 @@ var MeshNode = class {
110287
110729
  #engine = null;
110288
110730
  #sock = null;
110289
110731
  #timer = null;
110732
+ /** The policy refresh poll. See `bundle-refresh.ts` — this is what makes eviction real. */
110733
+ #refreshTimer = null;
110290
110734
  #unsubscribe = null;
110291
110735
  #identity = null;
110736
+ /** The policy bundle we hold, as bytes, so we can OFFER it to a peer that is behind.
110737
+ * Kept beside the engine because the engine exposes an epoch but not the bytes. */
110738
+ #bundle = null;
110739
+ /** Who has already been offered which epoch — see `bundle-gossip.ts` for why a ledger
110740
+ * is needed at all (the relay broadcasts to the whole mesh, not to one peer). */
110741
+ #bundleOffered = new BundleOfferLedger();
110292
110742
  #port = null;
110293
110743
  #lastError = null;
110294
110744
  /**
@@ -110353,8 +110803,11 @@ var MeshNode = class {
110353
110803
  }
110354
110804
  status() {
110355
110805
  return {
110356
- available: meshCore.available,
110357
- loadError: meshCore.loadError,
110806
+ // In netd mode the engine is the helper's, and whether THIS process could load a
110807
+ // prebuilt is beside the point — a CLI install whose native fetch failed still has a
110808
+ // working network when the helper runs it, and must not say "engine unavailable".
110809
+ available: this.#engine instanceof NetdEngine ? true : meshCore.available,
110810
+ loadError: this.#engine instanceof NetdEngine ? null : meshCore.loadError,
110358
110811
  enrolled: this.#identity !== null || this.#loadIdentity() !== null,
110359
110812
  running: this.#engine !== null,
110360
110813
  port: this.#port,
@@ -110371,7 +110824,22 @@ var MeshNode = class {
110371
110824
  this.#established.has(hex6) ? this.#relayed.has(hex6) ? "relay" : "direct" : "none"
110372
110825
  ])),
110373
110826
  meter: this.#engine?.meter() ?? null,
110374
- lastError: this.#lastError,
110827
+ // THE POLICY EPOCH this node is actually running on.
110828
+ //
110829
+ // Read from the ENGINE, never from `bundle.bin`: the engine installs a bundle the
110830
+ // moment it verifies one, and a bundle pulled from a peer over the Noise session
110831
+ // never touches that file. Judging freshness by the file — which is what there was
110832
+ // to judge by — therefore reports a node as stale when it is current, and is how a
110833
+ // machine came to look a dozen epochs behind while carrying the right policy.
110834
+ //
110835
+ // It is the one number that answers "why is nothing passing": a node whose policy
110836
+ // predates a peer has no roster entry for it and drops every packet, correctly, with
110837
+ // nothing anywhere saying so.
110838
+ policyEpoch: this.#engine?.policyEpoch() ?? null,
110839
+ lastError: this.#lastError ?? (this.#engine instanceof NetdEngine ? this.#engine.lastError : null),
110840
+ // In netd mode "running" means netd runs it: an adapter with no node behind it yet
110841
+ // (routes not synced, helper just installed) is not a running node.
110842
+ ...this.#engine instanceof NetdEngine && !this.#engine.running ? { running: false } : {},
110375
110843
  // Surfaced so `net status` can say "removed" rather than "not running". Those look
110376
110844
  // identical from outside and have completely different remedies.
110377
110845
  removed: this.#revoked
@@ -110393,7 +110861,8 @@ var MeshNode = class {
110393
110861
  reason: "this machine was removed from the network \u2014 ask an admin for a new invitation"
110394
110862
  };
110395
110863
  }
110396
- if (!meshCore.available) {
110864
+ const inNetd = process.env.CCB_MESH_IN_DAEMON !== "1" && existsSync34(netdEndpoint());
110865
+ if (!inNetd && !meshCore.available) {
110397
110866
  return { ok: false, reason: `mesh engine unavailable: ${meshCore.loadError ?? "no prebuilt"}` };
110398
110867
  }
110399
110868
  const id = this.#loadIdentity();
@@ -110403,18 +110872,46 @@ var MeshNode = class {
110403
110872
  reason: this.#wantMeshId === null ? "this machine is not enrolled in a mesh" : `this machine is not enrolled in mesh ${this.#wantMeshId}`
110404
110873
  };
110405
110874
  }
110406
- try {
110407
- this.#engine = new meshCore.MeshEngine(Buffer.from(id.genesis), Buffer.from(id.staticPrivate), Buffer.from(id.cert), 0, Buffer.alloc(16));
110408
- if (id.bundle)
110409
- this.#engine.setBundle(Buffer.from(id.bundle), Date.now());
110410
- } catch (e) {
110411
- this.#engine = null;
110412
- this.#lastError = e instanceof Error ? e.message : String(e);
110413
- return { ok: false, reason: this.#lastError };
110875
+ if (inNetd) {
110876
+ const meshDir2 = this.#wantMeshId ?? listMeshIds()[0] ?? null;
110877
+ if (!meshDir2)
110878
+ return { ok: false, reason: "this machine is not enrolled in a mesh" };
110879
+ try {
110880
+ this.#engine = new NetdEngine(meshDir2, id.cert, {
110881
+ // netd reports the node up: now this side has a port to advertise, and can
110882
+ // publish itself to the relay and the LAN. Before that there is nothing to say.
110883
+ onUp: (port) => {
110884
+ this.#port = port;
110885
+ this.#join();
110886
+ this.#startLan();
110887
+ this.#publishStatus();
110888
+ }
110889
+ });
110890
+ if (id.bundle)
110891
+ this.#bundle = id.bundle;
110892
+ } catch (e) {
110893
+ this.#engine = null;
110894
+ this.#lastError = e instanceof Error ? e.message : String(e);
110895
+ return { ok: false, reason: this.#lastError };
110896
+ }
110897
+ } else {
110898
+ try {
110899
+ this.#engine = new meshCore.MeshEngine(Buffer.from(id.genesis), Buffer.from(id.staticPrivate), Buffer.from(id.cert), 0, Buffer.alloc(16));
110900
+ if (id.bundle) {
110901
+ this.#engine.setBundle(Buffer.from(id.bundle), Date.now());
110902
+ this.#bundle = id.bundle;
110903
+ }
110904
+ } catch (e) {
110905
+ this.#engine = null;
110906
+ this.#lastError = e instanceof Error ? e.message : String(e);
110907
+ return { ok: false, reason: this.#lastError };
110908
+ }
110414
110909
  }
110415
110910
  this.#identity = id;
110416
- this.#openSocket();
110417
- this.#startLan();
110911
+ if (!inNetd) {
110912
+ this.#openSocket();
110913
+ this.#startLan();
110914
+ }
110418
110915
  this.#offRejoin = onMeshRejoin(() => this.#join());
110419
110916
  this.#publishStatus();
110420
110917
  this.#statusTimer = setInterval(() => this.#publishStatus(), STATUS_REFRESH_MS);
@@ -110422,6 +110919,9 @@ var MeshNode = class {
110422
110919
  this.#subscribe();
110423
110920
  this.#timer = setInterval(() => this.#tick(), TICK_MS2);
110424
110921
  this.#timer.unref?.();
110922
+ void this.#refreshBundle();
110923
+ this.#refreshTimer = setInterval(() => void this.#refreshBundle(), REFRESH_INTERVAL_MS);
110924
+ this.#refreshTimer.unref?.();
110425
110925
  return { ok: true };
110426
110926
  }
110427
110927
  /** Tear down. Idempotent, and safe to call from a signal handler. */
@@ -110430,6 +110930,16 @@ var MeshNode = class {
110430
110930
  clearInterval(this.#timer);
110431
110931
  this.#timer = null;
110432
110932
  }
110933
+ if (this.#refreshTimer) {
110934
+ clearInterval(this.#refreshTimer);
110935
+ this.#refreshTimer = null;
110936
+ }
110937
+ if (this.#engine instanceof NetdEngine) {
110938
+ try {
110939
+ this.#engine.stop();
110940
+ } catch {
110941
+ }
110942
+ }
110433
110943
  this.#unsubscribe?.();
110434
110944
  this.#unsubscribe = null;
110435
110945
  this.#offRejoin?.();
@@ -110529,19 +111039,27 @@ var MeshNode = class {
110529
111039
  staticPub: engine.localStaticPub(),
110530
111040
  port: () => this.#port,
110531
111041
  epoch: () => engine.policyEpoch(),
110532
- onPeer: (staticPub, address, port) => this.#learnLanPeer(staticPub, address, port)
111042
+ onPeer: (staticPub, address, port, epoch2) => this.#learnLanPeer(staticPub, address, port, epoch2)
110533
111043
  });
110534
111044
  this.#lan.start();
110535
111045
  } catch {
110536
111046
  this.#lan = null;
110537
111047
  }
110538
111048
  }
110539
- /** A beacon arrived. Treated exactly like a relay hint — a candidate, not a fact. */
110540
- #learnLanPeer(staticPub, address, port) {
111049
+ /** A beacon arrived. Treated exactly like a relay hint — a candidate, not a fact.
111050
+ *
111051
+ * Including its EPOCH. The beacon has always carried one (`lan.ts`: "port(2) | epoch(8)")
111052
+ * for the same reason the relay hint does — so a peer that is behind can be brought up
111053
+ * to date — and, like the relay hint's, it was thrown away here. Two laptops on one
111054
+ * Wi-Fi with the internet down are the demonstration that the overlay is not
111055
+ * relay-dependent; without this they would find each other and then never agree on who
111056
+ * else exists. */
111057
+ #learnLanPeer(staticPub, address, port, epoch2 = 0) {
110541
111058
  const engine = this.#engine;
110542
111059
  if (!engine)
110543
111060
  return;
110544
111061
  const hex6 = staticPub.toString("hex");
111062
+ this.#offerBundleTo(hex6, epoch2);
110545
111063
  try {
110546
111064
  if (!this.#peers.has(hex6)) {
110547
111065
  engine.addPeer(staticPub, 0, Buffer.alloc(16));
@@ -110653,6 +111171,8 @@ var MeshNode = class {
110653
111171
  engine.setBundle(bytes2, Date.now());
110654
111172
  this.#stopIfRevoked(engine);
110655
111173
  cacheBundle(new Uint8Array(bytes2), meshDirNameFromWireId(engine.meshId()) ?? void 0);
111174
+ this.#bundle = new Uint8Array(bytes2);
111175
+ this.#bundleOffered.reset();
110656
111176
  break;
110657
111177
  }
110658
111178
  }
@@ -110661,6 +111181,89 @@ var MeshNode = class {
110661
111181
  this.#lastError = e instanceof Error ? e.message : String(e);
110662
111182
  }
110663
111183
  }
111184
+ /**
111185
+ * Ask the server for current policy, and adopt it if it is newer.
111186
+ *
111187
+ * The one path that can deliver an EVICTION. Gossip only ever moves a network to the
111188
+ * newest bundle some connected node already holds, and after an eviction no node holds
111189
+ * the new one — so without this, "remove that laptop" changed a database row and nothing
111190
+ * else until somebody happened to join.
111191
+ *
111192
+ * Applied through exactly the same steps as a gossiped bundle, deliberately: the engine
111193
+ * verifies it against genesis (so a hostile server cannot widen policy any more than a
111194
+ * hostile relay can), `#stopIfRevoked` reads our own serial out of it, it is cached to
111195
+ * disk, and the offer ledger resets so our peers hear about it too. That last part is
111196
+ * what makes one node's poll benefit the whole network.
111197
+ */
111198
+ async #refreshBundle() {
111199
+ const engine = this.#engine;
111200
+ const id = this.#identity;
111201
+ if (!engine || !id?.sigPrivate)
111202
+ return;
111203
+ const meshDir2 = meshDirNameFromWireId(engine.meshId());
111204
+ const origin = (meshDir2 ? readLocal(meshDir2)?.origin : void 0) ?? readCredentials()?.apiOrigin;
111205
+ if (!origin)
111206
+ return;
111207
+ const got = await fetchBundle({
111208
+ origin,
111209
+ genesisFp: engine.meshId(),
111210
+ staticPub: Buffer.from(engine.localStaticPub()).toString("hex"),
111211
+ sigPrivate: id.sigPrivate
111212
+ });
111213
+ if (!got.bundle)
111214
+ return;
111215
+ try {
111216
+ engine.setBundle(Buffer.from(got.bundle), Date.now());
111217
+ } catch {
111218
+ return;
111219
+ }
111220
+ this.#stopIfRevoked(engine);
111221
+ cacheBundle(got.bundle, meshDir2 ?? void 0);
111222
+ this.#bundle = got.bundle;
111223
+ this.#bundleOffered.reset();
111224
+ }
111225
+ /**
111226
+ * Offer our policy bundle to a peer whose epoch is older than ours.
111227
+ *
111228
+ * ## The half of gossip that was never written
111229
+ *
111230
+ * `MeshPeerHint.epoch` is documented in the protocol as "the epoch the node advertises,
111231
+ * SO A LAGGING PEER KNOWS TO PULL", and the relay has always carried it. Nothing read
111232
+ * it, and — the part that made this total — nothing in the product ever SENT a
111233
+ * `mesh:bundle`. The broker forwarded a message with no sender anywhere.
111234
+ *
111235
+ * So a node's roster was frozen at the instant it joined. `joinWithToken` caches the
111236
+ * bundle the server returns, which is why a machine works at all; but every machine that
111237
+ * joins LATER is invisible to it forever, because the only thing that would have told it
111238
+ * is the gossip that never happened. On a four-machine network built in join order, the
111239
+ * first machine knew nobody, the last knew everyone, and every one of them reported
111240
+ * itself healthy. That is what this fixes.
111241
+ *
111242
+ * PUSH, not pull, because push needs no new message type and the protocol already hands
111243
+ * us exactly the fact we need — the peer's epoch. For any pair, whoever is ahead offers,
111244
+ * so the direction takes care of itself and a new joiner (who always has the newest
111245
+ * bundle, straight from the server) feeds it to everyone already there.
111246
+ *
111247
+ * Safe by construction: the bundle is CA-signed, so `setBundle` on the receiving side
111248
+ * verifies it against genesis and refuses anything that does not chain. Offering is
111249
+ * therefore never a policy change — the worst a wrong offer costs a peer is a parse.
111250
+ */
111251
+ #offerBundleTo(peerHex, peerEpoch) {
111252
+ const engine = this.#engine;
111253
+ const bundle = this.#bundle;
111254
+ if (!engine || !bundle)
111255
+ return;
111256
+ if (!this.#bundleOffered.claim(peerHex, peerEpoch, engine.policyEpoch()))
111257
+ return;
111258
+ try {
111259
+ sendMesh({
111260
+ type: "mesh:bundle",
111261
+ meshId: engine.meshId(),
111262
+ bundle: Buffer.from(bundle).toString("base64")
111263
+ });
111264
+ } catch {
111265
+ }
111266
+ }
110664
111267
  #learnPeer(p) {
110665
111268
  const engine = this.#engine;
110666
111269
  if (!engine)
@@ -110669,6 +111272,7 @@ var MeshNode = class {
110669
111272
  if (pk.length !== 32)
110670
111273
  return;
110671
111274
  const hex6 = pk.toString("hex");
111275
+ this.#offerBundleTo(hex6, p.epoch ?? 0);
110672
111276
  if (p.cert) {
110673
111277
  try {
110674
111278
  const bytes2 = Buffer.from(p.cert, "base64");
@@ -110712,10 +111316,12 @@ var MeshNode = class {
110712
111316
  /** Flush everything the engine wants to send, and absorb its events. */
110713
111317
  #drain() {
110714
111318
  const engine = this.#engine;
110715
- const sock = this.#sock;
110716
- if (!engine || !sock)
111319
+ if (!engine)
110717
111320
  return;
111321
+ const sock = this.#sock;
110718
111322
  for (; ; ) {
111323
+ if (!sock)
111324
+ break;
110719
111325
  const item = engine.pollSend();
110720
111326
  if (!item)
110721
111327
  break;
@@ -110863,148 +111469,6 @@ function meshManager() {
110863
111469
  return singleton3;
110864
111470
  }
110865
111471
 
110866
- // ../../packages/host-core/dist/mesh/netd-client.js
110867
- import { connect as connect2 } from "node:net";
110868
- import { platform as platform3 } from "node:process";
110869
- var NETD_WIRE_VERSION = 1;
110870
- var MAX_LINE = 1024 * 1024;
110871
- var TIMEOUT_MS3 = 15e3;
110872
- function netdEndpoint() {
110873
- if (platform3 === "win32")
110874
- return "\\\\.\\pipe\\tesseract-netd";
110875
- if (platform3 === "darwin")
110876
- return "/var/run/tesseract-netd.sock";
110877
- return "/run/tesseract-netd.sock";
110878
- }
110879
- function endpointOverride() {
110880
- return process.env.CCB_NETD_SOCKET ?? null;
110881
- }
110882
- function fail2(code, detail) {
110883
- return { ok: false, code, detail };
110884
- }
110885
- async function call(line) {
110886
- const endpoint2 = endpointOverride() ?? netdEndpoint();
110887
- return new Promise((resolve16) => {
110888
- let settled = false;
110889
- const done = (r) => {
110890
- if (settled)
110891
- return;
110892
- settled = true;
110893
- try {
110894
- sock.destroy();
110895
- } catch {
110896
- }
110897
- resolve16(r);
110898
- };
110899
- let sock;
110900
- try {
110901
- sock = connect2(endpoint2);
110902
- } catch (e) {
110903
- return resolve16(fail2("not-installed", e instanceof Error ? e.message : String(e)));
110904
- }
110905
- const timer2 = setTimeout(() => {
110906
- done(fail2("timeout", `no answer from the network helper in ${TIMEOUT_MS3}ms`));
110907
- }, TIMEOUT_MS3);
110908
- timer2.unref?.();
110909
- sock.setTimeout(TIMEOUT_MS3);
110910
- sock.on("timeout", () => {
110911
- clearTimeout(timer2);
110912
- done(fail2("timeout", "socket idle"));
110913
- });
110914
- sock.on("error", (e) => {
110915
- clearTimeout(timer2);
110916
- const absent = e.code === "ENOENT" || e.code === "ECONNREFUSED";
110917
- done(fail2(absent ? "not-installed" : "io", e.message));
110918
- });
110919
- let buf = "";
110920
- sock.on("data", (chunk) => {
110921
- buf += chunk.toString("utf8");
110922
- if (buf.length > MAX_LINE) {
110923
- clearTimeout(timer2);
110924
- done(fail2("protocol", "the helper sent an oversized line"));
110925
- return;
110926
- }
110927
- const nl = buf.indexOf("\n");
110928
- if (nl === -1)
110929
- return;
110930
- clearTimeout(timer2);
110931
- done({ ok: true, line: buf.slice(0, nl) });
110932
- });
110933
- sock.on("close", () => {
110934
- clearTimeout(timer2);
110935
- done(fail2("protocol", "the helper closed the connection without answering"));
110936
- });
110937
- sock.on("connect", () => {
110938
- sock.write(`${line}
110939
- `);
110940
- });
110941
- });
110942
- }
110943
- function parse3(line) {
110944
- let body;
110945
- try {
110946
- body = JSON.parse(line);
110947
- } catch {
110948
- return fail2("protocol", "the helper sent something that is not JSON");
110949
- }
110950
- if (typeof body !== "object" || body === null) {
110951
- return fail2("protocol", "the helper sent something that is not an object");
110952
- }
110953
- const o = body;
110954
- if (o.ok === false) {
110955
- const code = typeof o.code === "string" ? o.code : "refused";
110956
- const detail = typeof o.detail === "string" ? o.detail : "the helper refused";
110957
- return fail2(code, detail);
110958
- }
110959
- if (o.ok !== true)
110960
- return fail2("protocol", "the helper did not say whether it succeeded");
110961
- return { ok: true, body: o };
110962
- }
110963
- async function request(line) {
110964
- const r = await call(line);
110965
- return r.ok ? parse3(r.line) : r;
110966
- }
110967
- function hex4(b) {
110968
- return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
110969
- }
110970
- async function netdHello() {
110971
- const r = await request(JSON.stringify({ op: "hello", wireVersion: NETD_WIRE_VERSION }));
110972
- if (!r.ok)
110973
- return r;
110974
- const v = r.body.wireVersion;
110975
- if (typeof v !== "number")
110976
- return fail2("protocol", "the helper did not report a wire version");
110977
- if (v !== NETD_WIRE_VERSION) {
110978
- return fail2("wire-version", `the helper speaks ${v}, this build speaks ${NETD_WIRE_VERSION}`);
110979
- }
110980
- return { ok: true, wireVersion: v };
110981
- }
110982
- async function netdStatus() {
110983
- const r = await request(JSON.stringify({ op: "status" }));
110984
- if (!r.ok)
110985
- return r;
110986
- const meshes = Array.isArray(r.body.meshes) ? r.body.meshes.filter((m) => typeof m === "string") : [];
110987
- const iface = typeof r.body.interface === "string" ? r.body.interface : null;
110988
- return { ok: true, meshes, interface: iface };
110989
- }
110990
- async function netdUp(args) {
110991
- const r = await request(JSON.stringify({
110992
- op: "up",
110993
- meshId: args.meshId,
110994
- genesis: hex4(args.genesis),
110995
- cert: hex4(args.cert),
110996
- fullTunnel: args.fullTunnel === true
110997
- }));
110998
- return r.ok ? { ok: true } : r;
110999
- }
111000
- async function netdDown(meshId) {
111001
- const r = await request(JSON.stringify({ op: "down", meshId }));
111002
- return r.ok ? { ok: true } : r;
111003
- }
111004
- async function netdAvailable() {
111005
- return (await netdHello()).ok;
111006
- }
111007
-
111008
111472
  // ../../packages/host-core/dist/mesh/route-sync.js
111009
111473
  async function syncRoutes(running2, log9 = () => {
111010
111474
  }) {
@@ -111033,7 +111497,14 @@ async function syncRoutes(running2, log9 = () => {
111033
111497
  // The per-machine choice, and `=== true` rather than a truthiness check: an absent or
111034
111498
  // malformed field must read as NO. netd unions this across meshes itself — any mesh
111035
111499
  // asking for a full tunnel makes it one for the machine — so this stays per mesh here.
111036
- fullTunnel: readLocal(meshId)?.fullTunnel === true
111500
+ fullTunnel: readLocal(meshId)?.fullTunnel === true,
111501
+ // THE KEY, and the bundle. With these netd runs the node — the data plane — rather
111502
+ // than only pointing routes at a device nothing reads. The crate's own doc always
111503
+ // described the enrolment as "a genesis block, a certificate and a key"; this is the
111504
+ // first caller to send the third. Over the helper's authenticated socket, to the one
111505
+ // process on the machine that already holds the device the traffic goes into.
111506
+ staticPrivate: identity.staticPrivate,
111507
+ ...identity.bundle ? { bundle: identity.bundle } : {}
111037
111508
  });
111038
111509
  if (r.ok) {
111039
111510
  out.up.push(meshId);
@@ -111059,22 +111530,63 @@ async function syncRoutes(running2, log9 = () => {
111059
111530
  }
111060
111531
 
111061
111532
  // ../../packages/host-core/dist/mesh/start-meshes.js
111062
- function startMeshes(log9) {
111533
+ var ROUTE_RETRY_MS = 6e4;
111534
+ function startMeshes(log9, opts = {}) {
111063
111535
  const running2 = startNodes(log9);
111064
- void syncRoutes(running2, log9).then((r) => {
111536
+ const retryMs = opts.retryMs ?? ROUTE_RETRY_MS;
111537
+ let timer2 = null;
111538
+ let stopped2 = false;
111539
+ let lastSummary = "";
111540
+ const attempt = async () => {
111541
+ let r;
111542
+ try {
111543
+ r = await syncRoutes(running2, log9);
111544
+ } catch (e) {
111545
+ log9(`routes: sync threw \u2014 ${e instanceof Error ? e.message : String(e)}`);
111546
+ return false;
111547
+ }
111065
111548
  if (r.helperAbsent) {
111066
- log9("routes: the network helper is not installed \u2014 the overlay carries no traffic yet");
111067
- return;
111549
+ const msg = "routes: the network helper is not installed \u2014 the overlay carries no traffic yet";
111550
+ if (msg !== lastSummary)
111551
+ log9(msg);
111552
+ lastSummary = msg;
111553
+ return false;
111068
111554
  }
111069
111555
  if (r.up.length)
111070
111556
  log9(`routes: installed for ${r.up.join(", ")}`);
111071
111557
  if (r.down.length)
111072
111558
  log9(`routes: removed for ${r.down.join(", ")}`);
111073
- for (const f of r.failed)
111074
- log9(`routes: ${f.meshId} refused \u2014 ${f.reason}`);
111075
- }).catch((e) => {
111076
- log9(`routes: sync threw \u2014 ${e instanceof Error ? e.message : String(e)}`);
111559
+ const failures2 = r.failed.map((f) => `routes: ${f.meshId} refused \u2014 ${f.reason}`).join("\n");
111560
+ if (failures2 && failures2 !== lastSummary)
111561
+ log9(failures2);
111562
+ lastSummary = failures2;
111563
+ return r.failed.length === 0;
111564
+ };
111565
+ const schedule = () => {
111566
+ if (stopped2 || running2.length === 0)
111567
+ return;
111568
+ timer2 = setTimeout(() => {
111569
+ timer2 = null;
111570
+ void attempt().then((done) => {
111571
+ if (!done)
111572
+ schedule();
111573
+ });
111574
+ }, retryMs);
111575
+ timer2.unref?.();
111576
+ };
111577
+ void attempt().then((done) => {
111578
+ if (!done)
111579
+ schedule();
111077
111580
  });
111581
+ return {
111582
+ stop() {
111583
+ stopped2 = true;
111584
+ if (timer2) {
111585
+ clearTimeout(timer2);
111586
+ timer2 = null;
111587
+ }
111588
+ }
111589
+ };
111078
111590
  }
111079
111591
  function startNodes(log9) {
111080
111592
  const ids = listMeshIds();
@@ -112404,7 +112916,7 @@ function appendRdeskAudit(e) {
112404
112916
 
112405
112917
  // ../../packages/host-core/dist/fileprovider-bridge.js
112406
112918
  init_managed_spawn();
112407
- import { writeFileSync as writeFileSync36, mkdirSync as mkdirSync37, rmSync as rmSync8, existsSync as existsSync34 } from "node:fs";
112919
+ import { writeFileSync as writeFileSync36, mkdirSync as mkdirSync37, rmSync as rmSync8, existsSync as existsSync35 } from "node:fs";
112408
112920
  import { homedir as homedir10 } from "node:os";
112409
112921
  import { join as join73 } from "node:path";
112410
112922
  init_safe_kill();
@@ -112575,7 +113087,7 @@ function stopFileProviderBridge() {
112575
113087
 
112576
113088
  // ../../packages/host-core/dist/vault/autofill-bridge.js
112577
113089
  import { randomBytes as randomBytes9 } from "node:crypto";
112578
- import { existsSync as existsSync35, mkdirSync as mkdirSync38, writeFileSync as writeFileSync37, rmSync as rmSync9 } from "node:fs";
113090
+ import { existsSync as existsSync36, mkdirSync as mkdirSync38, writeFileSync as writeFileSync37, rmSync as rmSync9 } from "node:fs";
112579
113091
  import { homedir as homedir11 } from "node:os";
112580
113092
  import { join as join74 } from "node:path";
112581
113093
 
@@ -112650,7 +113162,7 @@ function writeBridgeInfo(port, token3, log9) {
112650
113162
  return;
112651
113163
  const containerData = appexContainerData();
112652
113164
  try {
112653
- if (!existsSync35(containerData)) {
113165
+ if (!existsSync36(containerData)) {
112654
113166
  if (!infoLoggedOnce) {
112655
113167
  infoLoggedOnce = true;
112656
113168
  log9("autofill bridge-info SKIP: no appex container yet at", containerData);
@@ -112899,7 +113411,7 @@ function createNodePowerController() {
112899
113411
 
112900
113412
  // ../../packages/host-core/dist/native-host.js
112901
113413
  import { execFileSync as execFileSync4 } from "node:child_process";
112902
- import { chmodSync as chmodSync6, existsSync as existsSync36, mkdirSync as mkdirSync39, writeFileSync as writeFileSync38 } from "node:fs";
113414
+ import { chmodSync as chmodSync6, existsSync as existsSync37, mkdirSync as mkdirSync39, writeFileSync as writeFileSync38 } from "node:fs";
112903
113415
  import { homedir as homedir12, platform as platform5 } from "node:os";
112904
113416
  import { dirname as dirname34, join as join75, resolve as resolve12 } from "node:path";
112905
113417
  import { fileURLToPath as fileURLToPath6 } from "node:url";
@@ -112930,7 +113442,7 @@ function resolveBridge(log9) {
112930
113442
  } catch {
112931
113443
  }
112932
113444
  for (const c of candidates)
112933
- if (c && existsSync36(c))
113445
+ if (c && existsSync37(c))
112934
113446
  return c;
112935
113447
  log9("companion native-host: bridge not found; skipping registration (looked in", candidates.filter(Boolean).join(" , "), ")");
112936
113448
  return null;
@@ -112972,7 +113484,7 @@ function registerNativeHost(opts) {
112972
113484
  return registerWindows(opts.daemonDir, manifestJson, log9);
112973
113485
  let wrote = 0;
112974
113486
  for (const profile of browserProfileDirs()) {
112975
- if (!existsSync36(profile))
113487
+ if (!existsSync37(profile))
112976
113488
  continue;
112977
113489
  try {
112978
113490
  const dir2 = join75(profile, "NativeMessagingHosts");
@@ -113008,7 +113520,7 @@ init_daemon_settings();
113008
113520
 
113009
113521
  // ../../packages/host-core/dist/activity/activity-tracker.js
113010
113522
  init_credentials();
113011
- import { promises as fsp, writeFileSync as writeFileSync39, mkdirSync as mkdirSync40, existsSync as existsSync37 } from "node:fs";
113523
+ import { promises as fsp, writeFileSync as writeFileSync39, mkdirSync as mkdirSync40, existsSync as existsSync38 } from "node:fs";
113012
113524
  import { join as join76 } from "node:path";
113013
113525
  function activityProfilePath() {
113014
113526
  return join76(daemonDir2(), "activity-profile.json");
@@ -113086,7 +113598,7 @@ function createActivityTracker(opts = {}) {
113086
113598
  return;
113087
113599
  const key2 = front.bundleId || front.name;
113088
113600
  await ensureLoaded();
113089
- if (profile && Object.keys(profile.apps).length && !existsSync37(activityProfilePath())) {
113601
+ if (profile && Object.keys(profile.apps).length && !existsSync38(activityProfilePath())) {
113090
113602
  log9("activity profile cleared externally \u2014 resetting in-memory");
113091
113603
  profile = { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), apps: {} };
113092
113604
  }
@@ -113319,7 +113831,7 @@ function backoff2() {
113319
113831
 
113320
113832
  // ../../packages/host-core/dist/daemon-runtime.js
113321
113833
  var daemonClipboardSync = null;
113322
- var REFRESH_INTERVAL_MS = 12 * 60 * 60 * 1e3;
113834
+ var REFRESH_INTERVAL_MS2 = 12 * 60 * 60 * 1e3;
113323
113835
  async function runDaemon(opts = {}) {
113324
113836
  const VERSION3 = opts.version ?? process.env.CCB_DAEMON_VERSION ?? "dev";
113325
113837
  const TAG = opts.tag ?? "host-daemon";
@@ -113360,7 +113872,7 @@ async function runDaemon(opts = {}) {
113360
113872
  function acquireLock2() {
113361
113873
  const f = pidfile();
113362
113874
  try {
113363
- if (!existsSync38(daemonDir2()))
113875
+ if (!existsSync39(daemonDir2()))
113364
113876
  mkdirSync41(daemonDir2(), { recursive: true, mode: 448 });
113365
113877
  } catch {
113366
113878
  }
@@ -113386,7 +113898,7 @@ async function runDaemon(opts = {}) {
113386
113898
  function releaseLock() {
113387
113899
  try {
113388
113900
  const f = pidfile();
113389
- if (existsSync38(f) && Number(readFileSync49(f, "utf-8").trim()) === process.pid)
113901
+ if (existsSync39(f) && Number(readFileSync49(f, "utf-8").trim()) === process.pid)
113390
113902
  unlinkSync6(f);
113391
113903
  } catch {
113392
113904
  }
@@ -113428,6 +113940,7 @@ async function runDaemon(opts = {}) {
113428
113940
  }
113429
113941
  }
113430
113942
  let stopGatewayKeyRefresh = null;
113943
+ let meshRoutes = null;
113431
113944
  let timer2 = null;
113432
113945
  let identityWatch = null;
113433
113946
  let refreshRetry = null;
@@ -113522,6 +114035,10 @@ async function runDaemon(opts = {}) {
113522
114035
  stopGatewayKeyRefresh();
113523
114036
  stopGatewayKeyRefresh = null;
113524
114037
  }
114038
+ if (meshRoutes) {
114039
+ meshRoutes.stop();
114040
+ meshRoutes = null;
114041
+ }
113525
114042
  try {
113526
114043
  offRecovery?.();
113527
114044
  offRecovery = null;
@@ -114213,7 +114730,7 @@ async function runDaemon(opts = {}) {
114213
114730
  } catch {
114214
114731
  }
114215
114732
  try {
114216
- startMeshes(log9);
114733
+ meshRoutes = startMeshes(log9);
114217
114734
  } catch {
114218
114735
  }
114219
114736
  writeStatus2({ deviceId: c.deviceId, connected: true });
@@ -114492,7 +115009,7 @@ async function runDaemon(opts = {}) {
114492
115009
  const daemonLogFile = join77(daemonDir2(), "daemon.log");
114493
115010
  const capTimer = setInterval(() => {
114494
115011
  try {
114495
- if (existsSync38(daemonLogFile) && statSync14(daemonLogFile).size > DAEMON_LOG_CAP) {
115012
+ if (existsSync39(daemonLogFile) && statSync14(daemonLogFile).size > DAEMON_LOG_CAP) {
114496
115013
  const buf = readFileSync49(daemonLogFile, "utf-8");
114497
115014
  const kept = `\u2026[daemon.log capped at ${(/* @__PURE__ */ new Date()).toISOString()}]\u2026
114498
115015
  ` + buf.slice(-DAEMON_LOG_CAP / 2);
@@ -114509,7 +115026,7 @@ async function runDaemon(opts = {}) {
114509
115026
  startAutofillBridge(log9);
114510
115027
  }
114511
115028
  if (ownsMachine)
114512
- startMeshes(log9);
115029
+ meshRoutes = startMeshes(log9);
114513
115030
  if (ownsMachine) {
114514
115031
  try {
114515
115032
  stopGatewayKeyRefresh = startGatewayKeyRefresh();
@@ -114521,7 +115038,7 @@ async function runDaemon(opts = {}) {
114521
115038
  await refresh();
114522
115039
  timer2 = setInterval(() => {
114523
115040
  void refresh();
114524
- }, REFRESH_INTERVAL_MS);
115041
+ }, REFRESH_INTERVAL_MS2);
114525
115042
  }
114526
115043
 
114527
115044
  // ../../packages/host-core/dist/host-daemon-entry.js
@@ -114631,7 +115148,7 @@ async function evictNode(args) {
114631
115148
  const r = await call2(`/meshes/${encodeURIComponent(args.meshId)}/nodes/${encodeURIComponent(args.nodeId)}`, { method: "DELETE", body: {} });
114632
115149
  return r.ok ? { ok: true, epoch: r.data.epoch } : r;
114633
115150
  }
114634
- async function enrolWith(requested, post) {
115151
+ async function enrolWith(requested, post, origin) {
114635
115152
  const name = normaliseNodeName(requested);
114636
115153
  if (!name) {
114637
115154
  return fail3("bad-name", `"${requested}" cannot be a machine name \u2014 names are DNS labels, so letters, digits and hyphens only`);
@@ -114655,7 +115172,7 @@ async function enrolWith(requested, post) {
114655
115172
  }
114656
115173
  try {
114657
115174
  saveMesh(meshId, { genesis, cert, staticPrivate, sigPrivate });
114658
- writeLocal(meshId, { joinedAt: Date.now(), enabled: true });
115175
+ writeLocal(meshId, { joinedAt: Date.now(), enabled: true, ...origin ? { origin } : {} });
114659
115176
  if (r.data.bundle) {
114660
115177
  try {
114661
115178
  cacheBundle(fromHexOrB64(r.data.bundle), meshId);
@@ -114677,16 +115194,24 @@ async function enrolWith(requested, post) {
114677
115194
  };
114678
115195
  }
114679
115196
  async function enrol(args) {
114680
- return enrolWith(args.name, (staticPub, sigPub, name) => call2(`/meshes/${encodeURIComponent(args.meshId)}/nodes`, {
114681
- method: "POST",
114682
- body: { name, staticPub, sigPub, ...args.exitNode === true ? { exitNode: true } : {} }
114683
- }));
115197
+ return enrolWith(
115198
+ args.name,
115199
+ (staticPub, sigPub, name) => call2(`/meshes/${encodeURIComponent(args.meshId)}/nodes`, {
115200
+ method: "POST",
115201
+ body: { name, staticPub, sigPub, ...args.exitNode === true ? { exitNode: true } : {} }
115202
+ }),
115203
+ // This path is authenticated, so the origin is necessarily the signed-in one.
115204
+ resolveOrigin(void 0)
115205
+ );
114684
115206
  }
114685
115207
  async function joinWithToken(args) {
114686
- return enrolWith(args.name, async (staticPub, sigPub, name) => postJoin(args.origin, { token: args.token, name, staticPub, sigPub }));
115208
+ return enrolWith(args.name, async (staticPub, sigPub, name) => postJoin(args.origin, { token: args.token, name, staticPub, sigPub }), resolveOrigin(args.origin));
115209
+ }
115210
+ function resolveOrigin(origin) {
115211
+ return origin ?? readCredentials()?.apiOrigin ?? void 0;
114687
115212
  }
114688
115213
  async function postJoin(origin, body) {
114689
- const base = origin ?? readCredentials()?.apiOrigin;
115214
+ const base = resolveOrigin(origin);
114690
115215
  if (!base) {
114691
115216
  return fail3("not-signed-in", "this machine has never signed in \u2014 pass --origin https://\u2026 so it knows which server to ask");
114692
115217
  }
@@ -114714,7 +115239,7 @@ async function postJoin(origin, body) {
114714
115239
  return { ok: true, data: JSON.parse(text12) };
114715
115240
  }
114716
115241
  async function enrolWithCode(args) {
114717
- return enrolWith(args.name, async (staticPub, sigPub, name) => postJoin(args.origin, { code: args.code, name, staticPub, sigPub }));
115242
+ return enrolWith(args.name, async (staticPub, sigPub, name) => postJoin(args.origin, { code: args.code, name, staticPub, sigPub }), resolveOrigin(args.origin));
114718
115243
  }
114719
115244
  async function rawPrivate(key2) {
114720
115245
  const pkcs8 = new Uint8Array(await crypto.subtle.exportKey("pkcs8", key2));
@@ -114798,13 +115323,13 @@ init_credentials();
114798
115323
 
114799
115324
  // ../../packages/host-core/dist/net-service/ensure-installed.js
114800
115325
  import { execFile as execFile14 } from "node:child_process";
114801
- import { existsSync as existsSync42 } from "node:fs";
115326
+ import { existsSync as existsSync43 } from "node:fs";
114802
115327
  import { dirname as dirname36, join as join81 } from "node:path";
114803
115328
  import { promisify as promisify10 } from "node:util";
114804
115329
 
114805
115330
  // ../../packages/host-core/dist/net-service/launchd-system.js
114806
115331
  import { spawn as spawn9 } from "node:child_process";
114807
- import { existsSync as existsSync39, mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync41, rmSync as rmSync10, readFileSync as readFileSync50 } from "node:fs";
115332
+ import { existsSync as existsSync40, mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync41, rmSync as rmSync10, readFileSync as readFileSync50 } from "node:fs";
114808
115333
  import { tmpdir as tmpdir6 } from "node:os";
114809
115334
  import { join as join78 } from "node:path";
114810
115335
 
@@ -114975,7 +115500,7 @@ ${plist}TESSERACT_PLIST_EOF`,
114975
115500
  };
114976
115501
  },
114977
115502
  async state() {
114978
- if (!existsSync39(PLIST))
115503
+ if (!existsSync40(PLIST))
114979
115504
  return { installed: false, running: false };
114980
115505
  const owner = parseOwnerUid(readFileSync50(PLIST, "utf8"));
114981
115506
  const running2 = await netdAvailable();
@@ -114995,7 +115520,7 @@ ${plist}TESSERACT_PLIST_EOF`,
114995
115520
 
114996
115521
  // ../../packages/host-core/dist/net-service/systemd-system.js
114997
115522
  import { spawn as spawn11 } from "node:child_process";
114998
- import { existsSync as existsSync40, readFileSync as readFileSync51 } from "node:fs";
115523
+ import { existsSync as existsSync41, readFileSync as readFileSync51 } from "node:fs";
114999
115524
 
115000
115525
  // ../../packages/host-core/dist/service/exec.js
115001
115526
  import { execFile as execFile11, spawn as spawn10 } from "node:child_process";
@@ -115138,7 +115663,7 @@ function makeSystemdSystem() {
115138
115663
  return code === 0 ? { ok: true } : { ok: false, detail: `removing the helper failed (exit ${code})` };
115139
115664
  },
115140
115665
  async state() {
115141
- if (!existsSync40(UNIT_PATH))
115666
+ if (!existsSync41(UNIT_PATH))
115142
115667
  return { installed: false, running: false };
115143
115668
  const owner = parseOwnerUid(readFileSync51(UNIT_PATH, "utf8"));
115144
115669
  const active = await tryRun("systemctl", ["is-active", UNIT]);
@@ -115401,7 +115926,7 @@ function makeNetService() {
115401
115926
  }
115402
115927
 
115403
115928
  // ../../packages/host-core/dist/net-service/netd-binary.js
115404
- import { existsSync as existsSync41 } from "node:fs";
115929
+ import { existsSync as existsSync42 } from "node:fs";
115405
115930
  import { dirname as dirname35, join as join80 } from "node:path";
115406
115931
  import { fileURLToPath as fileURLToPath7 } from "node:url";
115407
115932
  function netdBinaryName() {
@@ -115409,7 +115934,7 @@ function netdBinaryName() {
115409
115934
  }
115410
115935
  function override2() {
115411
115936
  const p = process.env.CCB_NETD_BIN;
115412
- return p && existsSync41(p) ? p : null;
115937
+ return p && existsSync42(p) ? p : null;
115413
115938
  }
115414
115939
  function netdBinaryPath() {
115415
115940
  const fromEnv = override2();
@@ -115436,7 +115961,7 @@ function netdBinaryPath() {
115436
115961
  candidates.push(join80(process.cwd(), "crates", "target", "release", name));
115437
115962
  for (const c of candidates) {
115438
115963
  try {
115439
- if (existsSync41(c))
115964
+ if (existsSync42(c))
115440
115965
  return c;
115441
115966
  } catch {
115442
115967
  }
@@ -115478,7 +116003,7 @@ async function ensureNetHelperInstalled() {
115478
116003
  if (st.installed && !mismatched)
115479
116004
  return { ok: true, already: true };
115480
116005
  const exec2 = netdBinaryPath();
115481
- if (exec2 && process.platform === "win32" && !existsSync42(join81(dirname36(exec2), "wintun.dll"))) {
116006
+ if (exec2 && process.platform === "win32" && !existsSync43(join81(dirname36(exec2), "wintun.dll"))) {
115482
116007
  return {
115483
116008
  ok: false,
115484
116009
  reason: "no-binary",
@@ -115530,7 +116055,7 @@ import { platform as platform6 } from "node:os";
115530
116055
 
115531
116056
  // ../../packages/host-core/dist/service/launchd.js
115532
116057
  init_credentials();
115533
- import { writeFileSync as writeFileSync43, existsSync as existsSync43, mkdirSync as mkdirSync42, unlinkSync as unlinkSync7 } from "node:fs";
116058
+ import { writeFileSync as writeFileSync43, existsSync as existsSync44, mkdirSync as mkdirSync42, unlinkSync as unlinkSync7 } from "node:fs";
115534
116059
  import { dirname as dirname37, join as join82 } from "node:path";
115535
116060
  import { homedir as homedir13, userInfo as userInfo2 } from "node:os";
115536
116061
  function xml2(s) {
@@ -115585,6 +116110,36 @@ ${env}
115585
116110
  </plist>
115586
116111
  `;
115587
116112
  }
116113
+ const loaded = async () => {
116114
+ try {
116115
+ await run5("launchctl", ["print", serviceTarget()]);
116116
+ return true;
116117
+ } catch {
116118
+ return false;
116119
+ }
116120
+ };
116121
+ const untilGone = async () => {
116122
+ for (let i = 0; i < 25; i++) {
116123
+ if (!await loaded())
116124
+ return;
116125
+ await new Promise((r) => setTimeout(r, 200));
116126
+ }
116127
+ };
116128
+ const bootstrapWithRetry = async () => {
116129
+ let last;
116130
+ for (let i = 0; i < 5; i++) {
116131
+ try {
116132
+ await run5("launchctl", ["bootstrap", domainTarget(), plistPath()]);
116133
+ return;
116134
+ } catch (e) {
116135
+ last = e;
116136
+ if (await loaded())
116137
+ return;
116138
+ await new Promise((r) => setTimeout(r, 300 * (i + 1)));
116139
+ }
116140
+ }
116141
+ throw last instanceof Error ? last : new Error(String(last));
116142
+ };
115588
116143
  return {
115589
116144
  async install(spec) {
115590
116145
  mkdirSync42(workDirOf(spec), { recursive: true });
@@ -115593,16 +116148,20 @@ ${env}
115593
116148
  writeFileSync43(plistPath(), buildPlist2(spec));
115594
116149
  await run5("launchctl", ["bootout", serviceTarget()]).catch(() => {
115595
116150
  });
115596
- await run5("launchctl", ["bootstrap", domainTarget(), plistPath()]);
116151
+ await untilGone();
116152
+ await bootstrapWithRetry();
115597
116153
  await run5("launchctl", ["enable", serviceTarget()]).catch(() => {
115598
116154
  });
115599
116155
  await run5("launchctl", ["kickstart", "-k", serviceTarget()]).catch(() => {
115600
116156
  });
116157
+ if (!await loaded()) {
116158
+ throw new Error(`the service ${label} is not loaded after install \u2014 check \`launchctl print ${serviceTarget()}\``);
116159
+ }
115601
116160
  },
115602
116161
  async uninstall() {
115603
116162
  await run5("launchctl", ["bootout", serviceTarget()]).catch(() => {
115604
116163
  });
115605
- if (existsSync43(plistPath())) {
116164
+ if (existsSync44(plistPath())) {
115606
116165
  try {
115607
116166
  unlinkSync7(plistPath());
115608
116167
  } catch {
@@ -115613,7 +116172,7 @@ ${env}
115613
116172
  await this.install(spec);
115614
116173
  },
115615
116174
  async isInstalled() {
115616
- return existsSync43(plistPath());
116175
+ return existsSync44(plistPath());
115617
116176
  },
115618
116177
  async start() {
115619
116178
  await run5("launchctl", ["bootstrap", domainTarget(), plistPath()]).catch(() => {
@@ -115630,7 +116189,7 @@ ${env}
115630
116189
  },
115631
116190
  async logs(follow) {
115632
116191
  const f = logFile();
115633
- if (!existsSync43(f)) {
116192
+ if (!existsSync44(f)) {
115634
116193
  console.log(`No log yet at ${f} \u2014 has the service started?`);
115635
116194
  return;
115636
116195
  }
@@ -115641,7 +116200,7 @@ ${env}
115641
116200
 
115642
116201
  // ../../packages/host-core/dist/service/systemd.js
115643
116202
  init_credentials();
115644
- import { writeFileSync as writeFileSync44, existsSync as existsSync44, mkdirSync as mkdirSync43, unlinkSync as unlinkSync8 } from "node:fs";
116203
+ import { writeFileSync as writeFileSync44, existsSync as existsSync45, mkdirSync as mkdirSync43, unlinkSync as unlinkSync8 } from "node:fs";
115645
116204
  import { dirname as dirname38, join as join83 } from "node:path";
115646
116205
  import { homedir as homedir14, userInfo as userInfo3 } from "node:os";
115647
116206
  function unitVal2(v) {
@@ -115697,7 +116256,7 @@ WantedBy=default.target
115697
116256
  async uninstall() {
115698
116257
  await run5("systemctl", ["--user", "disable", "--now", unit]).catch(() => {
115699
116258
  });
115700
- if (existsSync44(unitPath())) {
116259
+ if (existsSync45(unitPath())) {
115701
116260
  try {
115702
116261
  unlinkSync8(unitPath());
115703
116262
  } catch {
@@ -115710,7 +116269,7 @@ WantedBy=default.target
115710
116269
  await this.install(spec);
115711
116270
  },
115712
116271
  async isInstalled() {
115713
- return existsSync44(unitPath());
116272
+ return existsSync45(unitPath());
115714
116273
  },
115715
116274
  async start() {
115716
116275
  await run5("systemctl", ["--user", "start", unit]);
@@ -115723,7 +116282,7 @@ WantedBy=default.target
115723
116282
  },
115724
116283
  async logs(follow) {
115725
116284
  const f = join83(daemonDir2(), "daemon.log");
115726
- if (existsSync44(f)) {
116285
+ if (existsSync45(f)) {
115727
116286
  await runInherit("tail", follow ? ["-f", f] : ["-n", "200", f]);
115728
116287
  return;
115729
116288
  }
@@ -115735,8 +116294,20 @@ WantedBy=default.target
115735
116294
 
115736
116295
  // ../../packages/host-core/dist/service/windows.js
115737
116296
  init_credentials();
115738
- import { writeFileSync as writeFileSync45, existsSync as existsSync45, mkdirSync as mkdirSync44, unlinkSync as unlinkSync9 } from "node:fs";
116297
+ import { writeFileSync as writeFileSync45, existsSync as existsSync46, mkdirSync as mkdirSync44, unlinkSync as unlinkSync9, readFileSync as readFileSync52 } from "node:fs";
115739
116298
  import { dirname as dirname39, join as join84 } from "node:path";
116299
+ function publishedDaemonPid(dir2) {
116300
+ for (const f of ["status.json", "daemon.lock"]) {
116301
+ try {
116302
+ const raw = readFileSync52(join84(dir2, f), "utf-8");
116303
+ const pid = f === "status.json" ? Number(JSON.parse(raw).pid) : Number(raw.trim());
116304
+ if (Number.isInteger(pid) && pid > 0)
116305
+ return pid;
116306
+ } catch {
116307
+ }
116308
+ }
116309
+ return null;
116310
+ }
115740
116311
  function makeWindowsTask(instanceId) {
115741
116312
  const suffix = instanceSuffix(instanceId);
115742
116313
  const task = `Fineguide\\TesseractHost${suffix ? `-${suffix}` : ""}`;
@@ -115768,14 +116339,20 @@ start "" /b ${cmd} >> "${oneLine2(spec.logPath).replace(/"/g, "")}" 2>&1\r
115768
116339
  async uninstall() {
115769
116340
  await run5("schtasks", ["/Delete", "/TN", task, "/F"]).catch(() => {
115770
116341
  });
115771
- if (existsSync45(wrapperPath())) {
116342
+ if (existsSync46(wrapperPath())) {
115772
116343
  try {
115773
116344
  unlinkSync9(wrapperPath());
115774
116345
  } catch {
115775
116346
  }
115776
116347
  }
115777
116348
  },
116349
+ // STOP FIRST. `install` alone re-creates the task and runs it, which on Windows spawns a
116350
+ // SECOND daemon beside the one that is still running (see stop()); the newcomer finds the
116351
+ // pid lock held and stands down, so "restart" restarted nothing — the old process kept
116352
+ // running the old code. That is how a machine upgraded to a fixed CLI kept exhibiting the
116353
+ // bug the upgrade fixed, and how `tesseract-agent restart` reported success while doing it.
115778
116354
  async restart(spec) {
116355
+ await this.stop();
115779
116356
  await this.install(spec);
115780
116357
  },
115781
116358
  async isInstalled() {
@@ -115789,16 +116366,54 @@ start "" /b ${cmd} >> "${oneLine2(spec.logPath).replace(/"/g, "")}" 2>&1\r
115789
116366
  async start() {
115790
116367
  await run5("schtasks", ["/Run", "/TN", task]);
115791
116368
  },
116369
+ /**
116370
+ * Stop the daemon. Not the task — the daemon.
116371
+ *
116372
+ * `schtasks /End` ends the TASK's process, and the task's process is the .cmd wrapper, which
116373
+ * exited the instant it ran `start "" /b` to detach the daemon. So /End ended a task that had
116374
+ * already finished, reported success, and the daemon ran on. Every `stop`, `restart` and
116375
+ * self-update on Windows has been a no-op against a running daemon since the wrapper was
116376
+ * written; observed directly as two node processes surviving stop, restart, and the task's
116377
+ * own /End, with start times from the original launch.
116378
+ *
116379
+ * The daemon publishes its own pid (status.json / daemon.lock), and that is what gets ended.
116380
+ * Its PARENT is ended instead when the parent is the CLI supervisor (`cli.js run`), because
116381
+ * killing only the worker makes the supervisor respawn it; `/T` takes the whole tree either
116382
+ * way. The parent is checked by command line before being touched: the lock can be held by a
116383
+ * process that is not ours to kill — the desktop app owning the machine — and a supervisor
116384
+ * that is not `cli.js` is left alone, worker only.
116385
+ *
116386
+ * Best-effort throughout, like the other controllers' stop: a daemon that is already gone
116387
+ * is the desired state, not an error.
116388
+ */
115792
116389
  async stop() {
115793
116390
  await run5("schtasks", ["/End", "/TN", task]).catch(() => {
115794
116391
  });
116392
+ const pid = publishedDaemonPid(daemonDir2());
116393
+ if (pid === null)
116394
+ return;
116395
+ let target = pid;
116396
+ try {
116397
+ const { stdout } = await run5("powershell", [
116398
+ "-NoProfile",
116399
+ "-Command",
116400
+ `$p = Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}"; if ($p) { $pp = Get-CimInstance Win32_Process -Filter "ProcessId = $($p.ParentProcessId)"; if ($pp) { "$($pp.ProcessId)|$($pp.CommandLine)" } }`
116401
+ ]);
116402
+ const [ppidRaw, cmd = ""] = String(stdout).trim().split("|");
116403
+ const ppid = Number(ppidRaw);
116404
+ if (Number.isInteger(ppid) && ppid > 0 && /cli\.js["']?\s+run\b/.test(cmd))
116405
+ target = ppid;
116406
+ } catch {
116407
+ }
116408
+ await run5("taskkill", ["/pid", String(target), "/T", "/F"]).catch(() => {
116409
+ });
115795
116410
  },
115796
116411
  async status() {
115797
116412
  await runInherit("schtasks", ["/Query", "/TN", task, "/V", "/FO", "LIST"]);
115798
116413
  },
115799
116414
  async logs(follow) {
115800
116415
  const f = logFile();
115801
- if (!existsSync45(f)) {
116416
+ if (!existsSync46(f)) {
115802
116417
  console.log(`No log yet at ${f} \u2014 has the task run?`);
115803
116418
  return;
115804
116419
  }
@@ -115830,7 +116445,7 @@ init_safe_kill();
115830
116445
  // src/auth.ts
115831
116446
  var import_qrcode_terminal = __toESM(require_main(), 1);
115832
116447
  import { randomUUID as randomUUID30 } from "node:crypto";
115833
- import { existsSync as existsSync46, rmSync as rmSync12 } from "node:fs";
116448
+ import { existsSync as existsSync47, rmSync as rmSync12 } from "node:fs";
115834
116449
  import { join as join85 } from "node:path";
115835
116450
 
115836
116451
  // src/config.ts
@@ -116033,7 +116648,7 @@ async function logout() {
116033
116648
  for (const f of ["credentials.json", "status.json", "daemon.lock"]) {
116034
116649
  try {
116035
116650
  const p = join85(daemonDir2(), f);
116036
- if (existsSync46(p)) rmSync12(p);
116651
+ if (existsSync47(p)) rmSync12(p);
116037
116652
  } catch {
116038
116653
  }
116039
116654
  }
@@ -116193,7 +116808,7 @@ function startSelfUpdater(current) {
116193
116808
  // src/service.ts
116194
116809
  import { join as join86 } from "node:path";
116195
116810
  import { platform as platform7 } from "node:os";
116196
- import { existsSync as existsSync47, readFileSync as readFileSync52 } from "node:fs";
116811
+ import { existsSync as existsSync48, readFileSync as readFileSync53 } from "node:fs";
116197
116812
  function controller(instanceId) {
116198
116813
  const c = getServiceController(instanceId);
116199
116814
  if (!c) {
@@ -116220,8 +116835,8 @@ var isAlive = (pid) => {
116220
116835
  function runningDaemonInfo() {
116221
116836
  const lock = join86(daemonDir2(), "daemon.lock");
116222
116837
  try {
116223
- if (!existsSync47(lock)) return null;
116224
- const pid = Number(readFileSync52(lock, "utf-8").trim());
116838
+ if (!existsSync48(lock)) return null;
116839
+ const pid = Number(readFileSync53(lock, "utf-8").trim());
116225
116840
  if (!pid || !isAlive(pid)) return null;
116226
116841
  return { pid, device: readCredentials()?.deviceId };
116227
116842
  } catch {
@@ -116255,7 +116870,7 @@ async function logs(instanceId, follow) {
116255
116870
  }
116256
116871
 
116257
116872
  // src/net.ts
116258
- import { readFileSync as readFileSync54 } from "node:fs";
116873
+ import { readFileSync as readFileSync55 } from "node:fs";
116259
116874
  import { randomBytes as randomBytes11 } from "node:crypto";
116260
116875
  import { hostname as hostname7 } from "node:os";
116261
116876
  import { resolve as resolve14 } from "node:path";
@@ -116320,7 +116935,7 @@ function readOrDie(label, p) {
116320
116935
  process.exit(1);
116321
116936
  }
116322
116937
  try {
116323
- return new Uint8Array(readFileSync54(resolve14(p)));
116938
+ return new Uint8Array(readFileSync55(resolve14(p)));
116324
116939
  } catch (e) {
116325
116940
  console.error(`net enroll: could not read --${label} ${p}: ${e instanceof Error ? e.message : e}`);
116326
116941
  process.exit(1);
@@ -116392,6 +117007,9 @@ async function netCommand(rest, flags) {
116392
117007
  const established = s.established ?? [];
116393
117008
  const relayed = s.relayed ?? [];
116394
117009
  console.log(`peers: ${s.peers ?? 0} known, ${established.length} with a live session`);
117010
+ if (typeof s.policyEpoch === "number") {
117011
+ console.log(`policy: epoch ${s.policyEpoch}${s.policyEpoch === 0 ? " \u2014 no policy yet, so NO traffic will pass" : ""}`);
117012
+ }
116395
117013
  if (relayed.length) {
116396
117014
  console.log(`relayed: ${relayed.length} (${relayed.join(", ")}) \u2014 these are NOT direct`);
116397
117015
  } else if (established.length) {
@@ -116871,7 +117489,7 @@ async function netCommand(rest, flags) {
116871
117489
  function pkgVersion() {
116872
117490
  try {
116873
117491
  const p = join88(dirname40(fileURLToPath8(import.meta.url)), "..", "package.json");
116874
- return JSON.parse(readFileSync55(p, "utf8")).version ?? "0.0.0";
117492
+ return JSON.parse(readFileSync56(p, "utf8")).version ?? "0.0.0";
116875
117493
  } catch {
116876
117494
  return "0.0.0";
116877
117495
  }
@@ -117117,7 +117735,7 @@ async function captureClaudeSubscription(flags) {
117117
117735
  });
117118
117736
  if (code !== 0) throw new Error(`Claude sign-in did not complete (exit ${code}).`);
117119
117737
  const file2 = join88(home, ".credentials.json");
117120
- if (existsSync49(file2)) return readFileSync55(file2, "utf8");
117738
+ if (existsSync50(file2)) return readFileSync56(file2, "utf8");
117121
117739
  if (process.platform === "darwin") {
117122
117740
  const value = execFileSync6("security", [
117123
117741
  "find-generic-password",
@@ -117199,7 +117817,7 @@ async function runnerCommand(rest, flags) {
117199
117817
  process.exit(1);
117200
117818
  }
117201
117819
  const oauthFilePath = typeof flags["oauth-file"] === "string" ? resolve15(flags["oauth-file"]) : void 0;
117202
- const oauthAuthFile = oauthFilePath ? readFileSync55(oauthFilePath, "utf8") : void 0;
117820
+ const oauthAuthFile = oauthFilePath ? readFileSync56(oauthFilePath, "utf8") : void 0;
117203
117821
  if (oauthAuthFile) {
117204
117822
  try {
117205
117823
  JSON.parse(oauthAuthFile);