tesseract-agent 0.9.32 → 0.9.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist-bundle/cli.js +435 -223
  2. package/package.json +1 -1
@@ -64867,7 +64867,7 @@ function compareUtf8(a, b) {
64867
64867
  function canonicalStrs(items) {
64868
64868
  return [...new Set(items)].sort(compareUtf8);
64869
64869
  }
64870
- var MeshCodecError, fail2, LIMITS, Writer, Reader;
64870
+ var MeshCodecError, fail, LIMITS, Writer, Reader;
64871
64871
  var init_codec = __esm({
64872
64872
  "../../packages/mesh-pki/dist/codec.js"() {
64873
64873
  "use strict";
@@ -64879,7 +64879,7 @@ var init_codec = __esm({
64879
64879
  this.reason = reason;
64880
64880
  }
64881
64881
  };
64882
- fail2 = (reason, detail) => {
64882
+ fail = (reason, detail) => {
64883
64883
  throw new MeshCodecError(reason, detail);
64884
64884
  };
64885
64885
  LIMITS = {
@@ -64913,17 +64913,17 @@ var init_codec = __esm({
64913
64913
  }
64914
64914
  u8(v) {
64915
64915
  if (!Number.isInteger(v) || v < 0 || v > 255)
64916
- fail2("bad-value", `u8 ${v}`);
64916
+ fail("bad-value", `u8 ${v}`);
64917
64917
  return this.#push(new Uint8Array([v]));
64918
64918
  }
64919
64919
  u16(v) {
64920
64920
  if (!Number.isInteger(v) || v < 0 || v > 65535)
64921
- fail2("bad-value", `u16 ${v}`);
64921
+ fail("bad-value", `u16 ${v}`);
64922
64922
  return this.#push(new Uint8Array([v >>> 8 & 255, v & 255]));
64923
64923
  }
64924
64924
  u32(v) {
64925
64925
  if (!Number.isInteger(v) || v < 0 || v > 4294967295)
64926
- fail2("bad-value", `u32 ${v}`);
64926
+ fail("bad-value", `u32 ${v}`);
64927
64927
  return this.#push(new Uint8Array([v >>> 24 & 255, v >>> 16 & 255, v >>> 8 & 255, v & 255]));
64928
64928
  }
64929
64929
  /** Milliseconds-since-epoch and similar. JS numbers are exact to 2^53, which is
@@ -64931,7 +64931,7 @@ var init_codec = __esm({
64931
64931
  * than silently losing precision. */
64932
64932
  u64(v) {
64933
64933
  if (!Number.isInteger(v) || v < 0 || v > Number.MAX_SAFE_INTEGER)
64934
- fail2("bad-value", `u64 ${v}`);
64934
+ fail("bad-value", `u64 ${v}`);
64935
64935
  const hi = Math.floor(v / 4294967296);
64936
64936
  const lo = v >>> 0;
64937
64937
  return this.u32(hi).u32(lo);
@@ -64939,13 +64939,13 @@ var init_codec = __esm({
64939
64939
  /** Fixed-width opaque bytes — no length prefix; the field's width is positional. */
64940
64940
  fixed(b, width) {
64941
64941
  if (b.length !== width)
64942
- fail2("bad-value", `fixed expected ${width} got ${b.length}`);
64942
+ fail("bad-value", `fixed expected ${width} got ${b.length}`);
64943
64943
  return this.#push(b);
64944
64944
  }
64945
64945
  /** Length-prefixed opaque bytes. */
64946
64946
  bytes(b, max = LIMITS.STR) {
64947
64947
  if (b.length > max)
64948
- fail2("too-long", `bytes ${b.length} > ${max}`);
64948
+ fail("too-long", `bytes ${b.length} > ${max}`);
64949
64949
  return this.u16(b.length).#push(b);
64950
64950
  }
64951
64951
  /** Length-prefixed UTF-8 string. */
@@ -64958,13 +64958,13 @@ var init_codec = __esm({
64958
64958
  */
64959
64959
  sortedStrs(items, max = LIMITS.VEC) {
64960
64960
  if (items.length > max)
64961
- fail2("too-long", `vec ${items.length} > ${max}`);
64961
+ fail("too-long", `vec ${items.length} > ${max}`);
64962
64962
  for (let i = 1; i < items.length; i++) {
64963
64963
  const cmp = compareUtf8(items[i - 1], items[i]);
64964
64964
  if (cmp === 0)
64965
- fail2("duplicate", items[i]);
64965
+ fail("duplicate", items[i]);
64966
64966
  if (cmp > 0)
64967
- fail2("not-sorted", `${items[i - 1]} > ${items[i]}`);
64967
+ fail("not-sorted", `${items[i - 1]} > ${items[i]}`);
64968
64968
  }
64969
64969
  this.u16(items.length);
64970
64970
  for (const s of items)
@@ -64974,7 +64974,7 @@ var init_codec = __esm({
64974
64974
  /** A sequence whose order is meaningful (signatures, key lists). */
64975
64975
  vec(items, each, max = LIMITS.VEC) {
64976
64976
  if (items.length > max)
64977
- fail2("too-long", `vec ${items.length} > ${max}`);
64977
+ fail("too-long", `vec ${items.length} > ${max}`);
64978
64978
  this.u16(items.length);
64979
64979
  for (const it of items)
64980
64980
  each(this, it);
@@ -65004,7 +65004,7 @@ var init_codec = __esm({
65004
65004
  }
65005
65005
  #need(n) {
65006
65006
  if (n < 0 || this.#o + n > this.#b.length) {
65007
- fail2("truncated", `need ${n} at ${this.#o}, have ${this.#b.length - this.#o}`);
65007
+ fail("truncated", `need ${n} at ${this.#o}, have ${this.#b.length - this.#o}`);
65008
65008
  }
65009
65009
  }
65010
65010
  u8() {
@@ -65028,7 +65028,7 @@ var init_codec = __esm({
65028
65028
  const lo = this.u32();
65029
65029
  const v = hi * 4294967296 + lo;
65030
65030
  if (!Number.isSafeInteger(v))
65031
- fail2("bad-value", "u64 exceeds 2^53");
65031
+ fail("bad-value", "u64 exceeds 2^53");
65032
65032
  return v;
65033
65033
  }
65034
65034
  fixed(width) {
@@ -65040,7 +65040,7 @@ var init_codec = __esm({
65040
65040
  bytes(max = LIMITS.STR) {
65041
65041
  const n = this.u16();
65042
65042
  if (n > max)
65043
- fail2("too-long", `bytes ${n} > ${max}`);
65043
+ fail("too-long", `bytes ${n} > ${max}`);
65044
65044
  return this.fixed(n);
65045
65045
  }
65046
65046
  str(max = LIMITS.STR) {
@@ -65049,7 +65049,7 @@ var init_codec = __esm({
65049
65049
  try {
65050
65050
  s = new TextDecoder("utf-8", { fatal: true }).decode(raw);
65051
65051
  } catch {
65052
- return fail2("bad-utf8");
65052
+ return fail("bad-utf8");
65053
65053
  }
65054
65054
  return s;
65055
65055
  }
@@ -65057,16 +65057,16 @@ var init_codec = __esm({
65057
65057
  sortedStrs(max = LIMITS.VEC) {
65058
65058
  const n = this.u16();
65059
65059
  if (n > max)
65060
- fail2("too-long", `vec ${n} > ${max}`);
65060
+ fail("too-long", `vec ${n} > ${max}`);
65061
65061
  const out = [];
65062
65062
  for (let i = 0; i < n; i++) {
65063
65063
  const s = this.str();
65064
65064
  if (i > 0) {
65065
65065
  const cmp = compareUtf8(out[i - 1], s);
65066
65066
  if (cmp === 0)
65067
- fail2("duplicate", s);
65067
+ fail("duplicate", s);
65068
65068
  if (cmp > 0)
65069
- fail2("not-sorted", `${out[i - 1]} > ${s}`);
65069
+ fail("not-sorted", `${out[i - 1]} > ${s}`);
65070
65070
  }
65071
65071
  out.push(s);
65072
65072
  }
@@ -65075,7 +65075,7 @@ var init_codec = __esm({
65075
65075
  vec(each, max = LIMITS.VEC) {
65076
65076
  const n = this.u16();
65077
65077
  if (n > max)
65078
- fail2("too-long", `vec ${n} > ${max}`);
65078
+ fail("too-long", `vec ${n} > ${max}`);
65079
65079
  const out = [];
65080
65080
  for (let i = 0; i < n; i++)
65081
65081
  out.push(each(this));
@@ -65084,7 +65084,7 @@ var init_codec = __esm({
65084
65084
  /** Assert the value consumed the whole buffer. Trailing bytes are an error. */
65085
65085
  finish() {
65086
65086
  if (this.#o !== this.#b.length) {
65087
- fail2("trailing", `${this.#b.length - this.#o} byte(s) after value`);
65087
+ fail("trailing", `${this.#b.length - this.#o} byte(s) after value`);
65088
65088
  }
65089
65089
  }
65090
65090
  };
@@ -65473,18 +65473,18 @@ var init_blake2s = __esm({
65473
65473
  });
65474
65474
 
65475
65475
  // ../../packages/mesh-pki/dist/crypto.js
65476
- function randomBytes9(n) {
65476
+ function randomBytes7(n) {
65477
65477
  return globalThis.crypto.getRandomValues(new Uint8Array(n));
65478
65478
  }
65479
65479
  async function generateSigningKey(extractable = true) {
65480
- const kp = await subtle4.generateKey(ED25519, extractable, ["sign", "verify"]);
65481
- const publicRaw = new Uint8Array(await subtle4.exportKey("raw", kp.publicKey));
65480
+ const kp = await subtle3.generateKey(ED25519, extractable, ["sign", "verify"]);
65481
+ const publicRaw = new Uint8Array(await subtle3.exportKey("raw", kp.publicKey));
65482
65482
  return { privateKey: kp.privateKey, publicRaw };
65483
65483
  }
65484
65484
  async function importSigningPublic(raw) {
65485
65485
  if (raw.length !== 32)
65486
65486
  throw new Error(`Ed25519 public key must be 32 bytes, got ${raw.length}`);
65487
- return subtle4.importKey("raw", bs4(raw), ED25519, true, ["verify"]);
65487
+ return subtle3.importKey("raw", bs3(raw), ED25519, true, ["verify"]);
65488
65488
  }
65489
65489
  async function signingKeyFromSeed(seed) {
65490
65490
  if (seed.length !== 32)
@@ -65508,14 +65508,14 @@ async function signingKeyFromSeed(seed) {
65508
65508
  32
65509
65509
  ]);
65510
65510
  const pkcs8 = concat2(prefix, seed);
65511
- const privateKey = await subtle4.importKey("pkcs8", bs4(pkcs8), ED25519, true, ["sign"]);
65512
- const jwk = await subtle4.exportKey("jwk", privateKey);
65511
+ const privateKey = await subtle3.importKey("pkcs8", bs3(pkcs8), ED25519, true, ["sign"]);
65512
+ const jwk = await subtle3.exportKey("jwk", privateKey);
65513
65513
  if (typeof jwk.x !== "string")
65514
65514
  throw new Error("Ed25519 JWK missing x");
65515
65515
  return { privateKey, publicRaw: unb64u2(jwk.x) };
65516
65516
  }
65517
65517
  async function exportStaticPrivate(kp) {
65518
- const jwk = await subtle4.exportKey("jwk", kp.privateKey);
65518
+ const jwk = await subtle3.exportKey("jwk", kp.privateKey);
65519
65519
  if (typeof jwk.d !== "string")
65520
65520
  throw new Error("X25519 JWK missing d");
65521
65521
  const raw = unb64u2(jwk.d);
@@ -65524,34 +65524,34 @@ async function exportStaticPrivate(kp) {
65524
65524
  return raw;
65525
65525
  }
65526
65526
  async function exportSigningPrivate(kp) {
65527
- return subtle4.exportKey("jwk", kp.privateKey);
65527
+ return subtle3.exportKey("jwk", kp.privateKey);
65528
65528
  }
65529
65529
  async function importSigningPrivate(jwk) {
65530
- const privateKey = await subtle4.importKey("jwk", jwk, ED25519, true, ["sign"]);
65530
+ const privateKey = await subtle3.importKey("jwk", jwk, ED25519, true, ["sign"]);
65531
65531
  if (typeof jwk.x !== "string")
65532
65532
  throw new Error("Ed25519 JWK missing x");
65533
65533
  return { privateKey, publicRaw: unb64u2(jwk.x) };
65534
65534
  }
65535
65535
  async function sign2(privateKey, message) {
65536
- return new Uint8Array(await subtle4.sign(ED25519, privateKey, bs4(message)));
65536
+ return new Uint8Array(await subtle3.sign(ED25519, privateKey, bs3(message)));
65537
65537
  }
65538
65538
  async function verify2(publicRaw, message, sig) {
65539
65539
  if (sig.length !== 64 || publicRaw.length !== 32)
65540
65540
  return false;
65541
65541
  try {
65542
65542
  const key2 = await importSigningPublic(publicRaw);
65543
- return await subtle4.verify(ED25519, key2, bs4(sig), bs4(message));
65543
+ return await subtle3.verify(ED25519, key2, bs3(sig), bs3(message));
65544
65544
  } catch {
65545
65545
  return false;
65546
65546
  }
65547
65547
  }
65548
65548
  async function generateStaticKey(extractable = true) {
65549
- const kp = await subtle4.generateKey(X25519, extractable, ["deriveBits"]);
65550
- const publicRaw = new Uint8Array(await subtle4.exportKey("raw", kp.publicKey));
65549
+ const kp = await subtle3.generateKey(X25519, extractable, ["deriveBits"]);
65550
+ const publicRaw = new Uint8Array(await subtle3.exportKey("raw", kp.publicKey));
65551
65551
  return { privateKey: kp.privateKey, publicRaw };
65552
65552
  }
65553
65553
  async function sha2562(bytes2) {
65554
- return new Uint8Array(await subtle4.digest("SHA-256", bs4(bytes2)));
65554
+ return new Uint8Array(await subtle3.digest("SHA-256", bs3(bytes2)));
65555
65555
  }
65556
65556
  function blake2s256(bytes2) {
65557
65557
  return blake2s(bytes2, { dkLen: 32 });
@@ -65570,10 +65570,10 @@ function unb64u2(s) {
65570
65570
  out[i] = b.charCodeAt(i);
65571
65571
  return out;
65572
65572
  }
65573
- function hex4(bytes2) {
65573
+ function hex3(bytes2) {
65574
65574
  return Array.from(bytes2, (b) => b.toString(16).padStart(2, "0")).join("");
65575
65575
  }
65576
- function utf83(s) {
65576
+ function utf82(s) {
65577
65577
  return new TextEncoder().encode(s);
65578
65578
  }
65579
65579
  function concat2(...parts) {
@@ -65594,13 +65594,13 @@ function bytesEqual(a, b) {
65594
65594
  diff |= a[i] ^ b[i];
65595
65595
  return diff === 0;
65596
65596
  }
65597
- var subtle4, bs4, ED25519, X25519;
65597
+ var subtle3, bs3, ED25519, X25519;
65598
65598
  var init_crypto = __esm({
65599
65599
  "../../packages/mesh-pki/dist/crypto.js"() {
65600
65600
  "use strict";
65601
65601
  init_blake2s();
65602
- subtle4 = globalThis.crypto.subtle;
65603
- bs4 = (b) => b;
65602
+ subtle3 = globalThis.crypto.subtle;
65603
+ bs3 = (b) => b;
65604
65604
  ED25519 = "Ed25519";
65605
65605
  X25519 = "X25519";
65606
65606
  }
@@ -65705,7 +65705,7 @@ async function genesisFingerprint(g) {
65705
65705
  return sha2562(encodeGenesis(g));
65706
65706
  }
65707
65707
  function fingerprintShort(fp, groups = 12) {
65708
- const h = hex4(fp.subarray(0, Math.min(fp.length, groups * 2)));
65708
+ const h = hex3(fp.subarray(0, Math.min(fp.length, groups * 2)));
65709
65709
  return (h.match(/.{1,4}/g) ?? []).join(" ");
65710
65710
  }
65711
65711
  async function signGenesis(block2, signers) {
@@ -66066,8 +66066,8 @@ var init_addr = __esm({
66066
66066
  init_crypto();
66067
66067
  init_prefix();
66068
66068
  init_codec();
66069
- CTX_V6 = utf83("tsr-addr6-v1");
66070
- CTX_V4 = utf83("tsr-addr4-v1");
66069
+ CTX_V6 = utf82("tsr-addr6-v1");
66070
+ CTX_V4 = utf82("tsr-addr4-v1");
66071
66071
  }
66072
66072
  });
66073
66073
 
@@ -68642,7 +68642,7 @@ var init_sas = __esm({
68642
68642
  WORD_MASK = (1 << WORD_BITS) - 1;
68643
68643
  SAS_WORDS = 6;
68644
68644
  SAS_MIN_BITS = 64;
68645
- CTX_ENROLL = utf83("tsr-enroll-sas-v1");
68645
+ CTX_ENROLL = utf82("tsr-enroll-sas-v1");
68646
68646
  if (wordlist.length !== 1 << WORD_BITS) {
68647
68647
  throw new Error(`BIP-39 wordlist must have ${1 << WORD_BITS} entries, got ${wordlist.length}`);
68648
68648
  }
@@ -69061,7 +69061,7 @@ function alphabet(letters) {
69061
69061
  };
69062
69062
  }
69063
69063
  // @__NO_SIDE_EFFECTS__
69064
- function join70(separator = "") {
69064
+ function join61(separator = "") {
69065
69065
  astr("join", separator);
69066
69066
  return {
69067
69067
  encode: (from) => {
@@ -69266,7 +69266,7 @@ var init_esm = __esm({
69266
69266
  convertRadix2,
69267
69267
  radix,
69268
69268
  radix2,
69269
- join: join70,
69269
+ join: join61,
69270
69270
  padding
69271
69271
  };
69272
69272
  }
@@ -69337,7 +69337,7 @@ function validateKdf2(k) {
69337
69337
  throw new MeshCodecError("bad-value", `unsupported KDF cost (m=${k.m} t=${k.t} p=${k.p})`);
69338
69338
  }
69339
69339
  function kdf(passphrase, salt, cost) {
69340
- return argon2id(utf83(passphrase.normalize("NFKC")), salt, { ...cost, dkLen: KEY_LEN });
69340
+ return argon2id(utf82(passphrase.normalize("NFKC")), salt, { ...cost, dkLen: KEY_LEN });
69341
69341
  }
69342
69342
  function header(version2, label, kdfCost, salt, nonce2) {
69343
69343
  const w = new Writer();
@@ -69355,8 +69355,8 @@ async function sealCaKey(seed, passphrase, label = "mesh CA", cost = DEFAULT_KDF
69355
69355
  if (label.length > MAX_LABEL)
69356
69356
  throw new MeshCodecError("too-long", "label too long");
69357
69357
  validateKdf2(cost);
69358
- const salt = randomBytes9(SALT_LEN);
69359
- const nonce2 = randomBytes9(NONCE_LEN);
69358
+ const salt = randomBytes7(SALT_LEN);
69359
+ const nonce2 = randomBytes7(NONCE_LEN);
69360
69360
  const aad = header(RECOVERY_VERSION, label, cost, salt, nonce2);
69361
69361
  const key2 = await aesKey(kdf(passphrase, salt, cost));
69362
69362
  const ct = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce2, additionalData: aad }, key2, seed));
@@ -69412,7 +69412,7 @@ function caKeyFromMnemonic(mnemonic) {
69412
69412
  return mnemonicToEntropy(norm, wordlist);
69413
69413
  }
69414
69414
  async function newDisablement() {
69415
- const secret = randomBytes9(SECRET_LEN);
69415
+ const secret = randomBytes7(SECRET_LEN);
69416
69416
  return { secret, hash: await sha2562(secret) };
69417
69417
  }
69418
69418
  async function verifyDisablement(secret, committedHash) {
@@ -69442,7 +69442,7 @@ var init_recovery = __esm({
69442
69442
  NONCE_LEN = 12;
69443
69443
  KEY_LEN = 32;
69444
69444
  MAX_LABEL = 128;
69445
- DOMAIN = utf83("tesseract-mesh-recovery-v1");
69445
+ DOMAIN = utf82("tesseract-mesh-recovery-v1");
69446
69446
  }
69447
69447
  });
69448
69448
 
@@ -69452,7 +69452,7 @@ async function mintJoinTicket(opts) {
69452
69452
  const ttl = opts.ttlMs ?? DEFAULT_TICKET_TTL_MS;
69453
69453
  const details = {
69454
69454
  version: 1,
69455
- serial: randomBytes9(16),
69455
+ serial: randomBytes7(16),
69456
69456
  genesisFp,
69457
69457
  nodeId: opts.nodeId,
69458
69458
  name: opts.name,
@@ -69516,7 +69516,7 @@ async function redeemJoinTicket(opts) {
69516
69516
  const genesisFp = await genesisFingerprint(ticket.genesis);
69517
69517
  const details = {
69518
69518
  version: 1,
69519
- serial: randomBytes9(16),
69519
+ serial: randomBytes7(16),
69520
69520
  genesisFp,
69521
69521
  nodeId: opts.nodeId ?? ca.nodeId,
69522
69522
  name: opts.name ?? ca.name,
@@ -69661,7 +69661,7 @@ __export(dist_exports3, {
69661
69661
  generateSigningKey: () => generateSigningKey,
69662
69662
  generateStaticKey: () => generateStaticKey,
69663
69663
  genesisFingerprint: () => genesisFingerprint,
69664
- hex: () => hex4,
69664
+ hex: () => hex3,
69665
69665
  importSigningPrivate: () => importSigningPrivate,
69666
69666
  importSigningPublic: () => importSigningPublic,
69667
69667
  isCa: () => isCa,
@@ -69690,7 +69690,7 @@ __export(dist_exports3, {
69690
69690
  prefixContains: () => prefixContains,
69691
69691
  prefixContainsAddr: () => prefixContainsAddr,
69692
69692
  prefixEqual: () => prefixEqual,
69693
- randomBytes: () => randomBytes9,
69693
+ randomBytes: () => randomBytes7,
69694
69694
  readPrefix: () => readPrefix,
69695
69695
  readPrefixes: () => readPrefixes,
69696
69696
  readRule: () => readRule,
@@ -69706,7 +69706,7 @@ __export(dist_exports3, {
69706
69706
  signingKeyFromSeed: () => signingKeyFromSeed,
69707
69707
  u32ToAddr: () => u32ToAddr,
69708
69708
  unb64u: () => unb64u2,
69709
- utf8: () => utf83,
69709
+ utf8: () => utf82,
69710
69710
  validateBundle: () => validateBundle,
69711
69711
  validateDetails: () => validateDetails,
69712
69712
  validateGenesis: () => validateGenesis,
@@ -70889,7 +70889,7 @@ async function netCaCommand(rest, flags) {
70889
70889
  if (pass.length < 12) die("Use a passphrase of at least 12 characters \u2014 this key issues every identity on the mesh.");
70890
70890
  const confirm2 = await readPassphrase("Confirm: ");
70891
70891
  if (pass !== confirm2) die("Passphrases did not match.");
70892
- const seed = randomBytes9(SEED_LEN);
70892
+ const seed = randomBytes7(SEED_LEN);
70893
70893
  const ca = await signingKeyFromSeed(seed);
70894
70894
  const pol = ca;
70895
70895
  const disable = await newDisablement();
@@ -70906,10 +70906,10 @@ async function netCaCommand(rest, flags) {
70906
70906
  ensureDir(caDir);
70907
70907
  writeFileSync42(join76(caDir, "genesis.bin"), genesisBytes);
70908
70908
  writeSecretFile(join76(caDir, "ca.sealed"), encodeRecoveryBundle(await sealCaKey(seed, pass, `${org} mesh CA`)));
70909
- writeSecretFile(join76(caDir, "disablement.secret"), hex4(disable.secret));
70909
+ writeSecretFile(join76(caDir, "disablement.secret"), hex3(disable.secret));
70910
70910
  console.log(`
70911
70911
  Mesh created for "${org}".`);
70912
- console.log(` fingerprint: ${hex4(fp)}`);
70912
+ console.log(` fingerprint: ${hex3(fp)}`);
70913
70913
  console.log(` CA directory: ${caDir}`);
70914
70914
  console.log("\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
70915
70915
  console.log("WRITE THESE DOWN NOW. They are not shown again.\n");
@@ -70918,7 +70918,7 @@ Mesh created for "${org}".`);
70918
70918
  `);
70919
70919
  console.log("Disablement secret (last resort if the CA AND the mnemonic are gone \u2014");
70920
70920
  console.log("it drops every node back to relay-trusted mode, permanently):");
70921
- console.log(` ${hex4(disable.secret)}`);
70921
+ console.log(` ${hex3(disable.secret)}`);
70922
70922
  console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
70923
70923
  console.log(`
70924
70924
  A copy of the disablement secret is in ${join76(caDir, "disablement.secret")}.`);
@@ -70954,7 +70954,7 @@ Next, issue an identity for a machine (pick a free address in ${formatPrefix(gen
70954
70954
  const cert = await issueCert(
70955
70955
  {
70956
70956
  version: 1,
70957
- serial: randomBytes9(16),
70957
+ serial: randomBytes7(16),
70958
70958
  genesisFp: fp,
70959
70959
  nodeId: name,
70960
70960
  name,
@@ -92628,6 +92628,15 @@ function ownHostId() {
92628
92628
  const mine = serverHosts.find((h) => h.hostId.endsWith(`:${deviceId}`));
92629
92629
  return mine?.hostId ?? "local";
92630
92630
  }
92631
+ var lastOwnHostLog = null;
92632
+ function logOwnHostVerdict() {
92633
+ const own2 = ownHostId();
92634
+ const verdict = own2 === "local" ? `NOT REGISTERED \u2014 the account lists ${serverHosts.length} host(s), none matching this deviceId=${deviceId.slice(0, 8)}; this machine is invisible to every client` : `registered as ${own2}`;
92635
+ if (verdict === lastOwnHostLog)
92636
+ return;
92637
+ lastOwnHostLog = verdict;
92638
+ console.log(`[control-link] own host: ${verdict}`);
92639
+ }
92631
92640
  function presentedHosts() {
92632
92641
  const out = serverHosts.slice();
92633
92642
  if (!out.some((h) => h.hostId.endsWith(`:${deviceId}`))) {
@@ -93428,6 +93437,8 @@ function handleHostChannel(payload) {
93428
93437
  entitlementDenied = true;
93429
93438
  stopHeartbeat();
93430
93439
  const message = typeof payload.message === "string" ? payload.message : "Running agents on your own machines requires a paid plan.";
93440
+ const d = payload;
93441
+ console.log(`[control-link] host:register DENIED reason=${String(d.reason ?? "?")} feature=${String(d.feature ?? "?")} limit=${String(d.limit ?? "?")} \u2014 ${message}`);
93431
93442
  hooks?.notify({ type: "notify", level: "warning", message });
93432
93443
  scheduleEntitlementRetry();
93433
93444
  return;
@@ -93451,6 +93462,7 @@ function handleHostChannel(payload) {
93451
93462
  setActiveLinkPeerOnline(l.linkId, true);
93452
93463
  }
93453
93464
  assertServerSelection();
93465
+ logOwnHostVerdict();
93454
93466
  hooks?.ownHostResolved(ownHostId());
93455
93467
  hooks?.pushHosts(presentedHosts());
93456
93468
  resumeActiveLinks();
@@ -93472,6 +93484,7 @@ function registerAsHost() {
93472
93484
  const upAdvert = upscaleAdvert();
93473
93485
  if (upAdvert.gpu)
93474
93486
  caps.push("gpu");
93487
+ console.log(`[control-link] host:register \u2192 deviceId=${deviceId.slice(0, 8)} machineId=${getMachinePublicId() ?? "none"} kind=${kind ?? "electron"} caps=${caps.join(",")}`);
93475
93488
  send3("host", { type: "host:register", name, platform: process.platform, deviceId, publicMachineId: getMachinePublicId(), caps, ...kind ? { kind } : {}, ...staticMetrics(hostVersion), gpu: upAdvert.gpu, upscaleModels: upAdvert.upscaleModels, ...p2pAdvert ?? {} });
93476
93489
  if (!heartbeat)
93477
93490
  heartbeat = setInterval(() => send3("host", { type: "host:heartbeat", ...dynamicMetrics() }), 2e4);
@@ -93793,7 +93806,7 @@ function connect() {
93793
93806
  } else if (rdSession && typeof p?.type === "string" && p.type.startsWith("rdesk:")) {
93794
93807
  if (inbound) {
93795
93808
  if (driving)
93796
- hooks?.onRdesk?.(env.payload, clientId, rdSession);
93809
+ hooks?.onRdesk?.(env.payload, clientId, rdSession, env.viewerOwner === true);
93797
93810
  } else
93798
93811
  hooks?.deliverRdesk?.(env.payload, typeof env.hostId === "string" ? env.hostId : void 0, rdSession);
93799
93812
  }
@@ -93806,7 +93819,7 @@ function connect() {
93806
93819
  return;
93807
93820
  if (inbound) {
93808
93821
  if (driving)
93809
- hooks?.onRdesk?.(p, clientId, sessionId);
93822
+ hooks?.onRdesk?.(p, clientId, sessionId, env.viewerOwner === true);
93810
93823
  return;
93811
93824
  }
93812
93825
  hooks?.deliverRdesk?.(p, typeof env.hostId === "string" ? env.hostId : void 0, sessionId);
@@ -94038,6 +94051,32 @@ var RdeskSessionManager = class {
94038
94051
  return 0;
94039
94052
  return Math.min(PW_BACKOFF_BASE_MS * 2 ** (over - 1), PW_BACKOFF_MAX_MS);
94040
94053
  }
94054
+ /** Tell the viewer that this host is (or is again) producing a picture.
94055
+ *
94056
+ * Sent by the HOST because only the host can know why: the OS reports a muted or ended
94057
+ * capture track as a fact, whereas a viewer sees an absence of frames and cannot tell a
94058
+ * slept display from a slow link from a genuinely black screen. Audited too, so "it just
94059
+ * showed black" is answerable after the fact. */
94060
+ reportCapture(sessionId, clientId, ok, message) {
94061
+ this.log(`capture ${ok ? "restored" : `lost \u2014 ${message}`} sessionId=${sessionId}`);
94062
+ this.audit({ event: "capture", sessionId, detail: ok ? "ok" : message });
94063
+ this.deps.sendControl(sessionId, clientId, { type: "rdesk:capture", ok, ...message ? { message } : {} });
94064
+ }
94065
+ /** Record which path a session's media actually took, once ICE has settled.
94066
+ *
94067
+ * Media is direct-first (no relay pinning); TURN exists only as a last resort for NATs
94068
+ * that never punch through. That makes relay usage a COST we pay, so it is written to
94069
+ * the audit trail rather than only logged — `path` events in rdesk-audit.jsonl answer
94070
+ * "what fraction of sessions actually relayed?" without grepping daemon logs. Candidate
94071
+ * types only: an audit file is not the place for peer addresses. */
94072
+ recordPath(sessionId, viewerUserId, path6, d) {
94073
+ this.audit({
94074
+ event: "path",
94075
+ sessionId,
94076
+ viewerUserId,
94077
+ detail: `${path6} local=${d.localType ?? "?"} remote=${d.remoteType ?? "?"}`
94078
+ });
94079
+ }
94041
94080
  audit(e) {
94042
94081
  this.deps.audit?.({ at: this.now(), ...e });
94043
94082
  }
@@ -94062,10 +94101,10 @@ var RdeskSessionManager = class {
94062
94101
  const viewer = viewerIn.name || !declared ? viewerIn : { ...viewerIn, name: declared };
94063
94102
  const grant0 = grantOf(msg);
94064
94103
  this.audit({ event: "connect", sessionId, viewerUserId: viewer.userId, viewerName: viewer.name, grant: grant0 });
94065
- const deny = (reason) => {
94066
- this.log(`connect denied sessionId=${sessionId} reason=${reason}`);
94067
- this.audit({ event: "deny", sessionId, viewerUserId: viewer.userId, grant: grant0, detail: reason });
94068
- this.deps.sendControl(sessionId, viewer.clientId, { type: "rdesk:denied", reason });
94104
+ const deny = (reason, message) => {
94105
+ this.log(`connect denied sessionId=${sessionId} reason=${reason}${message ? ` \u2014 ${message}` : ""}`);
94106
+ this.audit({ event: "deny", sessionId, viewerUserId: viewer.userId, grant: grant0, detail: message ? `${reason}: ${message}` : reason });
94107
+ this.deps.sendControl(sessionId, viewer.clientId, { type: "rdesk:denied", reason, ...message ? { message } : {} });
94069
94108
  };
94070
94109
  if (!this.deps.canServe())
94071
94110
  return deny("entitlement");
@@ -94083,6 +94122,10 @@ var RdeskSessionManager = class {
94083
94122
  return deny("busy");
94084
94123
  }
94085
94124
  const grant = grantOf(msg);
94125
+ if (grant === "registry" && viewer.sameAccount !== true) {
94126
+ this.log(`connect denied sessionId=${sessionId} \u2014 'registry' claimed by a viewer the relay does not consider the owner`);
94127
+ return deny("consent");
94128
+ }
94086
94129
  if (grant === "password") {
94087
94130
  if (this.now() < this.pwLockedUntil) {
94088
94131
  this.log(`password attempt refused \u2014 locked for ${Math.ceil((this.pwLockedUntil - this.now()) / 1e3)}s`);
@@ -94123,7 +94166,9 @@ var RdeskSessionManager = class {
94123
94166
  sendControl: (m) => this.deps.sendControl(sessionId, viewer.clientId, m),
94124
94167
  sendData: (_m) => {
94125
94168
  },
94126
- log: (m) => this.log(`${sessionId}: ${m}`)
94169
+ log: (m) => this.log(`${sessionId}: ${m}`),
94170
+ onPath: (path6, d) => this.recordPath(sessionId, viewer.userId, path6, d),
94171
+ onCapture: (ok, message) => this.reportCapture(sessionId, viewer.clientId, ok, message)
94127
94172
  };
94128
94173
  let engine;
94129
94174
  try {
@@ -94134,7 +94179,7 @@ var RdeskSessionManager = class {
94134
94179
  const err2 = e instanceof Error ? e.message : String(e);
94135
94180
  this.log(`engine start failed sessionId=${sessionId}: ${err2}`);
94136
94181
  this.deps.onCaptureBlocked?.({ sessionId, viewerName: viewer.name, error: err2 });
94137
- return deny("offline");
94182
+ return deny("offline", err2);
94138
94183
  }
94139
94184
  const monitors = await engine.listMonitors().catch(() => []);
94140
94185
  const sources = await engine.listSources?.().catch(() => []) ?? [];
@@ -94203,7 +94248,9 @@ var RdeskSessionManager = class {
94203
94248
  sendControl: (m) => this.deps.sendControl(s.sessionId, s.viewer.clientId, m),
94204
94249
  sendData: () => {
94205
94250
  },
94206
- log: (m) => this.log(`${s.sessionId}: ${m}`)
94251
+ log: (m) => this.log(`${s.sessionId}: ${m}`),
94252
+ onPath: (path6, d) => this.recordPath(s.sessionId, s.viewer.userId, path6, d),
94253
+ onCapture: (ok, message) => this.reportCapture(s.sessionId, s.viewer.clientId, ok, message)
94207
94254
  };
94208
94255
  try {
94209
94256
  s.engine = await this.deps.createEngine("tesseract", ctx2);
@@ -94378,6 +94425,93 @@ var RdeskSessionManager = class {
94378
94425
  }
94379
94426
  };
94380
94427
 
94428
+ // ../../packages/host-core/dist/daemon-identity-reload.js
94429
+ var reload = null;
94430
+ function setDaemonIdentityReload(fn) {
94431
+ reload = fn;
94432
+ }
94433
+ function nextDaemonIdentity(fresh, current) {
94434
+ if (!fresh?.accessToken)
94435
+ return { kind: "machine" };
94436
+ if (fresh.accessToken === current.accessToken && (fresh.userId ?? "") === current.userId)
94437
+ return { kind: "unchanged" };
94438
+ return { kind: "account" };
94439
+ }
94440
+
94441
+ // ../../packages/host-core/dist/rdesk/screen-permission.js
94442
+ function screenRecordingStatus() {
94443
+ if (process.platform !== "darwin")
94444
+ return "unsupported";
94445
+ try {
94446
+ const sp = electronModule()?.systemPreferences;
94447
+ const s = sp?.getMediaAccessStatus("screen");
94448
+ if (s === "granted" || s === "denied" || s === "restricted" || s === "not-determined")
94449
+ return s;
94450
+ return "not-determined";
94451
+ } catch {
94452
+ return "not-determined";
94453
+ }
94454
+ }
94455
+ function canCaptureScreen() {
94456
+ const s = screenRecordingStatus();
94457
+ return s === "granted" || s === "unsupported";
94458
+ }
94459
+ async function registerForScreenRecording() {
94460
+ try {
94461
+ const dc = electronModule()?.desktopCapturer;
94462
+ await dc?.getSources({ types: ["screen"], thumbnailSize: { width: 1, height: 1 } });
94463
+ } catch {
94464
+ }
94465
+ }
94466
+ async function openScreenRecordingSettings() {
94467
+ if (process.platform !== "darwin")
94468
+ return;
94469
+ await registerForScreenRecording();
94470
+ try {
94471
+ const shell = electronModule()?.shell;
94472
+ await shell?.openExternal("x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture");
94473
+ } catch {
94474
+ }
94475
+ }
94476
+ var PROMPT_COOLDOWN_MS = 6e4;
94477
+ var lastPromptAt = 0;
94478
+ async function promptForScreenRecording(opts = {}) {
94479
+ if (process.platform !== "darwin")
94480
+ return false;
94481
+ if (screenRecordingStatus() === "granted") {
94482
+ opts.log?.("capture failed although Screen Recording IS granted \u2014 the capture stack is wedged (seen after a GPU-process crash); restarting Tesseract clears it");
94483
+ return false;
94484
+ }
94485
+ const now2 = Date.now();
94486
+ if (now2 - lastPromptAt < PROMPT_COOLDOWN_MS)
94487
+ return false;
94488
+ lastPromptAt = now2;
94489
+ const el2 = electronModule();
94490
+ const who = opts.viewerName ? `${opts.viewerName} is` : "Someone is";
94491
+ opts.log?.(`screen recording not granted \u2014 prompting (${who} waiting)`);
94492
+ if (!el2?.dialog) {
94493
+ opts.log?.("no GUI to prompt with \u2014 grant Screen Recording in System Settings \u2192 Privacy & Security");
94494
+ return false;
94495
+ }
94496
+ try {
94497
+ const { response } = await el2.dialog.showMessageBox({
94498
+ type: "warning",
94499
+ buttons: ["Open System Settings", "Not Now"],
94500
+ defaultId: 0,
94501
+ cancelId: 1,
94502
+ message: "Allow screen sharing on this Mac?",
94503
+ detail: `${who} trying to connect to this computer, but macOS is blocking screen capture.
94504
+
94505
+ Open System Settings \u2192 Privacy & Security \u2192 Screen Recording and switch on Tesseract Remote Desktop, then ask them to connect again.`
94506
+ });
94507
+ if (response === 0)
94508
+ await openScreenRecordingSettings();
94509
+ return true;
94510
+ } catch {
94511
+ return false;
94512
+ }
94513
+ }
94514
+
94381
94515
  // ../../packages/host-core/dist/rdesk/publisher-window.js
94382
94516
  import { join as join54 } from "node:path";
94383
94517
  var windows = /* @__PURE__ */ new Map();
@@ -94415,6 +94549,7 @@ function ensureIpc() {
94415
94549
  el2.ipcMain.on("rdesk:pub:ready", (_e, p) => route2(p, (w) => w.handlers.onReady()));
94416
94550
  el2.ipcMain.on("rdesk:pub:error", (_e, p) => route2(p, (w, x) => w.handlers.onError(String(x.error ?? "publisher error"))));
94417
94551
  el2.ipcMain.on("rdesk:pub:path", (_e, p) => route2(p, (w, x) => w.handlers.onPath?.(String(x.path ?? "unknown"), { localType: x.localType ?? null, remoteType: x.remoteType ?? null })));
94552
+ el2.ipcMain.on("rdesk:pub:capture", (_e, p) => route2(p, (w, x) => w.handlers.onCapture?.(x.ok !== false, String(x.message ?? ""))));
94418
94553
  el2.ipcMain.on("rdesk:pub:stopped", (_e, p) => route2(p, (w) => w.onStoppedAck()));
94419
94554
  }
94420
94555
  async function listPublisherMonitors() {
@@ -94461,21 +94596,41 @@ function displayBoundsById(monitorId) {
94461
94596
  const d = el2.screen.getAllDisplays().find((x) => String(x.id) === monitorId) ?? el2.screen.getPrimaryDisplay();
94462
94597
  return d ? { x: d.bounds.x, y: d.bounds.y, width: d.bounds.width, height: d.bounds.height } : null;
94463
94598
  }
94464
- async function sourceIdForMonitor(monitorId) {
94599
+ async function wakeDisplay(log9) {
94600
+ if (process.platform !== "darwin")
94601
+ return;
94602
+ try {
94603
+ const { spawn: spawn17 } = await import("node:child_process");
94604
+ spawn17("caffeinate", ["-u", "-t", "5"], { detached: true, stdio: "ignore" }).unref();
94605
+ log9?.("display was asleep \u2014 declared user activity to wake it");
94606
+ await new Promise((r) => setTimeout(r, 1200));
94607
+ } catch {
94608
+ }
94609
+ }
94610
+ async function sourceIdForMonitor(monitorId, log9) {
94465
94611
  const el2 = electronModule();
94466
94612
  if (!el2?.desktopCapturer || !el2.screen)
94467
94613
  throw new Error("Screen capture unavailable (Electron desktopCapturer/screen missing).");
94468
94614
  const display = el2.screen.getAllDisplays().find((d) => String(d.id) === monitorId) ?? el2.screen.getPrimaryDisplay();
94469
- const sources = await el2.desktopCapturer.getSources({ types: ["screen"], thumbnailSize: { width: 1, height: 1 } });
94615
+ const list = async () => el2.desktopCapturer.getSources({ types: ["screen"], thumbnailSize: { width: 1, height: 1 } });
94616
+ let sources = await list();
94617
+ if (sources.length === 0) {
94618
+ await wakeDisplay(log9);
94619
+ sources = await list();
94620
+ }
94470
94621
  const match = sources.find((s) => s.display_id === String(display.id)) ?? sources[0];
94471
- if (!match)
94472
- throw new Error("Screen capture failed: no screen source (grant Screen Recording permission).");
94622
+ if (!match) {
94623
+ const denied = !canCaptureScreen();
94624
+ throw new Error(denied ? "Screen capture failed: Screen Recording permission is not granted to this app." : "Screen capture failed: no screen source. Screen Recording IS granted, so this is usually a display that stayed asleep, or a Mac with no active display attached.");
94625
+ }
94473
94626
  return match.id;
94474
94627
  }
94475
94628
  var PublisherWindow = class {
94476
94629
  sessionId;
94477
94630
  handlers;
94478
94631
  win = null;
94632
+ /** powerSaveBlocker id held for the life of the session — see startDisplayHold(). */
94633
+ displayHold = null;
94479
94634
  stopAck = null;
94480
94635
  constructor(sessionId, handlers) {
94481
94636
  this.sessionId = sessionId;
@@ -94493,6 +94648,7 @@ var PublisherWindow = class {
94493
94648
  if (!el2?.BrowserWindow)
94494
94649
  throw new Error("Publisher requires the daemon GUI shell (no BrowserWindow).");
94495
94650
  ensureIpc();
94651
+ this.startDisplayHold();
94496
94652
  const sourceId = opts.sourceId ?? await sourceIdForMonitor(opts.monitorId ?? await this.primaryMonitorId());
94497
94653
  const win3 = new el2.BrowserWindow({
94498
94654
  width: 320,
@@ -94606,7 +94762,36 @@ var PublisherWindow = class {
94606
94762
  maxFps: opts.maxFps
94607
94763
  });
94608
94764
  }
94765
+ /** Hold the display awake for as long as this session lives.
94766
+ *
94767
+ * Waking it once at start is not enough: the idle timer keeps running, so an unattended
94768
+ * host would go dark again a few minutes into a session and capture would die with the
94769
+ * same empty source list it started with. Released in stop() — never leaked, or the host
94770
+ * can never sleep again. */
94771
+ startDisplayHold() {
94772
+ if (this.displayHold !== null)
94773
+ return;
94774
+ try {
94775
+ const psb = electronModule()?.powerSaveBlocker;
94776
+ if (psb)
94777
+ this.displayHold = psb.start("prevent-display-sleep");
94778
+ } catch {
94779
+ }
94780
+ }
94781
+ releaseDisplayHold() {
94782
+ const id = this.displayHold;
94783
+ this.displayHold = null;
94784
+ if (id === null)
94785
+ return;
94786
+ try {
94787
+ const psb = electronModule()?.powerSaveBlocker;
94788
+ if (psb?.isStarted(id))
94789
+ psb.stop(id);
94790
+ } catch {
94791
+ }
94792
+ }
94609
94793
  async stop() {
94794
+ this.releaseDisplayHold();
94610
94795
  const w = this.win;
94611
94796
  this.win = null;
94612
94797
  if (!w || w.isDestroyed()) {
@@ -94968,10 +95153,20 @@ var TesseractEngine = class {
94968
95153
  }
94969
95154
  },
94970
95155
  onReady: () => this.ctx.log("publisher connected"),
94971
- onError: (err2) => this.ctx.log(`publisher error: ${err2}`),
95156
+ onError: (err2) => {
95157
+ this.ctx.log(`publisher error: ${err2}`);
95158
+ this.ctx.sendControl({ type: "rdesk:closed", reason: "network" });
95159
+ },
94972
95160
  // Media is direct-first; log which path actually won so a regression to relay
94973
95161
  // (which costs real bandwidth) is visible instead of silent. Types only, no IPs.
94974
- onPath: (path6, d) => this.ctx.log(`media path=${path6} (local=${d.localType ?? "?"} remote=${d.remoteType ?? "?"})`)
95162
+ onCapture: (ok, message) => {
95163
+ this.ctx.log(`capture ${ok ? "ok" : `LOST \u2014 ${message}`}`);
95164
+ this.ctx.onCapture?.(ok, message);
95165
+ },
95166
+ onPath: (path6, d) => {
95167
+ this.ctx.log(`media path=${path6} (local=${d.localType ?? "?"} remote=${d.remoteType ?? "?"})`);
95168
+ this.ctx.onPath?.(path6, d);
95169
+ }
94975
95170
  });
94976
95171
  this.pub = pub;
94977
95172
  await pub.start({
@@ -97440,7 +97635,7 @@ async function startLocalServer(opts) {
97440
97635
  const rd = opts.getRdesk?.();
97441
97636
  if (rd && env.sessionId) {
97442
97637
  rdeskSessions.add(env.sessionId);
97443
- void rd.onClientMessage(env.sessionId, { clientId: loopbackOrigin, name: "This device" }, env.payload);
97638
+ void rd.onClientMessage(env.sessionId, { clientId: loopbackOrigin, name: "This device", sameAccount: true }, env.payload);
97444
97639
  }
97445
97640
  }
97446
97641
  } else if (env.channel === "signal") {
@@ -98025,8 +98220,8 @@ async function startLocalServer(opts) {
98025
98220
  init_base_paths();
98026
98221
  import { readFileSync as readFileSync45, writeFileSync as writeFileSync38, existsSync as existsSync35, mkdirSync as mkdirSync37, unlinkSync as unlinkSync6, chmodSync as chmodSync7, statSync as statSync9 } from "node:fs";
98027
98222
  import { execFileSync as execFileSync5 } from "node:child_process";
98028
- import { join as join69 } from "node:path";
98029
- import { randomBytes as randomBytes8 } from "node:crypto";
98223
+ import { join as join70 } from "node:path";
98224
+ import { randomBytes as randomBytes10 } from "node:crypto";
98030
98225
  import { hostname as hostname5 } from "node:os";
98031
98226
 
98032
98227
  // ../../packages/shared/dist/daemon-registry.js
@@ -98855,9 +99050,54 @@ var MeshNode = class {
98855
99050
  #relayed = /* @__PURE__ */ new Set();
98856
99051
  /** staticPub hex → the Buffer, so events can be matched back to a peer. */
98857
99052
  #peers = /* @__PURE__ */ new Map();
99053
+ /**
99054
+ * Peers whose CERTIFICATE says they are this network's way out, by display name.
99055
+ *
99056
+ * A hint for the UI, never an authorisation: the engine checks the same signed bit per
99057
+ * packet, alongside our own selection and a bundle capability. This exists so a person
99058
+ * can be offered a choice that has somewhere to go.
99059
+ */
99060
+ #exitNodes = /* @__PURE__ */ new Map();
98858
99061
  #lan = null;
98859
99062
  #offRejoin = null;
98860
99063
  #statusTimer = null;
99064
+ /**
99065
+ * Route this machine's non-mesh traffic through the network's exit node, or stop.
99066
+ *
99067
+ * GATE 1 OF THREE, and the only one that lives on this machine. `Engine::exit_allowed`
99068
+ * additionally requires that the CA marked the peer's certificate and that the signed
99069
+ * bundle grants CAP_EXIT_NODE, both checked per packet — so selecting a peer widens
99070
+ * nothing by itself, and selecting one that turns out not to qualify simply drops the
99071
+ * traffic rather than misrouting it.
99072
+ *
99073
+ * Without this call netd installs the routes and the engine refuses every packet that
99074
+ * arrives on them: a machine with its default route captured and nothing carrying it,
99075
+ * which is the worst of both states.
99076
+ *
99077
+ * Returns the name selected, or null when there is nobody to select.
99078
+ */
99079
+ useExitNode(on) {
99080
+ const engine = this.#engine;
99081
+ if (!engine)
99082
+ return null;
99083
+ if (!on) {
99084
+ try {
99085
+ engine.setExitNode?.(null);
99086
+ } catch {
99087
+ }
99088
+ return null;
99089
+ }
99090
+ const first = [...this.#exitNodes.entries()][0];
99091
+ if (first === void 0)
99092
+ return null;
99093
+ const [hex6, name] = first;
99094
+ try {
99095
+ engine.setExitNode?.(Buffer.from(hex6, "hex"));
99096
+ } catch {
99097
+ return null;
99098
+ }
99099
+ return name;
99100
+ }
98861
99101
  status() {
98862
99102
  return {
98863
99103
  available: meshCore.available,
@@ -98869,6 +99109,14 @@ var MeshNode = class {
98869
99109
  peers: this.#engine?.peerCount() ?? 0,
98870
99110
  established: [...this.#established],
98871
99111
  relayed: [...this.#relayed],
99112
+ exitNodes: [...this.#exitNodes.values()],
99113
+ // Derived from the SAME sets the peer list is: an exit node with no established
99114
+ // session is one that cannot carry traffic yet, and saying "waiting" is different
99115
+ // from saying "none". Keyed by name so a caller never has to hold a key to read it.
99116
+ exitPaths: Object.fromEntries([...this.#exitNodes.entries()].map(([hex6, name]) => [
99117
+ name,
99118
+ this.#established.has(hex6) ? this.#relayed.has(hex6) ? "relay" : "direct" : "none"
99119
+ ])),
98872
99120
  meter: this.#engine?.meter() ?? null,
98873
99121
  lastError: this.#lastError,
98874
99122
  // Surfaced so `net status` can say "removed" rather than "not running". Those look
@@ -99137,6 +99385,8 @@ var MeshNode = class {
99137
99385
  }
99138
99386
  case "mesh:gone": {
99139
99387
  const pk = Buffer.from(m.staticPub, "base64");
99388
+ if (pk.length === 32)
99389
+ this.#exitNodes.delete(pk.toString("hex"));
99140
99390
  if (pk.length === 32) {
99141
99391
  const hex6 = pk.toString("hex");
99142
99392
  this.#peers.delete(hex6);
@@ -99166,6 +99416,18 @@ var MeshNode = class {
99166
99416
  if (pk.length !== 32)
99167
99417
  return;
99168
99418
  const hex6 = pk.toString("hex");
99419
+ if (p.cert) {
99420
+ try {
99421
+ const bytes2 = Buffer.from(p.cert, "base64");
99422
+ const pki = (init_dist6(), __toCommonJS(dist_exports3));
99423
+ const details = pki.decodeCert(new Uint8Array(bytes2)).details;
99424
+ if (pki.isExitNode(details))
99425
+ this.#exitNodes.set(hex6, details.name);
99426
+ else
99427
+ this.#exitNodes.delete(hex6);
99428
+ } catch {
99429
+ }
99430
+ }
99169
99431
  if (!this.#peers.has(hex6)) {
99170
99432
  engine.addPeer(pk, 0, Buffer.alloc(16));
99171
99433
  this.#peers.set(hex6, pk);
@@ -99364,7 +99626,7 @@ function netdEndpoint() {
99364
99626
  function endpointOverride() {
99365
99627
  return process.env.CCB_NETD_SOCKET ?? null;
99366
99628
  }
99367
- function fail(code, detail) {
99629
+ function fail2(code, detail) {
99368
99630
  return { ok: false, code, detail };
99369
99631
  }
99370
99632
  async function call(line) {
@@ -99385,28 +99647,28 @@ async function call(line) {
99385
99647
  try {
99386
99648
  sock = connect2(endpoint2);
99387
99649
  } catch (e) {
99388
- return resolve16(fail("not-installed", e instanceof Error ? e.message : String(e)));
99650
+ return resolve16(fail2("not-installed", e instanceof Error ? e.message : String(e)));
99389
99651
  }
99390
99652
  const timer2 = setTimeout(() => {
99391
- done(fail("timeout", `no answer from the network helper in ${TIMEOUT_MS2}ms`));
99653
+ done(fail2("timeout", `no answer from the network helper in ${TIMEOUT_MS2}ms`));
99392
99654
  }, TIMEOUT_MS2);
99393
99655
  timer2.unref?.();
99394
99656
  sock.setTimeout(TIMEOUT_MS2);
99395
99657
  sock.on("timeout", () => {
99396
99658
  clearTimeout(timer2);
99397
- done(fail("timeout", "socket idle"));
99659
+ done(fail2("timeout", "socket idle"));
99398
99660
  });
99399
99661
  sock.on("error", (e) => {
99400
99662
  clearTimeout(timer2);
99401
99663
  const absent = e.code === "ENOENT" || e.code === "ECONNREFUSED";
99402
- done(fail(absent ? "not-installed" : "io", e.message));
99664
+ done(fail2(absent ? "not-installed" : "io", e.message));
99403
99665
  });
99404
99666
  let buf = "";
99405
99667
  sock.on("data", (chunk) => {
99406
99668
  buf += chunk.toString("utf8");
99407
99669
  if (buf.length > MAX_LINE) {
99408
99670
  clearTimeout(timer2);
99409
- done(fail("protocol", "the helper sent an oversized line"));
99671
+ done(fail2("protocol", "the helper sent an oversized line"));
99410
99672
  return;
99411
99673
  }
99412
99674
  const nl = buf.indexOf("\n");
@@ -99417,7 +99679,7 @@ async function call(line) {
99417
99679
  });
99418
99680
  sock.on("close", () => {
99419
99681
  clearTimeout(timer2);
99420
- done(fail("protocol", "the helper closed the connection without answering"));
99682
+ done(fail2("protocol", "the helper closed the connection without answering"));
99421
99683
  });
99422
99684
  sock.on("connect", () => {
99423
99685
  sock.write(`${line}
@@ -99430,26 +99692,26 @@ function parse3(line) {
99430
99692
  try {
99431
99693
  body = JSON.parse(line);
99432
99694
  } catch {
99433
- return fail("protocol", "the helper sent something that is not JSON");
99695
+ return fail2("protocol", "the helper sent something that is not JSON");
99434
99696
  }
99435
99697
  if (typeof body !== "object" || body === null) {
99436
- return fail("protocol", "the helper sent something that is not an object");
99698
+ return fail2("protocol", "the helper sent something that is not an object");
99437
99699
  }
99438
99700
  const o = body;
99439
99701
  if (o.ok === false) {
99440
99702
  const code = typeof o.code === "string" ? o.code : "refused";
99441
99703
  const detail = typeof o.detail === "string" ? o.detail : "the helper refused";
99442
- return fail(code, detail);
99704
+ return fail2(code, detail);
99443
99705
  }
99444
99706
  if (o.ok !== true)
99445
- return fail("protocol", "the helper did not say whether it succeeded");
99707
+ return fail2("protocol", "the helper did not say whether it succeeded");
99446
99708
  return { ok: true, body: o };
99447
99709
  }
99448
99710
  async function request(line) {
99449
99711
  const r = await call(line);
99450
99712
  return r.ok ? parse3(r.line) : r;
99451
99713
  }
99452
- function hex3(b) {
99714
+ function hex4(b) {
99453
99715
  return Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
99454
99716
  }
99455
99717
  async function netdHello() {
@@ -99458,9 +99720,9 @@ async function netdHello() {
99458
99720
  return r;
99459
99721
  const v = r.body.wireVersion;
99460
99722
  if (typeof v !== "number")
99461
- return fail("protocol", "the helper did not report a wire version");
99723
+ return fail2("protocol", "the helper did not report a wire version");
99462
99724
  if (v !== NETD_WIRE_VERSION) {
99463
- return fail("wire-version", `the helper speaks ${v}, this build speaks ${NETD_WIRE_VERSION}`);
99725
+ return fail2("wire-version", `the helper speaks ${v}, this build speaks ${NETD_WIRE_VERSION}`);
99464
99726
  }
99465
99727
  return { ok: true, wireVersion: v };
99466
99728
  }
@@ -99476,8 +99738,8 @@ async function netdUp(args) {
99476
99738
  const r = await request(JSON.stringify({
99477
99739
  op: "up",
99478
99740
  meshId: args.meshId,
99479
- genesis: hex3(args.genesis),
99480
- cert: hex3(args.cert),
99741
+ genesis: hex4(args.genesis),
99742
+ cert: hex4(args.cert),
99481
99743
  fullTunnel: args.fullTunnel === true
99482
99744
  }));
99483
99745
  return r.ok ? { ok: true } : r;
@@ -99677,7 +99939,7 @@ import { basename as basename13 } from "node:path";
99677
99939
  // ../../packages/host-core/dist/drops/paths.js
99678
99940
  init_base_paths();
99679
99941
  import { mkdir as mkdir12 } from "node:fs/promises";
99680
- import { join as join61 } from "node:path";
99942
+ import { join as join62 } from "node:path";
99681
99943
 
99682
99944
  // ../../packages/host-core/dist/drops/names.js
99683
99945
  var WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
@@ -99686,8 +99948,8 @@ var BIDI = /[\u200e\u200f\u202a-\u202e\u2066-\u2069]/g;
99686
99948
  var MAX_NAME_BYTES = 200;
99687
99949
  var MAX_EXT_BYTES = 32;
99688
99950
  var MAX_DEPTH2 = 32;
99689
- var utf82 = new TextEncoder();
99690
- var byteLen = (s) => utf82.encode(s).length;
99951
+ var utf83 = new TextEncoder();
99952
+ var byteLen = (s) => utf83.encode(s).length;
99691
99953
  function capBytes(stem, ext, max) {
99692
99954
  if (byteLen(stem + ext) <= max)
99693
99955
  return stem + ext;
@@ -99766,7 +100028,7 @@ function basenameOf(p) {
99766
100028
  function resolveDropsRoot() {
99767
100029
  const root = resolveHostRoot();
99768
100030
  const preferred = dropsDir();
99769
- return isInsideRoot(preferred, root) ? preferred : join61(root, "Drops");
100031
+ return isInsideRoot(preferred, root) ? preferred : join62(root, "Drops");
99770
100032
  }
99771
100033
  async function ensureDropsRoot() {
99772
100034
  const dir2 = resolveDropsRoot();
@@ -99782,7 +100044,7 @@ async function ensureDropDir(opts) {
99782
100044
  parts.push(sanitizeDropName(opts.sender, "Unknown sender"));
99783
100045
  if (!opts.singleItem && opts.label)
99784
100046
  parts.push(sanitizeDropName(opts.label, "Files"));
99785
- const dir2 = join61(...parts);
100047
+ const dir2 = join62(...parts);
99786
100048
  await mkdir12(dir2, { recursive: true });
99787
100049
  return dir2;
99788
100050
  }
@@ -99925,7 +100187,7 @@ function createDropHandler(deps) {
99925
100187
  // ../../packages/host-core/dist/drops/blob-fetch.js
99926
100188
  import { createWriteStream as createWriteStream2 } from "node:fs";
99927
100189
  import { mkdir as mkdir13, rm as rm5, chmod as chmod3 } from "node:fs/promises";
99928
- import { dirname as dirname26, join as join62 } from "node:path";
100190
+ import { dirname as dirname26, join as join63 } from "node:path";
99929
100191
  import { Readable as Readable2 } from "node:stream";
99930
100192
  import { pipeline } from "node:stream/promises";
99931
100193
 
@@ -99937,10 +100199,10 @@ var HEADER_BYTES = MAGIC2.length + NONCE_BYTES;
99937
100199
  var FRAME_BYTES = 1024 * 1024;
99938
100200
  var TAG_BYTES = 16;
99939
100201
  var MAX_FRAME_CIPHERTEXT = FRAME_BYTES + TAG_BYTES + 1024;
99940
- var subtle3 = () => globalThis.crypto.subtle;
99941
- var bs3 = (b) => b;
100202
+ var subtle4 = () => globalThis.crypto.subtle;
100203
+ var bs4 = (b) => b;
99942
100204
  async function importKey(raw) {
99943
- return subtle3().importKey("raw", bs3(raw), { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
100205
+ return subtle4().importKey("raw", bs4(raw), { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
99944
100206
  }
99945
100207
  function nonceFor(base, index) {
99946
100208
  const n = Buffer.from(base);
@@ -100035,7 +100297,7 @@ function decryptStream(source, keyRaw) {
100035
100297
  const frame = q.peek(4 + len);
100036
100298
  if (!frame)
100037
100299
  break;
100038
- const plain = Buffer.from(await subtle3().decrypt({ name: "AES-GCM", iv: bs3(nonceFor(base, index)), additionalData: bs3(aadFor(index)) }, key2, bs3(frame.subarray(4))));
100300
+ const plain = Buffer.from(await subtle4().decrypt({ name: "AES-GCM", iv: bs4(nonceFor(base, index)), additionalData: bs4(aadFor(index)) }, key2, bs4(frame.subarray(4))));
100039
100301
  this.push(plain);
100040
100302
  index += 1;
100041
100303
  q.consume(4 + len);
@@ -100097,7 +100359,7 @@ async function fetchBlobDrop(opts) {
100097
100359
  log7(`drop ${opts.dropId}: refusing directory "${item.rel}"`);
100098
100360
  continue;
100099
100361
  }
100100
- const abs = join62(opts.destDir, rel);
100362
+ const abs = join63(opts.destDir, rel);
100101
100363
  if (!isInsideRoot(abs, opts.destDir)) {
100102
100364
  log7(`drop ${opts.dropId}: refusing directory outside the destination`);
100103
100365
  continue;
@@ -100118,7 +100380,7 @@ async function fetchBlobDrop(opts) {
100118
100380
  log7(`drop ${opts.dropId}: refusing "${item.rel}"`);
100119
100381
  continue;
100120
100382
  }
100121
- let abs = join62(opts.destDir, rel);
100383
+ let abs = join63(opts.destDir, rel);
100122
100384
  if (!isInsideRoot(abs, opts.destDir)) {
100123
100385
  log7(`drop ${opts.dropId}: refusing a path outside the destination`);
100124
100386
  continue;
@@ -100396,12 +100658,12 @@ var RdeskServerChannel = class {
100396
100658
 
100397
100659
  // ../../packages/host-core/dist/rdesk/channel-client.js
100398
100660
  import { readFileSync as readFileSync44 } from "node:fs";
100399
- import { join as join63 } from "node:path";
100661
+ import { join as join64 } from "node:path";
100400
100662
  var RECONNECT_MS = 2e3;
100401
100663
  var CLAIM_RETRY_MS = 5e3;
100402
100664
  function readEndpoint(dir2, selfPid) {
100403
100665
  try {
100404
- const s = JSON.parse(readFileSync44(join63(dir2, "status.json"), "utf8"));
100666
+ const s = JSON.parse(readFileSync44(join64(dir2, "status.json"), "utf8"));
100405
100667
  if (selfPid && s.pid === selfPid)
100406
100668
  return null;
100407
100669
  return s.localPort && s.localToken ? `ws://127.0.0.1:${s.localPort}/?token=${s.localToken}` : null;
@@ -100542,19 +100804,6 @@ function setRdeskServerAdmin(a) {
100542
100804
  admin = a;
100543
100805
  }
100544
100806
 
100545
- // ../../packages/host-core/dist/daemon-identity-reload.js
100546
- var reload = null;
100547
- function setDaemonIdentityReload(fn) {
100548
- reload = fn;
100549
- }
100550
- function nextDaemonIdentity(fresh, current) {
100551
- if (!fresh?.accessToken)
100552
- return { kind: "machine" };
100553
- if (fresh.accessToken === current.accessToken && (fresh.userId ?? "") === current.userId)
100554
- return { kind: "unchanged" };
100555
- return { kind: "account" };
100556
- }
100557
-
100558
100807
  // ../../packages/host-core/dist/daemon-online-watch.js
100559
100808
  var FIRST_DELAY_MS = 5e3;
100560
100809
  var MAX_DELAY_MS = 3e4;
@@ -100847,10 +101096,10 @@ function registerRdpEngine() {
100847
101096
  // ../../packages/host-core/dist/rdesk/audit.js
100848
101097
  init_credentials();
100849
101098
  import { appendFile, stat as stat8, rename as rename3 } from "node:fs/promises";
100850
- import { join as join64 } from "node:path";
101099
+ import { join as join65 } from "node:path";
100851
101100
  var MAX_BYTES = 2 * 1024 * 1024;
100852
101101
  function rdeskAuditPath() {
100853
- return join64(daemonDir2(), "rdesk-audit.jsonl");
101102
+ return join65(daemonDir2(), "rdesk-audit.jsonl");
100854
101103
  }
100855
101104
  var queue = [];
100856
101105
  var draining = false;
@@ -100890,81 +101139,13 @@ function appendRdeskAudit(e) {
100890
101139
  }
100891
101140
  }
100892
101141
 
100893
- // ../../packages/host-core/dist/rdesk/screen-permission.js
100894
- function screenRecordingStatus() {
100895
- if (process.platform !== "darwin")
100896
- return "unsupported";
100897
- try {
100898
- const sp = electronModule()?.systemPreferences;
100899
- const s = sp?.getMediaAccessStatus("screen");
100900
- if (s === "granted" || s === "denied" || s === "restricted" || s === "not-determined")
100901
- return s;
100902
- return "not-determined";
100903
- } catch {
100904
- return "not-determined";
100905
- }
100906
- }
100907
- async function registerForScreenRecording() {
100908
- try {
100909
- const dc = electronModule()?.desktopCapturer;
100910
- await dc?.getSources({ types: ["screen"], thumbnailSize: { width: 1, height: 1 } });
100911
- } catch {
100912
- }
100913
- }
100914
- async function openScreenRecordingSettings() {
100915
- if (process.platform !== "darwin")
100916
- return;
100917
- await registerForScreenRecording();
100918
- try {
100919
- const shell = electronModule()?.shell;
100920
- await shell?.openExternal("x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture");
100921
- } catch {
100922
- }
100923
- }
100924
- var PROMPT_COOLDOWN_MS = 6e4;
100925
- var lastPromptAt = 0;
100926
- async function promptForScreenRecording(opts = {}) {
100927
- if (process.platform !== "darwin")
100928
- return false;
100929
- if (screenRecordingStatus() === "granted")
100930
- return false;
100931
- const now2 = Date.now();
100932
- if (now2 - lastPromptAt < PROMPT_COOLDOWN_MS)
100933
- return false;
100934
- lastPromptAt = now2;
100935
- const el2 = electronModule();
100936
- const who = opts.viewerName ? `${opts.viewerName} is` : "Someone is";
100937
- opts.log?.(`screen recording not granted \u2014 prompting (${who} waiting)`);
100938
- if (!el2?.dialog) {
100939
- opts.log?.("no GUI to prompt with \u2014 grant Screen Recording in System Settings \u2192 Privacy & Security");
100940
- return false;
100941
- }
100942
- try {
100943
- const { response } = await el2.dialog.showMessageBox({
100944
- type: "warning",
100945
- buttons: ["Open System Settings", "Not Now"],
100946
- defaultId: 0,
100947
- cancelId: 1,
100948
- message: "Allow screen sharing on this Mac?",
100949
- detail: `${who} trying to connect to this computer, but macOS is blocking screen capture.
100950
-
100951
- Open System Settings \u2192 Privacy & Security \u2192 Screen Recording and switch on Tesseract Remote Desktop, then ask them to connect again.`
100952
- });
100953
- if (response === 0)
100954
- await openScreenRecordingSettings();
100955
- return true;
100956
- } catch {
100957
- return false;
100958
- }
100959
- }
100960
-
100961
101142
  // ../../packages/host-core/dist/fileprovider-bridge.js
100962
101143
  import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync33, rmSync as rmSync8, existsSync as existsSync31 } from "node:fs";
100963
101144
  import { homedir as homedir9 } from "node:os";
100964
- import { join as join65 } from "node:path";
101145
+ import { join as join66 } from "node:path";
100965
101146
  var APPEX_BUNDLE_ID = "dev.tesseract.FileProvider";
100966
101147
  function appexBridgeInfoPath() {
100967
- return join65(homedir9(), "Library", "Containers", APPEX_BUNDLE_ID, "Data", "Library", "Application Support", "tesseract-bridge.json");
101148
+ return join66(homedir9(), "Library", "Containers", APPEX_BUNDLE_ID, "Data", "Library", "Application Support", "tesseract-bridge.json");
100968
101149
  }
100969
101150
  function clearBridgeInfo() {
100970
101151
  if (process.platform !== "darwin")
@@ -101131,10 +101312,10 @@ function stopFileProviderBridge() {
101131
101312
  }
101132
101313
 
101133
101314
  // ../../packages/host-core/dist/vault/autofill-bridge.js
101134
- import { randomBytes as randomBytes7 } from "node:crypto";
101315
+ import { randomBytes as randomBytes9 } from "node:crypto";
101135
101316
  import { existsSync as existsSync32, mkdirSync as mkdirSync34, writeFileSync as writeFileSync35, rmSync as rmSync9 } from "node:fs";
101136
101317
  import { homedir as homedir10 } from "node:os";
101137
- import { join as join66 } from "node:path";
101318
+ import { join as join67 } from "node:path";
101138
101319
 
101139
101320
  // ../../packages/host-core/dist/vault/peer-verify.js
101140
101321
  import { execFile as execFile9 } from "node:child_process";
@@ -101196,10 +101377,10 @@ async function isTrustedAutofillPeer(remotePort) {
101196
101377
  var APPEX_BUNDLE_ID2 = "dev.tesseract.AutoFill";
101197
101378
  var BRIDGE_PORTS2 = [17872, 17873, 17874, 17875];
101198
101379
  function appexContainerData() {
101199
- return join66(homedir10(), "Library", "Containers", APPEX_BUNDLE_ID2, "Data");
101380
+ return join67(homedir10(), "Library", "Containers", APPEX_BUNDLE_ID2, "Data");
101200
101381
  }
101201
101382
  function bridgeInfoPath() {
101202
- return join66(appexContainerData(), "Library", "Application Support", "tesseract-autofill-bridge.json");
101383
+ return join67(appexContainerData(), "Library", "Application Support", "tesseract-autofill-bridge.json");
101203
101384
  }
101204
101385
  var infoLoggedOnce = false;
101205
101386
  function writeBridgeInfo(port, token3, log9) {
@@ -101214,7 +101395,7 @@ function writeBridgeInfo(port, token3, log9) {
101214
101395
  }
101215
101396
  return;
101216
101397
  }
101217
- const dir2 = join66(containerData, "Library", "Application Support");
101398
+ const dir2 = join67(containerData, "Library", "Application Support");
101218
101399
  mkdirSync34(dir2, { recursive: true });
101219
101400
  writeFileSync35(bridgeInfoPath(), JSON.stringify({ port, token: token3, pid: process.pid, ts: Date.now() }), { mode: 384 });
101220
101401
  if (!infoLoggedOnce) {
@@ -101375,7 +101556,7 @@ function startAutofillBridge(log9 = () => {
101375
101556
  if (process.platform !== "darwin" || wss2)
101376
101557
  return;
101377
101558
  stopping2 = false;
101378
- token2 = randomBytes7(24).toString("base64url");
101559
+ token2 = randomBytes9(24).toString("base64url");
101379
101560
  bind(0, log9);
101380
101561
  }
101381
101562
  function stopAutofillBridge() {
@@ -101460,7 +101641,7 @@ function createNodePowerController() {
101460
101641
  import { execFileSync as execFileSync4 } from "node:child_process";
101461
101642
  import { chmodSync as chmodSync6, existsSync as existsSync33, mkdirSync as mkdirSync35, writeFileSync as writeFileSync36 } from "node:fs";
101462
101643
  import { homedir as homedir11, platform as platform5 } from "node:os";
101463
- import { dirname as dirname27, join as join67, resolve as resolve12 } from "node:path";
101644
+ import { dirname as dirname27, join as join68, resolve as resolve12 } from "node:path";
101464
101645
  import { fileURLToPath as fileURLToPath5 } from "node:url";
101465
101646
  var HOST_NAME = "com.tesseract.companion";
101466
101647
  var EXTENSION_IDS = ["icpnghhpcmaonjckkpilpajblfehpplo", "giolfpookdhjfafcchaflhmigghaeffn"];
@@ -101469,18 +101650,18 @@ function browserProfileDirs() {
101469
101650
  const home = homedir11();
101470
101651
  const p = platform5();
101471
101652
  if (p === "darwin") {
101472
- const base = join67(home, "Library/Application Support");
101473
- return ["Google/Chrome", "Google/Chrome Beta", "Google/Chrome Dev", "Google/Chrome Canary", "Chromium", "Microsoft Edge", "BraveSoftware/Brave-Browser", "Vivaldi", "Arc"].map((b) => join67(base, b));
101653
+ const base = join68(home, "Library/Application Support");
101654
+ return ["Google/Chrome", "Google/Chrome Beta", "Google/Chrome Dev", "Google/Chrome Canary", "Chromium", "Microsoft Edge", "BraveSoftware/Brave-Browser", "Vivaldi", "Arc"].map((b) => join68(base, b));
101474
101655
  }
101475
101656
  if (p === "win32")
101476
101657
  return [];
101477
- const cfg2 = join67(home, ".config");
101478
- return ["google-chrome", "google-chrome-beta", "chromium", "microsoft-edge", "BraveSoftware/Brave-Browser", "vivaldi"].map((b) => join67(cfg2, b));
101658
+ const cfg2 = join68(home, ".config");
101659
+ return ["google-chrome", "google-chrome-beta", "chromium", "microsoft-edge", "BraveSoftware/Brave-Browser", "vivaldi"].map((b) => join68(cfg2, b));
101479
101660
  }
101480
101661
  function resolveBridge(log9) {
101481
101662
  const candidates = [
101482
101663
  process.env.CCB_COMPANION_HOST_JS,
101483
- process.resourcesPath ? join67(process.resourcesPath, "companion-host", BRIDGE_BASENAME) : void 0
101664
+ process.resourcesPath ? join68(process.resourcesPath, "companion-host", BRIDGE_BASENAME) : void 0
101484
101665
  ];
101485
101666
  try {
101486
101667
  const here = dirname27(fileURLToPath5(import.meta.url));
@@ -101496,7 +101677,7 @@ function resolveBridge(log9) {
101496
101677
  }
101497
101678
  function writeLauncher(daemonDir3, bridge, runtime) {
101498
101679
  const isWin = platform5() === "win32";
101499
- const launcher = join67(daemonDir3, isWin ? "companion-host.cmd" : "companion-host.sh");
101680
+ const launcher = join68(daemonDir3, isWin ? "companion-host.cmd" : "companion-host.sh");
101500
101681
  if (isWin) {
101501
101682
  writeFileSync36(launcher, `@echo off\r
101502
101683
  set ELECTRON_RUN_AS_NODE=1\r
@@ -101534,9 +101715,9 @@ function registerNativeHost(opts) {
101534
101715
  if (!existsSync33(profile))
101535
101716
  continue;
101536
101717
  try {
101537
- const dir2 = join67(profile, "NativeMessagingHosts");
101718
+ const dir2 = join68(profile, "NativeMessagingHosts");
101538
101719
  mkdirSync35(dir2, { recursive: true });
101539
- writeFileSync36(join67(dir2, `${HOST_NAME}.json`), manifestJson);
101720
+ writeFileSync36(join68(dir2, `${HOST_NAME}.json`), manifestJson);
101540
101721
  wrote++;
101541
101722
  } catch {
101542
101723
  }
@@ -101548,7 +101729,7 @@ function registerNativeHost(opts) {
101548
101729
  }
101549
101730
  function registerWindows(daemonDir3, manifestJson, log9) {
101550
101731
  try {
101551
- const manifestPath = join67(daemonDir3, `${HOST_NAME}.json`);
101732
+ const manifestPath = join68(daemonDir3, `${HOST_NAME}.json`);
101552
101733
  writeFileSync36(manifestPath, manifestJson);
101553
101734
  for (const root of ["Software\\Google\\Chrome", "Software\\Microsoft\\Edge", "Software\\BraveSoftware\\Brave-Browser"]) {
101554
101735
  try {
@@ -101568,9 +101749,9 @@ init_daemon_settings();
101568
101749
  // ../../packages/host-core/dist/activity/activity-tracker.js
101569
101750
  init_credentials();
101570
101751
  import { promises as fsp, writeFileSync as writeFileSync37, mkdirSync as mkdirSync36, existsSync as existsSync34 } from "node:fs";
101571
- import { join as join68 } from "node:path";
101752
+ import { join as join69 } from "node:path";
101572
101753
  function activityProfilePath() {
101573
- return join68(daemonDir2(), "activity-profile.json");
101754
+ return join69(daemonDir2(), "activity-profile.json");
101574
101755
  }
101575
101756
  var MAX_APPS = 300;
101576
101757
  var MAX_AGE_DAYS = 120;
@@ -101879,8 +102060,8 @@ async function runDaemon(opts = {}) {
101879
102060
  log9("paths", JSON.stringify(sealBasePaths()));
101880
102061
  const OUR_STARTED_AT = procStartedAt(process.pid);
101881
102062
  let ownsMachine = false;
101882
- const pidfile = () => join69(daemonDir2(), "daemon.lock");
101883
- const statusFile = () => join69(daemonDir2(), "status.json");
102063
+ const pidfile = () => join70(daemonDir2(), "daemon.lock");
102064
+ const statusFile = () => join70(daemonDir2(), "status.json");
101884
102065
  const isAlive2 = (pid) => {
101885
102066
  try {
101886
102067
  process.kill(pid, 0);
@@ -102320,12 +102501,43 @@ async function runDaemon(opts = {}) {
102320
102501
  } catch {
102321
102502
  }
102322
102503
  });
102323
- eapp.on("render-process-gone", (_e, d) => {
102504
+ const renderRecoveries = [];
102505
+ const RECOVER_WINDOW_MS = 6e4, RECOVER_MAX = 4, RECOVER_DELAY_MS = 300;
102506
+ const recoverRenderer = (wc, reason) => {
102507
+ if (!mergedProcess || deliberateMergedQuit())
102508
+ return;
102509
+ if (reason && !["killed", "crashed", "abnormal-exit", "oom", "launch-failed", "integrity-failure"].includes(reason))
102510
+ return;
102511
+ const now2 = Date.now();
102512
+ while (renderRecoveries.length && now2 - renderRecoveries[0] > RECOVER_WINDOW_MS)
102513
+ renderRecoveries.shift();
102514
+ if (renderRecoveries.length >= RECOVER_MAX) {
102515
+ try {
102516
+ log9(`render-recover: ${RECOVER_MAX} reloads in <60s \u2014 backing off (page may be genuinely crashing)`);
102517
+ } catch {
102518
+ }
102519
+ return;
102520
+ }
102521
+ renderRecoveries.push(now2);
102522
+ const contents = wc;
102523
+ setTimeout(() => {
102524
+ try {
102525
+ if (!contents || contents.isDestroyed?.())
102526
+ return;
102527
+ log9(`render-recover: reloading window after '${reason ?? "unknown"}' (recovery #${renderRecoveries.length})`);
102528
+ contents.reload?.();
102529
+ } catch {
102530
+ }
102531
+ }, RECOVER_DELAY_MS).unref?.();
102532
+ };
102533
+ eapp.on("render-process-gone", (_e, wc, details) => {
102534
+ const reason = details?.reason;
102324
102535
  try {
102325
- log9(`render-process-gone (kept alive) uptime=${Date.now() - daemonStartedAt}ms: ${JSON.stringify(d)}`);
102536
+ log9(`render-process-gone (kept alive) uptime=${Date.now() - daemonStartedAt}ms: ${JSON.stringify(details ?? {})}`);
102326
102537
  snapshotProcs("render-gone");
102327
102538
  } catch {
102328
102539
  }
102540
+ recoverRenderer(wc, reason);
102329
102541
  });
102330
102542
  eapp.on("child-process-gone", (_e, d) => {
102331
102543
  try {
@@ -102344,7 +102556,7 @@ async function runDaemon(opts = {}) {
102344
102556
  }
102345
102557
  if (ownsMachine)
102346
102558
  setHostDaemonRole(true);
102347
- const localToken = randomBytes8(24).toString("hex");
102559
+ const localToken = randomBytes10(24).toString("hex");
102348
102560
  const sendToViewer = (kind, sessionId, clientId, msg) => {
102349
102561
  const lb = loopbackRdeskSend.get(clientId);
102350
102562
  if (lb) {
@@ -102503,7 +102715,7 @@ async function runDaemon(opts = {}) {
102503
102715
  if (r.status === 401) {
102504
102716
  log9("device token no longer valid \u2014 dropping to local-only on restart");
102505
102717
  try {
102506
- unlinkSync6(join69(daemonDir2(), "credentials.json"));
102718
+ unlinkSync6(join70(daemonDir2(), "credentials.json"));
102507
102719
  } catch {
102508
102720
  }
102509
102721
  shutdown(0, "token-revoked-401");
@@ -102901,14 +103113,14 @@ async function runDaemon(opts = {}) {
102901
103113
  // is on; with it off it is the standalone Remote Desktop app, and we forward rather than
102902
103114
  // handle. `provider()` re-reads the setting every time, so switching it back on takes the
102903
103115
  // channel away from the claimant mid-flight — there is never a moment with two servers.
102904
- onRdesk: (payload, clientId, sessionId) => {
103116
+ onRdesk: (payload, clientId, sessionId, viewerOwner) => {
102905
103117
  const p = rdeskChannel.provider();
102906
103118
  if (p && clientId) {
102907
103119
  p.control(sessionId, clientId, payload);
102908
103120
  return;
102909
103121
  }
102910
103122
  if (rdesk && clientId)
102911
- void rdesk.onClientMessage(sessionId, { clientId }, payload);
103123
+ void rdesk.onClientMessage(sessionId, { clientId, sameAccount: viewerOwner }, payload);
102912
103124
  },
102913
103125
  onRdeskSignal: (payload, clientId, sessionId) => {
102914
103126
  const p = rdeskChannel.provider();
@@ -103001,7 +103213,7 @@ async function runDaemon(opts = {}) {
103001
103213
  if (appPid > 0 && appPid !== process.pid) {
103002
103214
  const quitMarker = () => {
103003
103215
  try {
103004
- return Number(readFileSync45(join69(daemonDir2(), "app-quit.marker"), "utf-8").trim()) || 0;
103216
+ return Number(readFileSync45(join70(daemonDir2(), "app-quit.marker"), "utf-8").trim()) || 0;
103005
103217
  } catch {
103006
103218
  return 0;
103007
103219
  }
@@ -103026,7 +103238,7 @@ async function runDaemon(opts = {}) {
103026
103238
  log9(`desktop app (pid ${appPid}) is gone with NO clean-quit marker \u2014 reporting`);
103027
103239
  const tail3 = (name) => {
103028
103240
  try {
103029
- const buf = readFileSync45(join69(daemonDir2(), name), "utf-8");
103241
+ const buf = readFileSync45(join70(daemonDir2(), name), "utf-8");
103030
103242
  return buf.length > 4096 ? buf.slice(-4096) : buf;
103031
103243
  } catch {
103032
103244
  return void 0;
@@ -103057,7 +103269,7 @@ async function runDaemon(opts = {}) {
103057
103269
  }
103058
103270
  try {
103059
103271
  const DAEMON_LOG_CAP = 2 * 1024 * 1024;
103060
- const daemonLogFile = join69(daemonDir2(), "daemon.log");
103272
+ const daemonLogFile = join70(daemonDir2(), "daemon.log");
103061
103273
  const capTimer = setInterval(() => {
103062
103274
  try {
103063
103275
  if (existsSync35(daemonLogFile) && statSync9(daemonLogFile).size > DAEMON_LOG_CAP) {