tesseract-agent 0.11.1 → 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,6 +109819,7 @@ 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();
109822
109823
 
109823
109824
  // ../../packages/host-core/dist/mesh/bundle-gossip.js
109824
109825
  var BundleOfferLedger = class {
@@ -109850,15 +109851,423 @@ var BundleOfferLedger = class {
109850
109851
  }
109851
109852
  };
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
+ }
110261
+
109853
110262
  // ../../packages/host-core/dist/mesh/config.js
109854
110263
  init_credentials();
109855
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";
109856
- import { dirname as dirname31, join as join67 } from "node:path";
110265
+ import { dirname as dirname31, join as join68 } from "node:path";
109857
110266
  function meshDir() {
109858
- return join67(daemonDir2(), "mesh");
110267
+ return join68(daemonDir2(), "mesh");
109859
110268
  }
109860
110269
  function meshesDir() {
109861
- return join67(meshDir(), "meshes");
110270
+ return join68(meshDir(), "meshes");
109862
110271
  }
109863
110272
  var MESH_DIR_NAME_BYTES = 8;
109864
110273
  function meshDirName(genesisFp) {
@@ -109876,7 +110285,7 @@ function oneMeshDir(meshId) {
109876
110285
  if (!isMeshId(meshId)) {
109877
110286
  throw new Error(`not a mesh id: ${JSON.stringify(meshId.slice(0, 64))}`);
109878
110287
  }
109879
- return join67(meshesDir(), meshId);
110288
+ return join68(meshesDir(), meshId);
109880
110289
  }
109881
110290
  var LEGACY_MESH_ID = "legacy";
109882
110291
  var CERT_GENESIS_FP_OFFSET = 19;
@@ -109894,7 +110303,7 @@ var FILES = {
109894
110303
  local: "local.json"
109895
110304
  };
109896
110305
  function readIfPresent(name, dir2 = meshDir()) {
109897
- const p = join67(dir2, name);
110306
+ const p = join68(dir2, name);
109898
110307
  try {
109899
110308
  return existsSync33(p) ? new Uint8Array(readFileSync47(p)) : void 0;
109900
110309
  } catch {
@@ -109902,7 +110311,7 @@ function readIfPresent(name, dir2 = meshDir()) {
109902
110311
  }
109903
110312
  }
109904
110313
  function migrateSingleMesh() {
109905
- const legacyGenesis = join67(meshDir(), FILES.genesis);
110314
+ const legacyGenesis = join68(meshDir(), FILES.genesis);
109906
110315
  if (!existsSync33(legacyGenesis))
109907
110316
  return null;
109908
110317
  const id = meshIdFromCert(readIfPresent(FILES.cert)) ?? LEGACY_MESH_ID;
@@ -109911,11 +110320,11 @@ function migrateSingleMesh() {
109911
110320
  mkdirSync36(dest, { recursive: true, mode: 448 });
109912
110321
  chmodSync5(dest, 448);
109913
110322
  for (const f of Object.values(FILES)) {
109914
- const from = join67(meshDir(), f);
110323
+ const from = join68(meshDir(), f);
109915
110324
  if (existsSync33(from))
109916
- renameSync11(from, join67(dest, f));
110325
+ renameSync11(from, join68(dest, f));
109917
110326
  }
109918
- const key2 = join67(dest, FILES.key);
110327
+ const key2 = join68(dest, FILES.key);
109919
110328
  if (existsSync33(key2))
109920
110329
  chmodSync5(key2, 384);
109921
110330
  } catch {
@@ -109939,7 +110348,7 @@ function listMeshIds() {
109939
110348
  }
109940
110349
  function readLocal(meshId) {
109941
110350
  try {
109942
- return JSON.parse(readFileSync47(join67(oneMeshDir(meshId), FILES.local), "utf8"));
110351
+ return JSON.parse(readFileSync47(join68(oneMeshDir(meshId), FILES.local), "utf8"));
109943
110352
  } catch {
109944
110353
  return null;
109945
110354
  }
@@ -109947,7 +110356,7 @@ function readLocal(meshId) {
109947
110356
  function writeLocal(meshId, state3) {
109948
110357
  try {
109949
110358
  mkdirSync36(oneMeshDir(meshId), { recursive: true, mode: 448 });
109950
- 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 });
109951
110360
  } catch {
109952
110361
  }
109953
110362
  }
@@ -109980,7 +110389,7 @@ function loadMeshIdentity() {
109980
110389
  return first === void 0 ? null : loadMesh(first);
109981
110390
  }
109982
110391
  function writeSecret(name, bytes2, dir2 = meshDir()) {
109983
- const p = join67(dir2, name);
110392
+ const p = join68(dir2, name);
109984
110393
  writeFileSync35(p, bytes2, { mode: 384 });
109985
110394
  try {
109986
110395
  chmodSync5(p, 384);
@@ -109998,11 +110407,11 @@ function saveMeshIdentity(id) {
109998
110407
  chmodSync5(meshDir(), 448);
109999
110408
  } catch {
110000
110409
  }
110001
- writeFileSync35(join67(meshDir(), FILES.genesis), id.genesis);
110002
- writeFileSync35(join67(meshDir(), FILES.cert), id.cert);
110410
+ writeFileSync35(join68(meshDir(), FILES.genesis), id.genesis);
110411
+ writeFileSync35(join68(meshDir(), FILES.cert), id.cert);
110003
110412
  writeSecret(FILES.key, id.staticPrivate);
110004
110413
  if (id.bundle)
110005
- writeFileSync35(join67(meshDir(), FILES.bundle), id.bundle);
110414
+ writeFileSync35(join68(meshDir(), FILES.bundle), id.bundle);
110006
110415
  }
110007
110416
  function saveMesh(meshId, id) {
110008
110417
  const dir2 = oneMeshDir(meshId);
@@ -110019,13 +110428,13 @@ function saveMesh(meshId, id) {
110019
110428
  chmodSync5(dir2, 448);
110020
110429
  } catch {
110021
110430
  }
110022
- writeFileSync35(join67(dir2, FILES.genesis), id.genesis);
110023
- writeFileSync35(join67(dir2, FILES.cert), id.cert);
110431
+ writeFileSync35(join68(dir2, FILES.genesis), id.genesis);
110432
+ writeFileSync35(join68(dir2, FILES.cert), id.cert);
110024
110433
  writeSecret(FILES.key, id.staticPrivate, dir2);
110025
110434
  if (id.sigPrivate)
110026
110435
  writeSecret(FILES.sigKey, id.sigPrivate, dir2);
110027
110436
  if (id.bundle)
110028
- writeFileSync35(join67(dir2, FILES.bundle), id.bundle);
110437
+ writeFileSync35(join68(dir2, FILES.bundle), id.bundle);
110029
110438
  if (readLocal(meshId) === null)
110030
110439
  writeLocal(meshId, { joinedAt: Date.now() });
110031
110440
  }
@@ -110035,7 +110444,7 @@ function removeMesh(meshId) {
110035
110444
  var STATUS_REFRESH_MS = 5e3;
110036
110445
  var STATUS_STALE_MS = 2e4;
110037
110446
  function statusPath(meshId) {
110038
- 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");
110039
110448
  }
110040
110449
  function writeStatus(status2, meshId) {
110041
110450
  try {
@@ -110066,7 +110475,7 @@ function cacheBundle(bundle, meshId) {
110066
110475
  const id = meshId ?? listMeshIds()[0];
110067
110476
  const dir2 = id === void 0 ? meshDir() : oneMeshDir(id);
110068
110477
  mkdirSync36(dir2, { recursive: true, mode: 448 });
110069
- writeFileSync35(join67(dir2, FILES.bundle), bundle);
110478
+ writeFileSync35(join68(dir2, FILES.bundle), bundle);
110070
110479
  } catch {
110071
110480
  }
110072
110481
  }
@@ -110222,6 +110631,9 @@ function loadMeshCore() {
110222
110631
  }
110223
110632
  var meshCore = loadMeshCore();
110224
110633
  function meshEngineStatus() {
110634
+ if (process.env.CCB_MESH_IN_DAEMON !== "1" && existsSync34(netdEndpoint())) {
110635
+ return { available: true, reason: null };
110636
+ }
110225
110637
  return { available: meshCore.available === true, reason: meshCore.loadError ?? null };
110226
110638
  }
110227
110639
  var TICK_MS2 = 1e3;
@@ -110317,6 +110729,8 @@ var MeshNode = class {
110317
110729
  #engine = null;
110318
110730
  #sock = null;
110319
110731
  #timer = null;
110732
+ /** The policy refresh poll. See `bundle-refresh.ts` — this is what makes eviction real. */
110733
+ #refreshTimer = null;
110320
110734
  #unsubscribe = null;
110321
110735
  #identity = null;
110322
110736
  /** The policy bundle we hold, as bytes, so we can OFFER it to a peer that is behind.
@@ -110389,8 +110803,11 @@ var MeshNode = class {
110389
110803
  }
110390
110804
  status() {
110391
110805
  return {
110392
- available: meshCore.available,
110393
- 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,
110394
110811
  enrolled: this.#identity !== null || this.#loadIdentity() !== null,
110395
110812
  running: this.#engine !== null,
110396
110813
  port: this.#port,
@@ -110407,7 +110824,22 @@ var MeshNode = class {
110407
110824
  this.#established.has(hex6) ? this.#relayed.has(hex6) ? "relay" : "direct" : "none"
110408
110825
  ])),
110409
110826
  meter: this.#engine?.meter() ?? null,
110410
- 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 } : {},
110411
110843
  // Surfaced so `net status` can say "removed" rather than "not running". Those look
110412
110844
  // identical from outside and have completely different remedies.
110413
110845
  removed: this.#revoked
@@ -110429,7 +110861,8 @@ var MeshNode = class {
110429
110861
  reason: "this machine was removed from the network \u2014 ask an admin for a new invitation"
110430
110862
  };
110431
110863
  }
110432
- if (!meshCore.available) {
110864
+ const inNetd = process.env.CCB_MESH_IN_DAEMON !== "1" && existsSync34(netdEndpoint());
110865
+ if (!inNetd && !meshCore.available) {
110433
110866
  return { ok: false, reason: `mesh engine unavailable: ${meshCore.loadError ?? "no prebuilt"}` };
110434
110867
  }
110435
110868
  const id = this.#loadIdentity();
@@ -110439,20 +110872,46 @@ var MeshNode = class {
110439
110872
  reason: this.#wantMeshId === null ? "this machine is not enrolled in a mesh" : `this machine is not enrolled in mesh ${this.#wantMeshId}`
110440
110873
  };
110441
110874
  }
110442
- try {
110443
- this.#engine = new meshCore.MeshEngine(Buffer.from(id.genesis), Buffer.from(id.staticPrivate), Buffer.from(id.cert), 0, Buffer.alloc(16));
110444
- if (id.bundle) {
110445
- this.#engine.setBundle(Buffer.from(id.bundle), Date.now());
110446
- this.#bundle = id.bundle;
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 };
110447
110908
  }
110448
- } catch (e) {
110449
- this.#engine = null;
110450
- this.#lastError = e instanceof Error ? e.message : String(e);
110451
- return { ok: false, reason: this.#lastError };
110452
110909
  }
110453
110910
  this.#identity = id;
110454
- this.#openSocket();
110455
- this.#startLan();
110911
+ if (!inNetd) {
110912
+ this.#openSocket();
110913
+ this.#startLan();
110914
+ }
110456
110915
  this.#offRejoin = onMeshRejoin(() => this.#join());
110457
110916
  this.#publishStatus();
110458
110917
  this.#statusTimer = setInterval(() => this.#publishStatus(), STATUS_REFRESH_MS);
@@ -110460,6 +110919,9 @@ var MeshNode = class {
110460
110919
  this.#subscribe();
110461
110920
  this.#timer = setInterval(() => this.#tick(), TICK_MS2);
110462
110921
  this.#timer.unref?.();
110922
+ void this.#refreshBundle();
110923
+ this.#refreshTimer = setInterval(() => void this.#refreshBundle(), REFRESH_INTERVAL_MS);
110924
+ this.#refreshTimer.unref?.();
110463
110925
  return { ok: true };
110464
110926
  }
110465
110927
  /** Tear down. Idempotent, and safe to call from a signal handler. */
@@ -110468,6 +110930,16 @@ var MeshNode = class {
110468
110930
  clearInterval(this.#timer);
110469
110931
  this.#timer = null;
110470
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
+ }
110471
110943
  this.#unsubscribe?.();
110472
110944
  this.#unsubscribe = null;
110473
110945
  this.#offRejoin?.();
@@ -110567,19 +111039,27 @@ var MeshNode = class {
110567
111039
  staticPub: engine.localStaticPub(),
110568
111040
  port: () => this.#port,
110569
111041
  epoch: () => engine.policyEpoch(),
110570
- onPeer: (staticPub, address, port) => this.#learnLanPeer(staticPub, address, port)
111042
+ onPeer: (staticPub, address, port, epoch2) => this.#learnLanPeer(staticPub, address, port, epoch2)
110571
111043
  });
110572
111044
  this.#lan.start();
110573
111045
  } catch {
110574
111046
  this.#lan = null;
110575
111047
  }
110576
111048
  }
110577
- /** A beacon arrived. Treated exactly like a relay hint — a candidate, not a fact. */
110578
- #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) {
110579
111058
  const engine = this.#engine;
110580
111059
  if (!engine)
110581
111060
  return;
110582
111061
  const hex6 = staticPub.toString("hex");
111062
+ this.#offerBundleTo(hex6, epoch2);
110583
111063
  try {
110584
111064
  if (!this.#peers.has(hex6)) {
110585
111065
  engine.addPeer(staticPub, 0, Buffer.alloc(16));
@@ -110701,6 +111181,47 @@ var MeshNode = class {
110701
111181
  this.#lastError = e instanceof Error ? e.message : String(e);
110702
111182
  }
110703
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
+ }
110704
111225
  /**
110705
111226
  * Offer our policy bundle to a peer whose epoch is older than ours.
110706
111227
  *
@@ -110795,10 +111316,12 @@ var MeshNode = class {
110795
111316
  /** Flush everything the engine wants to send, and absorb its events. */
110796
111317
  #drain() {
110797
111318
  const engine = this.#engine;
110798
- const sock = this.#sock;
110799
- if (!engine || !sock)
111319
+ if (!engine)
110800
111320
  return;
111321
+ const sock = this.#sock;
110801
111322
  for (; ; ) {
111323
+ if (!sock)
111324
+ break;
110802
111325
  const item = engine.pollSend();
110803
111326
  if (!item)
110804
111327
  break;
@@ -110946,148 +111469,6 @@ function meshManager() {
110946
111469
  return singleton3;
110947
111470
  }
110948
111471
 
110949
- // ../../packages/host-core/dist/mesh/netd-client.js
110950
- import { connect as connect2 } from "node:net";
110951
- import { platform as platform3 } from "node:process";
110952
- var NETD_WIRE_VERSION = 1;
110953
- var MAX_LINE = 1024 * 1024;
110954
- var TIMEOUT_MS3 = 15e3;
110955
- function netdEndpoint() {
110956
- if (platform3 === "win32")
110957
- return "\\\\.\\pipe\\tesseract-netd";
110958
- if (platform3 === "darwin")
110959
- return "/var/run/tesseract-netd.sock";
110960
- return "/run/tesseract-netd.sock";
110961
- }
110962
- function endpointOverride() {
110963
- return process.env.CCB_NETD_SOCKET ?? null;
110964
- }
110965
- function fail2(code, detail) {
110966
- return { ok: false, code, detail };
110967
- }
110968
- async function call(line) {
110969
- const endpoint2 = endpointOverride() ?? netdEndpoint();
110970
- return new Promise((resolve16) => {
110971
- let settled = false;
110972
- const done = (r) => {
110973
- if (settled)
110974
- return;
110975
- settled = true;
110976
- try {
110977
- sock.destroy();
110978
- } catch {
110979
- }
110980
- resolve16(r);
110981
- };
110982
- let sock;
110983
- try {
110984
- sock = connect2(endpoint2);
110985
- } catch (e) {
110986
- return resolve16(fail2("not-installed", e instanceof Error ? e.message : String(e)));
110987
- }
110988
- const timer2 = setTimeout(() => {
110989
- done(fail2("timeout", `no answer from the network helper in ${TIMEOUT_MS3}ms`));
110990
- }, TIMEOUT_MS3);
110991
- timer2.unref?.();
110992
- sock.setTimeout(TIMEOUT_MS3);
110993
- sock.on("timeout", () => {
110994
- clearTimeout(timer2);
110995
- done(fail2("timeout", "socket idle"));
110996
- });
110997
- sock.on("error", (e) => {
110998
- clearTimeout(timer2);
110999
- const absent = e.code === "ENOENT" || e.code === "ECONNREFUSED";
111000
- done(fail2(absent ? "not-installed" : "io", e.message));
111001
- });
111002
- let buf = "";
111003
- sock.on("data", (chunk) => {
111004
- buf += chunk.toString("utf8");
111005
- if (buf.length > MAX_LINE) {
111006
- clearTimeout(timer2);
111007
- done(fail2("protocol", "the helper sent an oversized line"));
111008
- return;
111009
- }
111010
- const nl = buf.indexOf("\n");
111011
- if (nl === -1)
111012
- return;
111013
- clearTimeout(timer2);
111014
- done({ ok: true, line: buf.slice(0, nl) });
111015
- });
111016
- sock.on("close", () => {
111017
- clearTimeout(timer2);
111018
- done(fail2("protocol", "the helper closed the connection without answering"));
111019
- });
111020
- sock.on("connect", () => {
111021
- sock.write(`${line}
111022
- `);
111023
- });
111024
- });
111025
- }
111026
- function parse3(line) {
111027
- let body;
111028
- try {
111029
- body = JSON.parse(line);
111030
- } catch {
111031
- return fail2("protocol", "the helper sent something that is not JSON");
111032
- }
111033
- if (typeof body !== "object" || body === null) {
111034
- return fail2("protocol", "the helper sent something that is not an object");
111035
- }
111036
- const o = body;
111037
- if (o.ok === false) {
111038
- const code = typeof o.code === "string" ? o.code : "refused";
111039
- const detail = typeof o.detail === "string" ? o.detail : "the helper refused";
111040
- return fail2(code, detail);
111041
- }
111042
- if (o.ok !== true)
111043
- return fail2("protocol", "the helper did not say whether it succeeded");
111044
- return { ok: true, body: o };
111045
- }
111046
- async function request(line) {
111047
- const r = await call(line);
111048
- return r.ok ? parse3(r.line) : r;
111049
- }
111050
- function hex4(b) {
111051
- return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
111052
- }
111053
- async function netdHello() {
111054
- const r = await request(JSON.stringify({ op: "hello", wireVersion: NETD_WIRE_VERSION }));
111055
- if (!r.ok)
111056
- return r;
111057
- const v = r.body.wireVersion;
111058
- if (typeof v !== "number")
111059
- return fail2("protocol", "the helper did not report a wire version");
111060
- if (v !== NETD_WIRE_VERSION) {
111061
- return fail2("wire-version", `the helper speaks ${v}, this build speaks ${NETD_WIRE_VERSION}`);
111062
- }
111063
- return { ok: true, wireVersion: v };
111064
- }
111065
- async function netdStatus() {
111066
- const r = await request(JSON.stringify({ op: "status" }));
111067
- if (!r.ok)
111068
- return r;
111069
- const meshes = Array.isArray(r.body.meshes) ? r.body.meshes.filter((m) => typeof m === "string") : [];
111070
- const iface = typeof r.body.interface === "string" ? r.body.interface : null;
111071
- return { ok: true, meshes, interface: iface };
111072
- }
111073
- async function netdUp(args) {
111074
- const r = await request(JSON.stringify({
111075
- op: "up",
111076
- meshId: args.meshId,
111077
- genesis: hex4(args.genesis),
111078
- cert: hex4(args.cert),
111079
- fullTunnel: args.fullTunnel === true
111080
- }));
111081
- return r.ok ? { ok: true } : r;
111082
- }
111083
- async function netdDown(meshId) {
111084
- const r = await request(JSON.stringify({ op: "down", meshId }));
111085
- return r.ok ? { ok: true } : r;
111086
- }
111087
- async function netdAvailable() {
111088
- return (await netdHello()).ok;
111089
- }
111090
-
111091
111472
  // ../../packages/host-core/dist/mesh/route-sync.js
111092
111473
  async function syncRoutes(running2, log9 = () => {
111093
111474
  }) {
@@ -111116,7 +111497,14 @@ async function syncRoutes(running2, log9 = () => {
111116
111497
  // The per-machine choice, and `=== true` rather than a truthiness check: an absent or
111117
111498
  // malformed field must read as NO. netd unions this across meshes itself — any mesh
111118
111499
  // asking for a full tunnel makes it one for the machine — so this stays per mesh here.
111119
- 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 } : {}
111120
111508
  });
111121
111509
  if (r.ok) {
111122
111510
  out.up.push(meshId);
@@ -111142,22 +111530,63 @@ async function syncRoutes(running2, log9 = () => {
111142
111530
  }
111143
111531
 
111144
111532
  // ../../packages/host-core/dist/mesh/start-meshes.js
111145
- function startMeshes(log9) {
111533
+ var ROUTE_RETRY_MS = 6e4;
111534
+ function startMeshes(log9, opts = {}) {
111146
111535
  const running2 = startNodes(log9);
111147
- 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
+ }
111148
111548
  if (r.helperAbsent) {
111149
- log9("routes: the network helper is not installed \u2014 the overlay carries no traffic yet");
111150
- 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;
111151
111554
  }
111152
111555
  if (r.up.length)
111153
111556
  log9(`routes: installed for ${r.up.join(", ")}`);
111154
111557
  if (r.down.length)
111155
111558
  log9(`routes: removed for ${r.down.join(", ")}`);
111156
- for (const f of r.failed)
111157
- log9(`routes: ${f.meshId} refused \u2014 ${f.reason}`);
111158
- }).catch((e) => {
111159
- 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();
111160
111580
  });
111581
+ return {
111582
+ stop() {
111583
+ stopped2 = true;
111584
+ if (timer2) {
111585
+ clearTimeout(timer2);
111586
+ timer2 = null;
111587
+ }
111588
+ }
111589
+ };
111161
111590
  }
111162
111591
  function startNodes(log9) {
111163
111592
  const ids = listMeshIds();
@@ -112487,7 +112916,7 @@ function appendRdeskAudit(e) {
112487
112916
 
112488
112917
  // ../../packages/host-core/dist/fileprovider-bridge.js
112489
112918
  init_managed_spawn();
112490
- 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";
112491
112920
  import { homedir as homedir10 } from "node:os";
112492
112921
  import { join as join73 } from "node:path";
112493
112922
  init_safe_kill();
@@ -112658,7 +113087,7 @@ function stopFileProviderBridge() {
112658
113087
 
112659
113088
  // ../../packages/host-core/dist/vault/autofill-bridge.js
112660
113089
  import { randomBytes as randomBytes9 } from "node:crypto";
112661
- 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";
112662
113091
  import { homedir as homedir11 } from "node:os";
112663
113092
  import { join as join74 } from "node:path";
112664
113093
 
@@ -112733,7 +113162,7 @@ function writeBridgeInfo(port, token3, log9) {
112733
113162
  return;
112734
113163
  const containerData = appexContainerData();
112735
113164
  try {
112736
- if (!existsSync35(containerData)) {
113165
+ if (!existsSync36(containerData)) {
112737
113166
  if (!infoLoggedOnce) {
112738
113167
  infoLoggedOnce = true;
112739
113168
  log9("autofill bridge-info SKIP: no appex container yet at", containerData);
@@ -112982,7 +113411,7 @@ function createNodePowerController() {
112982
113411
 
112983
113412
  // ../../packages/host-core/dist/native-host.js
112984
113413
  import { execFileSync as execFileSync4 } from "node:child_process";
112985
- 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";
112986
113415
  import { homedir as homedir12, platform as platform5 } from "node:os";
112987
113416
  import { dirname as dirname34, join as join75, resolve as resolve12 } from "node:path";
112988
113417
  import { fileURLToPath as fileURLToPath6 } from "node:url";
@@ -113013,7 +113442,7 @@ function resolveBridge(log9) {
113013
113442
  } catch {
113014
113443
  }
113015
113444
  for (const c of candidates)
113016
- if (c && existsSync36(c))
113445
+ if (c && existsSync37(c))
113017
113446
  return c;
113018
113447
  log9("companion native-host: bridge not found; skipping registration (looked in", candidates.filter(Boolean).join(" , "), ")");
113019
113448
  return null;
@@ -113055,7 +113484,7 @@ function registerNativeHost(opts) {
113055
113484
  return registerWindows(opts.daemonDir, manifestJson, log9);
113056
113485
  let wrote = 0;
113057
113486
  for (const profile of browserProfileDirs()) {
113058
- if (!existsSync36(profile))
113487
+ if (!existsSync37(profile))
113059
113488
  continue;
113060
113489
  try {
113061
113490
  const dir2 = join75(profile, "NativeMessagingHosts");
@@ -113091,7 +113520,7 @@ init_daemon_settings();
113091
113520
 
113092
113521
  // ../../packages/host-core/dist/activity/activity-tracker.js
113093
113522
  init_credentials();
113094
- 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";
113095
113524
  import { join as join76 } from "node:path";
113096
113525
  function activityProfilePath() {
113097
113526
  return join76(daemonDir2(), "activity-profile.json");
@@ -113169,7 +113598,7 @@ function createActivityTracker(opts = {}) {
113169
113598
  return;
113170
113599
  const key2 = front.bundleId || front.name;
113171
113600
  await ensureLoaded();
113172
- if (profile && Object.keys(profile.apps).length && !existsSync37(activityProfilePath())) {
113601
+ if (profile && Object.keys(profile.apps).length && !existsSync38(activityProfilePath())) {
113173
113602
  log9("activity profile cleared externally \u2014 resetting in-memory");
113174
113603
  profile = { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), apps: {} };
113175
113604
  }
@@ -113402,7 +113831,7 @@ function backoff2() {
113402
113831
 
113403
113832
  // ../../packages/host-core/dist/daemon-runtime.js
113404
113833
  var daemonClipboardSync = null;
113405
- var REFRESH_INTERVAL_MS = 12 * 60 * 60 * 1e3;
113834
+ var REFRESH_INTERVAL_MS2 = 12 * 60 * 60 * 1e3;
113406
113835
  async function runDaemon(opts = {}) {
113407
113836
  const VERSION3 = opts.version ?? process.env.CCB_DAEMON_VERSION ?? "dev";
113408
113837
  const TAG = opts.tag ?? "host-daemon";
@@ -113443,7 +113872,7 @@ async function runDaemon(opts = {}) {
113443
113872
  function acquireLock2() {
113444
113873
  const f = pidfile();
113445
113874
  try {
113446
- if (!existsSync38(daemonDir2()))
113875
+ if (!existsSync39(daemonDir2()))
113447
113876
  mkdirSync41(daemonDir2(), { recursive: true, mode: 448 });
113448
113877
  } catch {
113449
113878
  }
@@ -113469,7 +113898,7 @@ async function runDaemon(opts = {}) {
113469
113898
  function releaseLock() {
113470
113899
  try {
113471
113900
  const f = pidfile();
113472
- if (existsSync38(f) && Number(readFileSync49(f, "utf-8").trim()) === process.pid)
113901
+ if (existsSync39(f) && Number(readFileSync49(f, "utf-8").trim()) === process.pid)
113473
113902
  unlinkSync6(f);
113474
113903
  } catch {
113475
113904
  }
@@ -113511,6 +113940,7 @@ async function runDaemon(opts = {}) {
113511
113940
  }
113512
113941
  }
113513
113942
  let stopGatewayKeyRefresh = null;
113943
+ let meshRoutes = null;
113514
113944
  let timer2 = null;
113515
113945
  let identityWatch = null;
113516
113946
  let refreshRetry = null;
@@ -113605,6 +114035,10 @@ async function runDaemon(opts = {}) {
113605
114035
  stopGatewayKeyRefresh();
113606
114036
  stopGatewayKeyRefresh = null;
113607
114037
  }
114038
+ if (meshRoutes) {
114039
+ meshRoutes.stop();
114040
+ meshRoutes = null;
114041
+ }
113608
114042
  try {
113609
114043
  offRecovery?.();
113610
114044
  offRecovery = null;
@@ -114296,7 +114730,7 @@ async function runDaemon(opts = {}) {
114296
114730
  } catch {
114297
114731
  }
114298
114732
  try {
114299
- startMeshes(log9);
114733
+ meshRoutes = startMeshes(log9);
114300
114734
  } catch {
114301
114735
  }
114302
114736
  writeStatus2({ deviceId: c.deviceId, connected: true });
@@ -114575,7 +115009,7 @@ async function runDaemon(opts = {}) {
114575
115009
  const daemonLogFile = join77(daemonDir2(), "daemon.log");
114576
115010
  const capTimer = setInterval(() => {
114577
115011
  try {
114578
- if (existsSync38(daemonLogFile) && statSync14(daemonLogFile).size > DAEMON_LOG_CAP) {
115012
+ if (existsSync39(daemonLogFile) && statSync14(daemonLogFile).size > DAEMON_LOG_CAP) {
114579
115013
  const buf = readFileSync49(daemonLogFile, "utf-8");
114580
115014
  const kept = `\u2026[daemon.log capped at ${(/* @__PURE__ */ new Date()).toISOString()}]\u2026
114581
115015
  ` + buf.slice(-DAEMON_LOG_CAP / 2);
@@ -114592,7 +115026,7 @@ async function runDaemon(opts = {}) {
114592
115026
  startAutofillBridge(log9);
114593
115027
  }
114594
115028
  if (ownsMachine)
114595
- startMeshes(log9);
115029
+ meshRoutes = startMeshes(log9);
114596
115030
  if (ownsMachine) {
114597
115031
  try {
114598
115032
  stopGatewayKeyRefresh = startGatewayKeyRefresh();
@@ -114604,7 +115038,7 @@ async function runDaemon(opts = {}) {
114604
115038
  await refresh();
114605
115039
  timer2 = setInterval(() => {
114606
115040
  void refresh();
114607
- }, REFRESH_INTERVAL_MS);
115041
+ }, REFRESH_INTERVAL_MS2);
114608
115042
  }
114609
115043
 
114610
115044
  // ../../packages/host-core/dist/host-daemon-entry.js
@@ -114714,7 +115148,7 @@ async function evictNode(args) {
114714
115148
  const r = await call2(`/meshes/${encodeURIComponent(args.meshId)}/nodes/${encodeURIComponent(args.nodeId)}`, { method: "DELETE", body: {} });
114715
115149
  return r.ok ? { ok: true, epoch: r.data.epoch } : r;
114716
115150
  }
114717
- async function enrolWith(requested, post) {
115151
+ async function enrolWith(requested, post, origin) {
114718
115152
  const name = normaliseNodeName(requested);
114719
115153
  if (!name) {
114720
115154
  return fail3("bad-name", `"${requested}" cannot be a machine name \u2014 names are DNS labels, so letters, digits and hyphens only`);
@@ -114738,7 +115172,7 @@ async function enrolWith(requested, post) {
114738
115172
  }
114739
115173
  try {
114740
115174
  saveMesh(meshId, { genesis, cert, staticPrivate, sigPrivate });
114741
- writeLocal(meshId, { joinedAt: Date.now(), enabled: true });
115175
+ writeLocal(meshId, { joinedAt: Date.now(), enabled: true, ...origin ? { origin } : {} });
114742
115176
  if (r.data.bundle) {
114743
115177
  try {
114744
115178
  cacheBundle(fromHexOrB64(r.data.bundle), meshId);
@@ -114760,16 +115194,24 @@ async function enrolWith(requested, post) {
114760
115194
  };
114761
115195
  }
114762
115196
  async function enrol(args) {
114763
- return enrolWith(args.name, (staticPub, sigPub, name) => call2(`/meshes/${encodeURIComponent(args.meshId)}/nodes`, {
114764
- method: "POST",
114765
- body: { name, staticPub, sigPub, ...args.exitNode === true ? { exitNode: true } : {} }
114766
- }));
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
+ );
114767
115206
  }
114768
115207
  async function joinWithToken(args) {
114769
- 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;
114770
115212
  }
114771
115213
  async function postJoin(origin, body) {
114772
- const base = origin ?? readCredentials()?.apiOrigin;
115214
+ const base = resolveOrigin(origin);
114773
115215
  if (!base) {
114774
115216
  return fail3("not-signed-in", "this machine has never signed in \u2014 pass --origin https://\u2026 so it knows which server to ask");
114775
115217
  }
@@ -114797,7 +115239,7 @@ async function postJoin(origin, body) {
114797
115239
  return { ok: true, data: JSON.parse(text12) };
114798
115240
  }
114799
115241
  async function enrolWithCode(args) {
114800
- 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));
114801
115243
  }
114802
115244
  async function rawPrivate(key2) {
114803
115245
  const pkcs8 = new Uint8Array(await crypto.subtle.exportKey("pkcs8", key2));
@@ -114881,13 +115323,13 @@ init_credentials();
114881
115323
 
114882
115324
  // ../../packages/host-core/dist/net-service/ensure-installed.js
114883
115325
  import { execFile as execFile14 } from "node:child_process";
114884
- import { existsSync as existsSync42 } from "node:fs";
115326
+ import { existsSync as existsSync43 } from "node:fs";
114885
115327
  import { dirname as dirname36, join as join81 } from "node:path";
114886
115328
  import { promisify as promisify10 } from "node:util";
114887
115329
 
114888
115330
  // ../../packages/host-core/dist/net-service/launchd-system.js
114889
115331
  import { spawn as spawn9 } from "node:child_process";
114890
- 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";
114891
115333
  import { tmpdir as tmpdir6 } from "node:os";
114892
115334
  import { join as join78 } from "node:path";
114893
115335
 
@@ -115058,7 +115500,7 @@ ${plist}TESSERACT_PLIST_EOF`,
115058
115500
  };
115059
115501
  },
115060
115502
  async state() {
115061
- if (!existsSync39(PLIST))
115503
+ if (!existsSync40(PLIST))
115062
115504
  return { installed: false, running: false };
115063
115505
  const owner = parseOwnerUid(readFileSync50(PLIST, "utf8"));
115064
115506
  const running2 = await netdAvailable();
@@ -115078,7 +115520,7 @@ ${plist}TESSERACT_PLIST_EOF`,
115078
115520
 
115079
115521
  // ../../packages/host-core/dist/net-service/systemd-system.js
115080
115522
  import { spawn as spawn11 } from "node:child_process";
115081
- import { existsSync as existsSync40, readFileSync as readFileSync51 } from "node:fs";
115523
+ import { existsSync as existsSync41, readFileSync as readFileSync51 } from "node:fs";
115082
115524
 
115083
115525
  // ../../packages/host-core/dist/service/exec.js
115084
115526
  import { execFile as execFile11, spawn as spawn10 } from "node:child_process";
@@ -115221,7 +115663,7 @@ function makeSystemdSystem() {
115221
115663
  return code === 0 ? { ok: true } : { ok: false, detail: `removing the helper failed (exit ${code})` };
115222
115664
  },
115223
115665
  async state() {
115224
- if (!existsSync40(UNIT_PATH))
115666
+ if (!existsSync41(UNIT_PATH))
115225
115667
  return { installed: false, running: false };
115226
115668
  const owner = parseOwnerUid(readFileSync51(UNIT_PATH, "utf8"));
115227
115669
  const active = await tryRun("systemctl", ["is-active", UNIT]);
@@ -115484,7 +115926,7 @@ function makeNetService() {
115484
115926
  }
115485
115927
 
115486
115928
  // ../../packages/host-core/dist/net-service/netd-binary.js
115487
- import { existsSync as existsSync41 } from "node:fs";
115929
+ import { existsSync as existsSync42 } from "node:fs";
115488
115930
  import { dirname as dirname35, join as join80 } from "node:path";
115489
115931
  import { fileURLToPath as fileURLToPath7 } from "node:url";
115490
115932
  function netdBinaryName() {
@@ -115492,7 +115934,7 @@ function netdBinaryName() {
115492
115934
  }
115493
115935
  function override2() {
115494
115936
  const p = process.env.CCB_NETD_BIN;
115495
- return p && existsSync41(p) ? p : null;
115937
+ return p && existsSync42(p) ? p : null;
115496
115938
  }
115497
115939
  function netdBinaryPath() {
115498
115940
  const fromEnv = override2();
@@ -115519,7 +115961,7 @@ function netdBinaryPath() {
115519
115961
  candidates.push(join80(process.cwd(), "crates", "target", "release", name));
115520
115962
  for (const c of candidates) {
115521
115963
  try {
115522
- if (existsSync41(c))
115964
+ if (existsSync42(c))
115523
115965
  return c;
115524
115966
  } catch {
115525
115967
  }
@@ -115561,7 +116003,7 @@ async function ensureNetHelperInstalled() {
115561
116003
  if (st.installed && !mismatched)
115562
116004
  return { ok: true, already: true };
115563
116005
  const exec2 = netdBinaryPath();
115564
- if (exec2 && process.platform === "win32" && !existsSync42(join81(dirname36(exec2), "wintun.dll"))) {
116006
+ if (exec2 && process.platform === "win32" && !existsSync43(join81(dirname36(exec2), "wintun.dll"))) {
115565
116007
  return {
115566
116008
  ok: false,
115567
116009
  reason: "no-binary",
@@ -115613,7 +116055,7 @@ import { platform as platform6 } from "node:os";
115613
116055
 
115614
116056
  // ../../packages/host-core/dist/service/launchd.js
115615
116057
  init_credentials();
115616
- 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";
115617
116059
  import { dirname as dirname37, join as join82 } from "node:path";
115618
116060
  import { homedir as homedir13, userInfo as userInfo2 } from "node:os";
115619
116061
  function xml2(s) {
@@ -115668,6 +116110,36 @@ ${env}
115668
116110
  </plist>
115669
116111
  `;
115670
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
+ };
115671
116143
  return {
115672
116144
  async install(spec) {
115673
116145
  mkdirSync42(workDirOf(spec), { recursive: true });
@@ -115676,16 +116148,20 @@ ${env}
115676
116148
  writeFileSync43(plistPath(), buildPlist2(spec));
115677
116149
  await run5("launchctl", ["bootout", serviceTarget()]).catch(() => {
115678
116150
  });
115679
- await run5("launchctl", ["bootstrap", domainTarget(), plistPath()]);
116151
+ await untilGone();
116152
+ await bootstrapWithRetry();
115680
116153
  await run5("launchctl", ["enable", serviceTarget()]).catch(() => {
115681
116154
  });
115682
116155
  await run5("launchctl", ["kickstart", "-k", serviceTarget()]).catch(() => {
115683
116156
  });
116157
+ if (!await loaded()) {
116158
+ throw new Error(`the service ${label} is not loaded after install \u2014 check \`launchctl print ${serviceTarget()}\``);
116159
+ }
115684
116160
  },
115685
116161
  async uninstall() {
115686
116162
  await run5("launchctl", ["bootout", serviceTarget()]).catch(() => {
115687
116163
  });
115688
- if (existsSync43(plistPath())) {
116164
+ if (existsSync44(plistPath())) {
115689
116165
  try {
115690
116166
  unlinkSync7(plistPath());
115691
116167
  } catch {
@@ -115696,7 +116172,7 @@ ${env}
115696
116172
  await this.install(spec);
115697
116173
  },
115698
116174
  async isInstalled() {
115699
- return existsSync43(plistPath());
116175
+ return existsSync44(plistPath());
115700
116176
  },
115701
116177
  async start() {
115702
116178
  await run5("launchctl", ["bootstrap", domainTarget(), plistPath()]).catch(() => {
@@ -115713,7 +116189,7 @@ ${env}
115713
116189
  },
115714
116190
  async logs(follow) {
115715
116191
  const f = logFile();
115716
- if (!existsSync43(f)) {
116192
+ if (!existsSync44(f)) {
115717
116193
  console.log(`No log yet at ${f} \u2014 has the service started?`);
115718
116194
  return;
115719
116195
  }
@@ -115724,7 +116200,7 @@ ${env}
115724
116200
 
115725
116201
  // ../../packages/host-core/dist/service/systemd.js
115726
116202
  init_credentials();
115727
- 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";
115728
116204
  import { dirname as dirname38, join as join83 } from "node:path";
115729
116205
  import { homedir as homedir14, userInfo as userInfo3 } from "node:os";
115730
116206
  function unitVal2(v) {
@@ -115780,7 +116256,7 @@ WantedBy=default.target
115780
116256
  async uninstall() {
115781
116257
  await run5("systemctl", ["--user", "disable", "--now", unit]).catch(() => {
115782
116258
  });
115783
- if (existsSync44(unitPath())) {
116259
+ if (existsSync45(unitPath())) {
115784
116260
  try {
115785
116261
  unlinkSync8(unitPath());
115786
116262
  } catch {
@@ -115793,7 +116269,7 @@ WantedBy=default.target
115793
116269
  await this.install(spec);
115794
116270
  },
115795
116271
  async isInstalled() {
115796
- return existsSync44(unitPath());
116272
+ return existsSync45(unitPath());
115797
116273
  },
115798
116274
  async start() {
115799
116275
  await run5("systemctl", ["--user", "start", unit]);
@@ -115806,7 +116282,7 @@ WantedBy=default.target
115806
116282
  },
115807
116283
  async logs(follow) {
115808
116284
  const f = join83(daemonDir2(), "daemon.log");
115809
- if (existsSync44(f)) {
116285
+ if (existsSync45(f)) {
115810
116286
  await runInherit("tail", follow ? ["-f", f] : ["-n", "200", f]);
115811
116287
  return;
115812
116288
  }
@@ -115818,8 +116294,20 @@ WantedBy=default.target
115818
116294
 
115819
116295
  // ../../packages/host-core/dist/service/windows.js
115820
116296
  init_credentials();
115821
- 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";
115822
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
+ }
115823
116311
  function makeWindowsTask(instanceId) {
115824
116312
  const suffix = instanceSuffix(instanceId);
115825
116313
  const task = `Fineguide\\TesseractHost${suffix ? `-${suffix}` : ""}`;
@@ -115851,14 +116339,20 @@ start "" /b ${cmd} >> "${oneLine2(spec.logPath).replace(/"/g, "")}" 2>&1\r
115851
116339
  async uninstall() {
115852
116340
  await run5("schtasks", ["/Delete", "/TN", task, "/F"]).catch(() => {
115853
116341
  });
115854
- if (existsSync45(wrapperPath())) {
116342
+ if (existsSync46(wrapperPath())) {
115855
116343
  try {
115856
116344
  unlinkSync9(wrapperPath());
115857
116345
  } catch {
115858
116346
  }
115859
116347
  }
115860
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.
115861
116354
  async restart(spec) {
116355
+ await this.stop();
115862
116356
  await this.install(spec);
115863
116357
  },
115864
116358
  async isInstalled() {
@@ -115872,16 +116366,54 @@ start "" /b ${cmd} >> "${oneLine2(spec.logPath).replace(/"/g, "")}" 2>&1\r
115872
116366
  async start() {
115873
116367
  await run5("schtasks", ["/Run", "/TN", task]);
115874
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
+ */
115875
116389
  async stop() {
115876
116390
  await run5("schtasks", ["/End", "/TN", task]).catch(() => {
115877
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
+ });
115878
116410
  },
115879
116411
  async status() {
115880
116412
  await runInherit("schtasks", ["/Query", "/TN", task, "/V", "/FO", "LIST"]);
115881
116413
  },
115882
116414
  async logs(follow) {
115883
116415
  const f = logFile();
115884
- if (!existsSync45(f)) {
116416
+ if (!existsSync46(f)) {
115885
116417
  console.log(`No log yet at ${f} \u2014 has the task run?`);
115886
116418
  return;
115887
116419
  }
@@ -115913,7 +116445,7 @@ init_safe_kill();
115913
116445
  // src/auth.ts
115914
116446
  var import_qrcode_terminal = __toESM(require_main(), 1);
115915
116447
  import { randomUUID as randomUUID30 } from "node:crypto";
115916
- import { existsSync as existsSync46, rmSync as rmSync12 } from "node:fs";
116448
+ import { existsSync as existsSync47, rmSync as rmSync12 } from "node:fs";
115917
116449
  import { join as join85 } from "node:path";
115918
116450
 
115919
116451
  // src/config.ts
@@ -116116,7 +116648,7 @@ async function logout() {
116116
116648
  for (const f of ["credentials.json", "status.json", "daemon.lock"]) {
116117
116649
  try {
116118
116650
  const p = join85(daemonDir2(), f);
116119
- if (existsSync46(p)) rmSync12(p);
116651
+ if (existsSync47(p)) rmSync12(p);
116120
116652
  } catch {
116121
116653
  }
116122
116654
  }
@@ -116276,7 +116808,7 @@ function startSelfUpdater(current) {
116276
116808
  // src/service.ts
116277
116809
  import { join as join86 } from "node:path";
116278
116810
  import { platform as platform7 } from "node:os";
116279
- import { existsSync as existsSync47, readFileSync as readFileSync52 } from "node:fs";
116811
+ import { existsSync as existsSync48, readFileSync as readFileSync53 } from "node:fs";
116280
116812
  function controller(instanceId) {
116281
116813
  const c = getServiceController(instanceId);
116282
116814
  if (!c) {
@@ -116303,8 +116835,8 @@ var isAlive = (pid) => {
116303
116835
  function runningDaemonInfo() {
116304
116836
  const lock = join86(daemonDir2(), "daemon.lock");
116305
116837
  try {
116306
- if (!existsSync47(lock)) return null;
116307
- const pid = Number(readFileSync52(lock, "utf-8").trim());
116838
+ if (!existsSync48(lock)) return null;
116839
+ const pid = Number(readFileSync53(lock, "utf-8").trim());
116308
116840
  if (!pid || !isAlive(pid)) return null;
116309
116841
  return { pid, device: readCredentials()?.deviceId };
116310
116842
  } catch {
@@ -116338,7 +116870,7 @@ async function logs(instanceId, follow) {
116338
116870
  }
116339
116871
 
116340
116872
  // src/net.ts
116341
- import { readFileSync as readFileSync54 } from "node:fs";
116873
+ import { readFileSync as readFileSync55 } from "node:fs";
116342
116874
  import { randomBytes as randomBytes11 } from "node:crypto";
116343
116875
  import { hostname as hostname7 } from "node:os";
116344
116876
  import { resolve as resolve14 } from "node:path";
@@ -116403,7 +116935,7 @@ function readOrDie(label, p) {
116403
116935
  process.exit(1);
116404
116936
  }
116405
116937
  try {
116406
- return new Uint8Array(readFileSync54(resolve14(p)));
116938
+ return new Uint8Array(readFileSync55(resolve14(p)));
116407
116939
  } catch (e) {
116408
116940
  console.error(`net enroll: could not read --${label} ${p}: ${e instanceof Error ? e.message : e}`);
116409
116941
  process.exit(1);
@@ -116475,6 +117007,9 @@ async function netCommand(rest, flags) {
116475
117007
  const established = s.established ?? [];
116476
117008
  const relayed = s.relayed ?? [];
116477
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
+ }
116478
117013
  if (relayed.length) {
116479
117014
  console.log(`relayed: ${relayed.length} (${relayed.join(", ")}) \u2014 these are NOT direct`);
116480
117015
  } else if (established.length) {
@@ -116954,7 +117489,7 @@ async function netCommand(rest, flags) {
116954
117489
  function pkgVersion() {
116955
117490
  try {
116956
117491
  const p = join88(dirname40(fileURLToPath8(import.meta.url)), "..", "package.json");
116957
- return JSON.parse(readFileSync55(p, "utf8")).version ?? "0.0.0";
117492
+ return JSON.parse(readFileSync56(p, "utf8")).version ?? "0.0.0";
116958
117493
  } catch {
116959
117494
  return "0.0.0";
116960
117495
  }
@@ -117200,7 +117735,7 @@ async function captureClaudeSubscription(flags) {
117200
117735
  });
117201
117736
  if (code !== 0) throw new Error(`Claude sign-in did not complete (exit ${code}).`);
117202
117737
  const file2 = join88(home, ".credentials.json");
117203
- if (existsSync49(file2)) return readFileSync55(file2, "utf8");
117738
+ if (existsSync50(file2)) return readFileSync56(file2, "utf8");
117204
117739
  if (process.platform === "darwin") {
117205
117740
  const value = execFileSync6("security", [
117206
117741
  "find-generic-password",
@@ -117282,7 +117817,7 @@ async function runnerCommand(rest, flags) {
117282
117817
  process.exit(1);
117283
117818
  }
117284
117819
  const oauthFilePath = typeof flags["oauth-file"] === "string" ? resolve15(flags["oauth-file"]) : void 0;
117285
- const oauthAuthFile = oauthFilePath ? readFileSync55(oauthFilePath, "utf8") : void 0;
117820
+ const oauthAuthFile = oauthFilePath ? readFileSync56(oauthFilePath, "utf8") : void 0;
117286
117821
  if (oauthAuthFile) {
117287
117822
  try {
117288
117823
  JSON.parse(oauthAuthFile);