tesseract-agent 0.11.22 → 0.11.23

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.
@@ -20016,7 +20016,7 @@ var init_screen = __esm({
20016
20016
  vars: [],
20017
20017
  sensitive: true,
20018
20018
  content: `
20019
- YOU CAN SEE AND CONTROL THIS MAC. You control the developer's ACTUAL macOS screen directly through the \`tesseract-computer\` tools, and you ALSO have your full Claude Code toolset (bash, files, editors, web, etc.). Use whichever fits: drive the screen for on-screen tasks, use your coding/file/shell tools for the rest, and combine them freely.
20019
+ YOU CAN SEE AND CONTROL THIS COMPUTER. You control the developer's ACTUAL screen directly through the \`tesseract-computer\` tools, and you ALSO have your full Claude Code toolset (bash, files, editors, web, etc.). Use whichever fits: drive the screen for on-screen tasks, use your coding/file/shell tools for the rest, and combine them freely.
20020
20020
 
20021
20021
  THE LOOP IS SEE \u2192 ACT \u2192 VERIFY:
20022
20022
  - Call \`see_screen\` to LOOK \u2014 it returns the current screen as an image. YOU read the image and decide where to act. Call it before acting on anything new, and AGAIN after acting to confirm what happened.
@@ -20028,7 +20028,7 @@ VERIFY & SELF-CORRECT AS YOU GO: after EVERY action, \`see_screen\` and check it
20028
20028
 
20029
20029
  READING TEXT ALOUD: when the developer asks you to READ something on the screen (an article, a message, an error, code, a field's value) \u2014 do NOT transcribe it from the picture, you will misread it. Call \`read_text\` with a short \`what\`, and when you can see the block, the region rectangle (x1,y1,x2,y2) around it \u2014 it pulls the EXACT text via accessibility. Then read that back. When it's spoken, keep it to the relevant part; if it's long, give the gist or ask how much of it to read.
20030
20030
 
20031
- MORE HELPERS (prefer these over guessing or blind waits): \`read_ui\` lists the on-screen elements with their exact click coordinates \u2014 use it to act precisely on a busy/unfamiliar window. \`wait_for\` waits until something appears or disappears (page loaded, spinner gone) instead of a blind pause. \`get_clipboard\` / \`get_selection\` read the clipboard or whatever the developer has highlighted (use \`get_selection\` when they say "this"). \`open_url\` / \`open_path\` open a link or a file/folder directly (more reliable than navigating by hand). \`list_windows\` shows what's open and \`focus_window\` brings a specific one to the front. \`notify\` posts a macOS notification.
20031
+ MORE HELPERS (prefer these over guessing or blind waits): \`read_ui\` lists the on-screen elements with their exact click coordinates \u2014 use it to act precisely on a busy/unfamiliar window. \`wait_for\` waits until something appears or disappears (page loaded, spinner gone) instead of a blind pause. \`get_clipboard\` / \`get_selection\` read the clipboard or whatever the developer has highlighted (use \`get_selection\` when they say "this"). \`open_url\` / \`open_path\` open a link or a file/folder directly (more reliable than navigating by hand). \`list_windows\` shows what's open and \`focus_window\` brings a specific one to the front. \`notify\` posts a system notification.
20032
20032
 
20033
20033
  BE TRUTHFUL: never say something worked, opened, or is showing unless you JUST confirmed it with a fresh \`see_screen\`. A tool returning "done" only means the keystroke/click was sent \u2014 NOT that it had the effect you wanted.
20034
20034
 
@@ -20046,7 +20046,7 @@ WHEN YOUR REPLIES ARE READ ALOUD (voice): keep them SHORT and conversational \u2
20046
20046
  vars: [],
20047
20047
  content: `
20048
20048
 
20049
- YOU ARE THE VOICE LAYER. You talk with the developer, understand what they want, and hand the actual DOING to a powerful agent (Claude Code) that fully controls this Mac \u2014 it takes high-res screenshots, uses the mouse/keyboard, the accessibility tree, processes, files, and every other tool. You do NOT operate the screen yourself.
20049
+ YOU ARE THE VOICE LAYER. You talk with the developer, understand what they want, and hand the actual DOING to a powerful agent (Claude Code) that fully controls this computer \u2014 it takes high-res screenshots, uses the mouse/keyboard, the accessibility tree, processes, files, and every other tool. You do NOT operate the screen yourself.
20050
20050
 
20051
20051
  You also see a LOW-RESOLUTION ~1 fps view of the screen, so you can follow along, describe what's happening, and confirm results in your own words \u2014 but it's just for awareness; never try to act on it.
20052
20052
 
@@ -27125,6 +27125,11 @@ function engineSearchDirs(opts) {
27125
27125
  const programFiles = env.ProgramFiles ?? "C:\\Program Files";
27126
27126
  const choco = env.ChocolateyInstall ?? "C:\\ProgramData\\chocolatey";
27127
27127
  return dedupe([
27128
+ // The native installer's launcher dir (Claude Code's `install.ps1` writes %USERPROFILE%\.local\bin
27129
+ // there, mirroring POSIX ~/.local/bin). Omitting it read a working native install as "not
27130
+ // installed" in the picker/Settings while chat worked — and left it off the probe PATH preamble,
27131
+ // since windowsPathPreamble derives its dirs from here.
27132
+ j(home, ".local", "bin"),
27128
27133
  j(appData, "npm"),
27129
27134
  j(localAppData, "pnpm"),
27130
27135
  j(localAppData, "Volta", "bin"),
@@ -28603,7 +28608,24 @@ function parseNSFilenames(raw) {
28603
28608
  function parseUriList(raw) {
28604
28609
  return raw.split(/[\r\n]+/).map((l) => l.trim()).filter((l) => l.startsWith("file://")).map(decodeFileUrl);
28605
28610
  }
28606
- function readFilePathsWith(read) {
28611
+ function parseCfHdrop(bytes3) {
28612
+ if (bytes3.length < 20)
28613
+ return [];
28614
+ const dv = new DataView(bytes3.buffer, bytes3.byteOffset, bytes3.byteLength);
28615
+ const pFiles = dv.getUint32(0, true);
28616
+ if (pFiles >= bytes3.length)
28617
+ return [];
28618
+ const fWide = dv.getUint32(16, true) !== 0;
28619
+ const body = new TextDecoder(fWide ? "utf-16le" : "latin1").decode(bytes3.subarray(pFiles));
28620
+ const out = [];
28621
+ for (const seg of body.split("\0")) {
28622
+ if (!seg)
28623
+ break;
28624
+ out.push(seg);
28625
+ }
28626
+ return out;
28627
+ }
28628
+ function readFilePathsWith(read, readBuffer) {
28607
28629
  const tryRead = (format) => {
28608
28630
  try {
28609
28631
  return read(format) || "";
@@ -28626,6 +28648,17 @@ function readFilePathsWith(read) {
28626
28648
  if (p.length)
28627
28649
  return p;
28628
28650
  }
28651
+ if (readBuffer) {
28652
+ try {
28653
+ const buf = readBuffer("CF_HDROP");
28654
+ if (buf && buf.length) {
28655
+ const p = parseCfHdrop(buf);
28656
+ if (p.length)
28657
+ return p;
28658
+ }
28659
+ } catch {
28660
+ }
28661
+ }
28629
28662
  return [];
28630
28663
  }
28631
28664
  var init_clipboard_files = __esm({
@@ -38653,11 +38686,11 @@ function inflightParamsOf(p) {
38653
38686
  clientRequestId: pick3("clientRequestId")
38654
38687
  };
38655
38688
  }
38656
- function recordInflightTurn(sessionId, params, attempts = 0) {
38689
+ function recordInflightTurn(sessionId, params, attempts2 = 0) {
38657
38690
  try {
38658
38691
  const dir2 = inflightDir();
38659
38692
  mkdirSync8(dir2, { recursive: true });
38660
- const entry = { sessionId, startedAt: (/* @__PURE__ */ new Date()).toISOString(), attempts, params: inflightParamsOf({ ...params, sessionId }) };
38693
+ const entry = { sessionId, startedAt: (/* @__PURE__ */ new Date()).toISOString(), attempts: attempts2, params: inflightParamsOf({ ...params, sessionId }) };
38661
38694
  const tmp = `${fileFor(sessionId)}.tmp`;
38662
38695
  writeFileSync6(tmp, JSON.stringify(entry), { mode: 384 });
38663
38696
  renameSync4(tmp, fileFor(sessionId));
@@ -39798,8 +39831,8 @@ async function anthropicMessage(args) {
39798
39831
  }
39799
39832
  async function anthropicCall(args) {
39800
39833
  const { env, model, system, content, maxTokens, timeoutMs, label } = args;
39801
- const attempts = Math.max(1, args.attempts ?? 1);
39802
- const budgetMs = args.budgetMs ?? attempts * timeoutMs + 4e3;
39834
+ const attempts2 = Math.max(1, args.attempts ?? 1);
39835
+ const budgetMs = args.budgetMs ?? attempts2 * timeoutMs + 4e3;
39803
39836
  const deadline = Date.now() + budgetMs;
39804
39837
  const base = { ...model ? { model } : {}, max_tokens: maxTokens, system, messages: [{ role: "user", content }] };
39805
39838
  const useSchema = !!args.schema && !schemaUnsupported.has(schemaKey(anthropicBaseUrl(env), model));
@@ -39808,7 +39841,7 @@ async function anthropicCall(args) {
39808
39841
  const headers = await anthropicHeaders(env);
39809
39842
  if (!headers)
39810
39843
  return { text: null, rateLimited: false };
39811
- for (let attempt = 0; attempt < attempts; attempt++) {
39844
+ for (let attempt = 0; attempt < attempts2; attempt++) {
39812
39845
  const remaining = deadline - Date.now();
39813
39846
  if (remaining < 1e3) {
39814
39847
  log(`${label}: out of budget after ${attempt} attempt(s) \u2014 giving up rather than starting a leg that cannot finish.`);
@@ -39845,7 +39878,7 @@ async function anthropicCall(args) {
39845
39878
  }
39846
39879
  const rateLimited = res.status === 429 || res.status === 529;
39847
39880
  const modelRejected = (res.status === 400 || res.status === 403 || res.status === 404) && !!model;
39848
- if (rateLimited && attempt < attempts - 1) {
39881
+ if (rateLimited && attempt < attempts2 - 1) {
39849
39882
  let stated = 0;
39850
39883
  try {
39851
39884
  stated = Number(res.headers?.get?.("retry-after")) * 1e3;
@@ -39861,7 +39894,7 @@ async function anthropicCall(args) {
39861
39894
  log(`${label}: HTTP ${res.status} (${model}) ${errBody.slice(0, 200)}`);
39862
39895
  return { text: null, rateLimited, modelRejected };
39863
39896
  } catch (err2) {
39864
- if (args.retryOnThrow && attempt < attempts - 1) {
39897
+ if (args.retryOnThrow && attempt < attempts2 - 1) {
39865
39898
  const wait = Math.max(0, Math.min(800 * (attempt + 1) * (attempt + 1), deadline - Date.now()));
39866
39899
  log(`${label}: call failed (${err2 instanceof Error ? err2.message : String(err2)}) \u2014 retry ${attempt + 1} in ${wait}ms`);
39867
39900
  await new Promise((r) => setTimeout(r, wait));
@@ -39995,7 +40028,7 @@ function killProcessTree(child, signal2 = "SIGKILL") {
39995
40028
  const pid = child.pid;
39996
40029
  if (process.platform === "win32") {
39997
40030
  try {
39998
- spawn("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
40031
+ spawn("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
39999
40032
  } catch {
40000
40033
  }
40001
40034
  return;
@@ -40480,7 +40513,7 @@ function findUserInstalledClaude(binName) {
40480
40513
  if (existsSync6(p))
40481
40514
  return p;
40482
40515
  try {
40483
- const out = execFileSync(process.platform === "win32" ? "where" : "which", [binName], { encoding: "utf8" }).split("\n")[0]?.trim();
40516
+ const out = execFileSync(process.platform === "win32" ? "where" : "which", [binName], { encoding: "utf8", windowsHide: true }).split("\n")[0]?.trim();
40484
40517
  if (out && existsSync6(out))
40485
40518
  return out;
40486
40519
  } catch {
@@ -40497,17 +40530,21 @@ function resolvedClaudeCodePath() {
40497
40530
  function resolveClaudeCodeExecutable() {
40498
40531
  if (cachedClaudeExe)
40499
40532
  return cachedClaudeExe;
40500
- const binName = process.platform === "win32" ? "claude.exe" : "claude";
40501
- const user = findUserInstalledClaude(binName);
40502
- if (user)
40503
- return cachedClaudeExe = user;
40533
+ const binNames = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
40534
+ for (const name of binNames) {
40535
+ const user = findUserInstalledClaude(name);
40536
+ if (user)
40537
+ return cachedClaudeExe = user;
40538
+ }
40504
40539
  const resourcesPath = process.resourcesPath;
40505
40540
  if (resourcesPath) {
40506
40541
  const platKey = process.platform === "darwin" ? "mac" : process.platform === "win32" ? "win" : "linux";
40507
40542
  for (const dir2 of [`${platKey}-${process.arch}`, platKey]) {
40508
- const bundled = join19(resourcesPath, "claude-cli", dir2, binName);
40509
- if (existsSync6(bundled))
40510
- return cachedClaudeExe = bundled;
40543
+ for (const name of binNames) {
40544
+ const bundled = join19(resourcesPath, "claude-cli", dir2, name);
40545
+ if (existsSync6(bundled))
40546
+ return cachedClaudeExe = bundled;
40547
+ }
40511
40548
  }
40512
40549
  }
40513
40550
  throw new Error("Claude Code isn\u2019t installed. Tesseract runs the agent framework you have installed rather than bundling one \u2014 install it with `curl -fsSL https://claude.ai/install.sh | bash` (Windows: `irm https://claude.ai/install.ps1 | iex`), or pick a different engine in Settings.");
@@ -40638,7 +40675,14 @@ function cliEffortLevels() {
40638
40675
  return cachedEfforts;
40639
40676
  cachedEfforts = null;
40640
40677
  try {
40641
- const help = execFileSync(resolveClaudeCodeExecutable(), ["--help"], { encoding: "utf8", timeout: 5e3, maxBuffer: 8 * 1024 * 1024 });
40678
+ const claudeExe = resolveClaudeCodeExecutable();
40679
+ const help = execFileSync(claudeExe, ["--help"], {
40680
+ encoding: "utf8",
40681
+ timeout: 5e3,
40682
+ maxBuffer: 8 * 1024 * 1024,
40683
+ windowsHide: true,
40684
+ ...process.platform === "win32" && /\.(cmd|bat)$/i.test(claudeExe) ? { shell: true } : {}
40685
+ });
40642
40686
  const line = help.split("\n").find((l) => l.includes("--effort"));
40643
40687
  const listed = line ? /\(([^)]*)\)/.exec(line) : null;
40644
40688
  const levels = listed?.[1].split(",").map((x) => x.trim()).filter(Boolean) ?? [];
@@ -40849,7 +40893,7 @@ function resolveNodeExecutable() {
40849
40893
  return cachedNodeExe = p;
40850
40894
  }
40851
40895
  try {
40852
- const found = execSync(process.platform === "win32" ? "where node" : "command -v node", { encoding: "utf8", timeout: 3e3 }).split(/\r?\n/)[0].trim();
40896
+ const found = execSync(process.platform === "win32" ? "where node" : "command -v node", { encoding: "utf8", timeout: 3e3, windowsHide: true }).split(/\r?\n/)[0].trim();
40853
40897
  if (found && existsSync6(found))
40854
40898
  return cachedNodeExe = found;
40855
40899
  } catch {
@@ -45829,11 +45873,14 @@ ${prompt("agent.system.long-running")}`;
45829
45873
  };
45830
45874
  delete spawnEnv.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS;
45831
45875
  live.endpointEnv = anthropicEnvOf(spawnEnv);
45876
+ const winBatch = process.platform === "win32" && /\.(cmd|bat)$/i.test(options.command);
45832
45877
  const child = spawn2(options.command, options.args, {
45833
45878
  cwd: options.cwd,
45834
45879
  env: spawnEnv,
45835
45880
  signal: options.signal,
45836
45881
  stdio: ["pipe", "pipe", "pipe"],
45882
+ windowsHide: true,
45883
+ ...winBatch ? { shell: true } : {},
45837
45884
  // Own process group (POSIX) so Stop can SIGKILL the WHOLE tree — the CLI's
45838
45885
  // Bash tool spawns grandchildren (builds, dev servers, sleeps) that a
45839
45886
  // single-PID kill would orphan. Pipes keep it attached; we never unref, so
@@ -49027,7 +49074,7 @@ function nodeExe() {
49027
49074
  }
49028
49075
  if (!cachedNodeExe2) {
49029
49076
  try {
49030
- const found = execSync2(process.platform === "win32" ? "where node" : "command -v node", { encoding: "utf8" }).split(/\r?\n/)[0].trim();
49077
+ const found = execSync2(process.platform === "win32" ? "where node" : "command -v node", { encoding: "utf8", windowsHide: true }).split(/\r?\n/)[0].trim();
49031
49078
  if (found && existsSync9(found))
49032
49079
  cachedNodeExe2 = found;
49033
49080
  } catch {
@@ -49110,7 +49157,7 @@ function writeConfig(cfg2) {
49110
49157
  function ccr(args, timeout = 8e3) {
49111
49158
  const { exe, asNode } = nodeExe();
49112
49159
  return new Promise((resolve16) => {
49113
- execFile4(exe, [ccrCliPath(), ...args], { env: ccrEnv(asNode), timeout }, (err2, stdout, stderr) => {
49160
+ execFile4(exe, [ccrCliPath(), ...args], { env: ccrEnv(asNode), timeout, windowsHide: true }, (err2, stdout, stderr) => {
49114
49161
  resolve16({ ok: !err2, out: `${stdout || ""}${stderr || ""}` });
49115
49162
  });
49116
49163
  });
@@ -49822,7 +49869,7 @@ function rememberedPath(engineId) {
49822
49869
  function which(bin, path6) {
49823
49870
  const tool2 = process.platform === "win32" ? "where" : "which";
49824
49871
  try {
49825
- const out = execFileSync2(tool2, [bin], { env: { ...process.env, PATH: path6 }, timeout: 4e3, encoding: "utf-8" });
49872
+ const out = execFileSync2(tool2, [bin], { env: { ...process.env, PATH: path6 }, timeout: 4e3, encoding: "utf-8", windowsHide: true });
49826
49873
  return out.split(/\r?\n/).map((l) => l.trim()).find(Boolean) || void 0;
49827
49874
  } catch {
49828
49875
  return void 0;
@@ -85795,7 +85842,7 @@ var init_mesh_ca = __esm({
85795
85842
  init_base_paths();
85796
85843
  import { existsSync as existsSync61, mkdtempSync as mkdtempSync4, readFileSync as readFileSync70, rmSync as rmSync14 } from "node:fs";
85797
85844
  import { createHash as createHash12 } from "node:crypto";
85798
- import { execFileSync as execFileSync9, spawn as spawn14 } from "node:child_process";
85845
+ import { execFileSync as execFileSync9, spawn as spawn16 } from "node:child_process";
85799
85846
  import { fileURLToPath as fileURLToPath8 } from "node:url";
85800
85847
  import { tmpdir as tmpdir8 } from "node:os";
85801
85848
  import { dirname as dirname47, join as join104, resolve as resolve15 } from "node:path";
@@ -96176,7 +96223,7 @@ function checkTrust(rootDir, trusted, relaxHardlink = false) {
96176
96223
  } catch {
96177
96224
  return "unreadable";
96178
96225
  }
96179
- if ((st.mode & 2) !== 0)
96226
+ if (!isWindows && (st.mode & 2) !== 0)
96180
96227
  return "world-writable";
96181
96228
  if (!isWindows && typeof process.getuid === "function") {
96182
96229
  const uid = process.getuid();
@@ -97613,6 +97660,13 @@ function capture(command, args, opts = {}) {
97613
97660
  }
97614
97661
  });
97615
97662
  }
97663
+ async function loadLibnutWindowing() {
97664
+ if (process.platform === "linux" && !process.env.DISPLAY)
97665
+ return null;
97666
+ const n = await loadLibnut();
97667
+ const w = n;
97668
+ return w && typeof w.getActiveWindow === "function" && typeof w.getWindowTitle === "function" && typeof w.focusWindow === "function" && typeof w.keyTap === "function" && typeof w.keyToggle === "function" ? w : null;
97669
+ }
97616
97670
  var libnutPromise;
97617
97671
  function loadLibnut() {
97618
97672
  if (libnutPromise)
@@ -97784,6 +97838,10 @@ function createDaemonOsControlRunner() {
97784
97838
  const p = n.getMousePos();
97785
97839
  return { x: p.x, y: p.y };
97786
97840
  },
97841
+ // KNOWN LIMITATION (Windows): libnut-win32's typeString drives the ASCII keyboard-event path and
97842
+ // CANNOT emit non-ASCII / Unicode characters (accents, CJK, emoji) — those come out wrong or empty.
97843
+ // Plain ASCII typing is fine. A dedicated Unicode helper (SendInput with KEYEVENTF_UNICODE) is a
97844
+ // separate rdesk task; until then typeString stays the text path. macOS/Linux type Unicode fine.
97787
97845
  async typeText(t) {
97788
97846
  const n = await nutOrThrow();
97789
97847
  n.typeString(t);
@@ -97797,8 +97855,20 @@ function createDaemonOsControlRunner() {
97797
97855
  n.keyTap(key2, modifiers);
97798
97856
  log3(`key ${combo}`);
97799
97857
  },
97800
- // App launch (the one non-input capability the voice path needs — shells `open`)
97858
+ // App launch (the one non-input capability the voice path needs) — platform-correct launcher.
97801
97859
  async appLaunch(app) {
97860
+ if (process.platform === "win32") {
97861
+ const r2 = await capture("cmd", ["/c", "start", "", app], { timeoutMs: 1e4 });
97862
+ if (r2.code !== 0)
97863
+ throw new Error(r2.stderr.trim() || `Could not launch ${app}`);
97864
+ return;
97865
+ }
97866
+ if (process.platform === "linux") {
97867
+ const r2 = await capture("xdg-open", [app], { timeoutMs: 1e4 });
97868
+ if (r2.code !== 0)
97869
+ throw new Error(r2.stderr.trim() || `Could not launch ${app}`);
97870
+ return;
97871
+ }
97802
97872
  const r = await capture("open", [app.includes(".") ? "-b" : "-a", app], { timeoutMs: 1e4 });
97803
97873
  if (r.code !== 0)
97804
97874
  throw new Error(r.stderr.trim() || `Could not launch ${app}`);
@@ -102017,7 +102087,7 @@ function run2(bin, args, opts) {
102017
102087
  let timedOut = false;
102018
102088
  let child;
102019
102089
  try {
102020
- child = spawn7(bin, args, { cwd: opts?.cwd ?? mediaSpawnCwd(), stdio: ["ignore", "pipe", "pipe"] });
102090
+ child = spawn7(bin, args, { cwd: opts?.cwd ?? mediaSpawnCwd(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
102021
102091
  } catch (e) {
102022
102092
  reject(e instanceof Error ? e : new Error(String(e)));
102023
102093
  return;
@@ -102427,7 +102497,7 @@ async function realesrganGpuWorks() {
102427
102497
  return false;
102428
102498
  }
102429
102499
  await new Promise((resolve16, reject) => {
102430
- const c = spawn8(re.bin, ["-i", inP, "-o", outP, "-n", model, "-s", "2", "-m", re.models, "-g", "0"], { cwd: upscaleSpawnCwd(), stdio: "ignore" });
102500
+ const c = spawn8(re.bin, ["-i", inP, "-o", outP, "-n", model, "-s", "2", "-m", re.models, "-g", "0"], { cwd: upscaleSpawnCwd(), stdio: "ignore", windowsHide: true });
102431
102501
  const t = setTimeout(() => {
102432
102502
  killChild(c, "SIGKILL", "realesrgan GPU probe (timeout)");
102433
102503
  reject(new Error("probe timeout"));
@@ -104875,6 +104945,7 @@ function readRecord(buf, base, rel) {
104875
104945
  }
104876
104946
  async function windowsFavorites() {
104877
104947
  const script = [
104948
+ `[Console]::OutputEncoding=[System.Text.Encoding]::UTF8`,
104878
104949
  `$ErrorActionPreference='SilentlyContinue'`,
104879
104950
  `$sh = New-Object -ComObject Shell.Application`,
104880
104951
  `$ns = $sh.Namespace('shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}')`,
@@ -104882,10 +104953,10 @@ async function windowsFavorites() {
104882
104953
  ].join("; ");
104883
104954
  const encoded = Buffer.from(script, "utf16le").toString("base64");
104884
104955
  const stdout = await new Promise((resolve16) => {
104885
- const child = execFile5("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encoded], { timeout: 4e3, windowsHide: true, maxBuffer: 1 << 20 }, (err2, out) => resolve16(err2 ? "" : out));
104956
+ const child = execFile5("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encoded], { timeout: 4e3, windowsHide: true, maxBuffer: 1 << 20, encoding: "utf8" }, (err2, out) => resolve16(err2 ? "" : out));
104886
104957
  child.on("error", () => resolve16(""));
104887
104958
  });
104888
- const items = stdout.split(/\r?\n/).map((line) => {
104959
+ const items = stdout.replace(/^/, "").split(/\r?\n/).map((line) => {
104889
104960
  const tab = line.indexOf(" ");
104890
104961
  if (tab < 0)
104891
104962
  return null;
@@ -105397,10 +105468,19 @@ async function runRemoteFsOp(root, op2, args, quotaKey) {
105397
105468
  id = statId(join56(real, e.name));
105398
105469
  } catch {
105399
105470
  }
105471
+ let isDir = e.isDirectory();
105472
+ if (e.isSymbolicLink()) {
105473
+ const resolved = safeContentPath(join56(real, e.name));
105474
+ try {
105475
+ isDir = resolved != null && statSync16(resolved).isDirectory();
105476
+ } catch {
105477
+ isDir = false;
105478
+ }
105479
+ }
105400
105480
  return {
105401
105481
  name: e.name,
105402
105482
  path: join56(dir2, e.name),
105403
- isDir: e.isDirectory(),
105483
+ isDir,
105404
105484
  ignored: ignored.has(e.name),
105405
105485
  size: id?.size,
105406
105486
  mtimeMs: id?.mtimeMs,
@@ -105476,13 +105556,35 @@ async function runRemoteFsOp(root, op2, args, quotaKey) {
105476
105556
  return OUTSIDE("write");
105477
105557
  const off = Math.max(0, Math.floor(Number(a[1]) || 0));
105478
105558
  const buf = Buffer.from(String(a[2] ?? ""), "base64");
105559
+ if (off === 0) {
105560
+ const tmp = join56(dirname22(real), `.${basename8(real)}.tesseract-upload-${process.pid}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`);
105561
+ let fd2 = null;
105562
+ try {
105563
+ await mkdir10(dirname22(real), { recursive: true });
105564
+ fd2 = openSync7(tmp, "w");
105565
+ if (buf.length > 0)
105566
+ writeSync2(fd2, buf, 0, buf.length, 0);
105567
+ closeSync7(fd2);
105568
+ fd2 = null;
105569
+ await renameAsync(tmp, real);
105570
+ return { ok: true, bytesWritten: buf.length };
105571
+ } catch (err2) {
105572
+ return { ok: false, error: String(err2.message) };
105573
+ } finally {
105574
+ if (fd2 != null)
105575
+ try {
105576
+ closeSync7(fd2);
105577
+ } catch {
105578
+ }
105579
+ try {
105580
+ rmSync6(tmp, { force: true });
105581
+ } catch {
105582
+ }
105583
+ }
105584
+ }
105479
105585
  let fd = null;
105480
105586
  try {
105481
- if (off === 0) {
105482
- await mkdir10(dirname22(real), { recursive: true });
105483
- fd = openSync7(real, "w");
105484
- } else
105485
- fd = openSync7(real, "r+");
105587
+ fd = openSync7(real, "r+");
105486
105588
  if (buf.length > 0)
105487
105589
  writeSync2(fd, buf, 0, buf.length, off);
105488
105590
  return { ok: true, bytesWritten: buf.length };
@@ -105607,8 +105709,16 @@ async function runRemoteFsOp(root, op2, args, quotaKey) {
105607
105709
  if (!insideOk(oldPath))
105608
105710
  return OUTSIDE("rename");
105609
105711
  const newPath = join56(dirname22(oldPath), newName);
105610
- if (existsSync24(newPath))
105611
- return { ok: false, error: `A file named "${newName}" already exists.` };
105712
+ const caseOnlyRename = newPath.toLowerCase() === oldPath.toLowerCase() && newPath !== oldPath;
105713
+ if (!caseOnlyRename && existsSync24(newPath)) {
105714
+ let targetIsDir = false;
105715
+ try {
105716
+ targetIsDir = statSync16(newPath).isDirectory();
105717
+ } catch {
105718
+ }
105719
+ if (targetIsDir)
105720
+ return { ok: false, error: `A folder named "${newName}" already exists.` };
105721
+ }
105612
105722
  try {
105613
105723
  await renameAsync(oldPath, newPath);
105614
105724
  return { ok: true, newPath };
@@ -105700,22 +105810,38 @@ async function runRemoteFsOp(root, op2, args, quotaKey) {
105700
105810
  case "movePaths": {
105701
105811
  const srcPaths = Array.isArray(a[0]) ? a[0] : [];
105702
105812
  const destDir = String(a[1] ?? "");
105813
+ const destNamesArg = a[2];
105814
+ const destNames = Array.isArray(destNamesArg) ? destNamesArg.map((n) => typeof n === "string" && validName(n) ? n : void 0) : typeof destNamesArg === "string" && validName(destNamesArg) ? [destNamesArg] : [];
105703
105815
  if (!insideOk(destDir))
105704
105816
  return OUTSIDE(op2 === "copyPaths" ? "copy" : "move");
105705
105817
  try {
105706
105818
  const newPaths = [];
105707
- for (const src of srcPaths) {
105819
+ for (let i = 0; i < srcPaths.length; i++) {
105820
+ const src = srcPaths[i];
105708
105821
  if (typeof src !== "string" || !src)
105709
105822
  continue;
105710
105823
  if (!insideOk(src))
105711
105824
  return OUTSIDE(op2 === "copyPaths" ? "copy from" : "move from");
105712
- if (op2 === "movePaths" && dirname22(src) === destDir) {
105825
+ const wantName = destNames[i];
105826
+ if (op2 === "movePaths" && !wantName && dirname22(src) === destDir) {
105713
105827
  newPaths.push(src);
105714
105828
  continue;
105715
105829
  }
105716
- const target = resolveUniqueName(destDir, basename8(src));
105830
+ let target;
105831
+ if (wantName) {
105832
+ target = join56(destDir, wantName);
105833
+ let targetIsDir = false;
105834
+ try {
105835
+ targetIsDir = statSync16(target).isDirectory();
105836
+ } catch {
105837
+ }
105838
+ if (targetIsDir)
105839
+ return { ok: false, error: `A folder named "${wantName}" already exists.` };
105840
+ } else {
105841
+ target = resolveUniqueName(destDir, basename8(src));
105842
+ }
105717
105843
  if (op2 === "copyPaths")
105718
- await cp(src, target, { recursive: true, errorOnExist: false, force: false });
105844
+ await cp(src, target, { recursive: true, errorOnExist: false, force: !!wantName });
105719
105845
  else
105720
105846
  await moveItem(src, target);
105721
105847
  newPaths.push(target);
@@ -106914,17 +107040,17 @@ var PeerLinkManager = class {
106914
107040
  }
106915
107041
  return this.awaitInbound(token4, msg.peerHostId);
106916
107042
  }
106917
- const attempts = [
107043
+ const attempts2 = [
106918
107044
  dialLadder({ peerHostId: msg.peerHostId, candidates: msg.peerCandidates, peerFingerprint256: msg.peerTlsFingerprint256, token: token4 })
106919
107045
  ];
106920
107046
  if (quicOn)
106921
107047
  for (const u of udp)
106922
- attempts.push(this.quicDial(msg, token4, u));
106923
- dbg(`[peer-link] handleGo dial: racing ${attempts.length} rung(s) (TCP${quicOn ? " + QUIC" : ""}) to ${msg.peerHostId}`);
107048
+ attempts2.push(this.quicDial(msg, token4, u));
107049
+ dbg(`[peer-link] handleGo dial: racing ${attempts2.length} rung(s) (TCP${quicOn ? " + QUIC" : ""}) to ${msg.peerHostId}`);
106924
107050
  return new Promise((resolve16, reject) => {
106925
107051
  let done = false, failed = 0;
106926
107052
  const errs = [];
106927
- for (const p of attempts) {
107053
+ for (const p of attempts2) {
106928
107054
  p.then((l) => {
106929
107055
  if (done) {
106930
107056
  try {
@@ -106940,7 +107066,7 @@ var PeerLinkManager = class {
106940
107066
  }, (e) => {
106941
107067
  errs.push(e);
106942
107068
  console.warn(`[peer-link] dial rung failed: ${e.message}`);
106943
- if (++failed === attempts.length && !done)
107069
+ if (++failed === attempts2.length && !done)
106944
107070
  reject(new Error(`peer-link: no rung connected (${errs.map((x) => x.message).join("; ")})`));
106945
107071
  });
106946
107072
  }
@@ -110633,6 +110759,7 @@ function routeInboundLinkFrame(frame) {
110633
110759
  init_dist5();
110634
110760
 
110635
110761
  // ../../packages/host-core/dist/pty-manager.js
110762
+ import { spawn as spawn9 } from "node:child_process";
110636
110763
  import { existsSync as existsSync31 } from "node:fs";
110637
110764
  import { homedir as homedir11 } from "node:os";
110638
110765
  init_safe_kill();
@@ -110668,6 +110795,24 @@ function flush(ptyId, s, sink3) {
110668
110795
  s.bytes = 0;
110669
110796
  sink3.output(ptyId, Buffer.from(joined, "utf8").toString("base64"));
110670
110797
  }
110798
+ function killPtyProc(proc, signal2, label) {
110799
+ if (process.platform !== "win32") {
110800
+ killChild(proc, signal2, label);
110801
+ return;
110802
+ }
110803
+ const pid = proc.pid;
110804
+ if (typeof pid !== "number" || pid <= 0)
110805
+ return;
110806
+ try {
110807
+ proc.kill();
110808
+ } catch {
110809
+ }
110810
+ try {
110811
+ spawn9("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true }).on("error", () => {
110812
+ });
110813
+ } catch {
110814
+ }
110815
+ }
110671
110816
  async function openPty(opts, sink3) {
110672
110817
  const pty = await loadPty();
110673
110818
  if (!pty)
@@ -110736,7 +110881,7 @@ function killPty(ptyId, signal2, clientId) {
110736
110881
  clearTimeout(s.timer);
110737
110882
  s.timer = null;
110738
110883
  }
110739
- killChild(s.proc, signal2, "pty session");
110884
+ killPtyProc(s.proc, signal2, "pty session");
110740
110885
  }
110741
110886
  function releaseClientPtys(clientId) {
110742
110887
  for (const [k, s] of [...sessions2]) {
@@ -110746,7 +110891,7 @@ function releaseClientPtys(clientId) {
110746
110891
  clearTimeout(s.timer);
110747
110892
  s.timer = null;
110748
110893
  }
110749
- killChild(s.proc, "SIGTERM", "pty session");
110894
+ killPtyProc(s.proc, "SIGTERM", "pty session");
110750
110895
  sessions2.delete(k);
110751
110896
  }
110752
110897
  }
@@ -110758,7 +110903,7 @@ function releaseRemotePtys() {
110758
110903
  clearTimeout(s.timer);
110759
110904
  s.timer = null;
110760
110905
  }
110761
- killChild(s.proc, "SIGTERM", "pty session");
110906
+ killPtyProc(s.proc, "SIGTERM", "pty session");
110762
110907
  sessions2.delete(k);
110763
110908
  }
110764
110909
  }
@@ -113855,12 +114000,40 @@ function displayBoundsById(monitorId) {
113855
114000
  const d = el2.screen.getAllDisplays().find((x) => String(x.id) === monitorId) ?? el2.screen.getPrimaryDisplay();
113856
114001
  return d ? { x: d.bounds.x, y: d.bounds.y, width: d.bounds.width, height: d.bounds.height } : null;
113857
114002
  }
114003
+ function virtualDesktopFraction(globalDip) {
114004
+ const el2 = electronModule();
114005
+ const scr = el2?.screen;
114006
+ if (!scr)
114007
+ return { fx: 0, fy: 0 };
114008
+ const toPhys = (p) => typeof scr.dipToScreenPoint === "function" ? scr.dipToScreenPoint(p) : p;
114009
+ const displays = scr.getAllDisplays();
114010
+ if (!displays.length)
114011
+ return { fx: 0, fy: 0 };
114012
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
114013
+ for (const d of displays) {
114014
+ const tl = toPhys({ x: d.bounds.x, y: d.bounds.y });
114015
+ const pw = Math.round(d.bounds.width * (d.scaleFactor || 1));
114016
+ const ph = Math.round(d.bounds.height * (d.scaleFactor || 1));
114017
+ if (tl.x < minX)
114018
+ minX = tl.x;
114019
+ if (tl.y < minY)
114020
+ minY = tl.y;
114021
+ if (tl.x + pw > maxX)
114022
+ maxX = tl.x + pw;
114023
+ if (tl.y + ph > maxY)
114024
+ maxY = tl.y + ph;
114025
+ }
114026
+ const vw = Math.max(1, maxX - minX), vh = Math.max(1, maxY - minY);
114027
+ const phys = toPhys(globalDip);
114028
+ const clamp5 = (n) => n < 0 ? 0 : n > 1 ? 1 : n;
114029
+ return { fx: clamp5((phys.x - minX) / vw), fy: clamp5((phys.y - minY) / vh) };
114030
+ }
113858
114031
  async function wakeDisplay(log10) {
113859
114032
  if (process.platform !== "darwin")
113860
114033
  return;
113861
114034
  try {
113862
- const { spawn: spawn15 } = await import("node:child_process");
113863
- spawn15("caffeinate", ["-u", "-t", "5"], { detached: true, stdio: "ignore" }).unref();
114035
+ const { spawn: spawn17 } = await import("node:child_process");
114036
+ spawn17("caffeinate", ["-u", "-t", "5"], { detached: true, stdio: "ignore" }).unref();
113864
114037
  log10?.("display was asleep \u2014 declared user activity to wake it");
113865
114038
  await new Promise((r) => setTimeout(r, 1200));
113866
114039
  } catch {
@@ -114083,8 +114256,397 @@ var PublisherWindow = class {
114083
114256
  }
114084
114257
  };
114085
114258
 
114259
+ // ../../packages/host-core/dist/rdesk/win-input.js
114260
+ import { spawn as spawn10 } from "node:child_process";
114261
+ function vkForCode(code, key2) {
114262
+ if (code) {
114263
+ if (code.length === 4 && code.startsWith("Key")) {
114264
+ const c = code.charCodeAt(3);
114265
+ if (c >= 65 && c <= 90)
114266
+ return c;
114267
+ }
114268
+ if (code.length === 6 && code.startsWith("Digit")) {
114269
+ const c = code.charCodeAt(5);
114270
+ if (c >= 48 && c <= 57)
114271
+ return c;
114272
+ }
114273
+ if (code.startsWith("Numpad")) {
114274
+ const n = NUMPAD[code];
114275
+ if (n !== void 0)
114276
+ return n;
114277
+ }
114278
+ if (code.length >= 2 && code[0] === "F") {
114279
+ const n = Number(code.slice(1));
114280
+ if (Number.isInteger(n) && n >= 1 && n <= 24)
114281
+ return 112 + (n - 1);
114282
+ }
114283
+ const mapped = CODE_VK[code];
114284
+ if (mapped !== void 0)
114285
+ return mapped;
114286
+ }
114287
+ const named = KEY_VK[key2];
114288
+ if (named !== void 0)
114289
+ return named;
114290
+ if (key2.length === 1) {
114291
+ const c = key2.toUpperCase().charCodeAt(0);
114292
+ if (c >= 65 && c <= 90 || c >= 48 && c <= 57)
114293
+ return c;
114294
+ }
114295
+ return 0;
114296
+ }
114297
+ var NUMPAD = {
114298
+ Numpad0: 96,
114299
+ Numpad1: 97,
114300
+ Numpad2: 98,
114301
+ Numpad3: 99,
114302
+ Numpad4: 100,
114303
+ Numpad5: 101,
114304
+ Numpad6: 102,
114305
+ Numpad7: 103,
114306
+ Numpad8: 104,
114307
+ Numpad9: 105,
114308
+ NumpadMultiply: 106,
114309
+ NumpadAdd: 107,
114310
+ NumpadSubtract: 109,
114311
+ NumpadDecimal: 110,
114312
+ NumpadDivide: 111,
114313
+ NumpadEnter: 13
114314
+ };
114315
+ var CODE_VK = {
114316
+ // Modifiers (L/R specific so a chord releases the exact key it pressed).
114317
+ ShiftLeft: 160,
114318
+ ShiftRight: 161,
114319
+ ControlLeft: 162,
114320
+ ControlRight: 163,
114321
+ AltLeft: 164,
114322
+ AltRight: 165,
114323
+ MetaLeft: 91,
114324
+ MetaRight: 92,
114325
+ // Editing / whitespace.
114326
+ Enter: 13,
114327
+ Escape: 27,
114328
+ Tab: 9,
114329
+ Backspace: 8,
114330
+ Space: 32,
114331
+ CapsLock: 20,
114332
+ // Navigation (extended keys — the helper flags them EXTENDEDKEY).
114333
+ ArrowUp: 38,
114334
+ ArrowDown: 40,
114335
+ ArrowLeft: 37,
114336
+ ArrowRight: 39,
114337
+ Home: 36,
114338
+ End: 35,
114339
+ PageUp: 33,
114340
+ PageDown: 34,
114341
+ Insert: 45,
114342
+ Delete: 46,
114343
+ // US-position punctuation (host layout re-interprets; text still rides typeUnicode).
114344
+ Backquote: 192,
114345
+ Minus: 189,
114346
+ Equal: 187,
114347
+ BracketLeft: 219,
114348
+ BracketRight: 221,
114349
+ Backslash: 220,
114350
+ Semicolon: 186,
114351
+ Quote: 222,
114352
+ Comma: 188,
114353
+ Period: 190,
114354
+ Slash: 191,
114355
+ PrintScreen: 44,
114356
+ ScrollLock: 145,
114357
+ Pause: 19,
114358
+ ContextMenu: 93
114359
+ };
114360
+ var KEY_VK = {
114361
+ Shift: 16,
114362
+ Control: 17,
114363
+ Alt: 18,
114364
+ Meta: 91,
114365
+ Enter: 13,
114366
+ Escape: 27,
114367
+ Tab: 9,
114368
+ Backspace: 8,
114369
+ Delete: 46,
114370
+ " ": 32,
114371
+ ArrowUp: 38,
114372
+ ArrowDown: 40,
114373
+ ArrowLeft: 37,
114374
+ ArrowRight: 39,
114375
+ Home: 36,
114376
+ End: 35,
114377
+ PageUp: 33,
114378
+ PageDown: 34,
114379
+ Insert: 45
114380
+ };
114381
+ var BTN_CODE = { L: "L", R: "R", M: "M", X1: "1", X2: "2" };
114382
+ var CSHARP = `
114383
+ using System;
114384
+ using System.Runtime.InteropServices;
114385
+ using System.Globalization;
114386
+ namespace Tess {
114387
+ public static class Input {
114388
+ [StructLayout(LayoutKind.Sequential)] struct MOUSEINPUT { public int dx; public int dy; public uint mouseData; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; }
114389
+ [StructLayout(LayoutKind.Sequential)] struct KEYBDINPUT { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; }
114390
+ [StructLayout(LayoutKind.Explicit)] struct INPUTUNION { [FieldOffset(0)] public MOUSEINPUT mi; [FieldOffset(0)] public KEYBDINPUT ki; }
114391
+ [StructLayout(LayoutKind.Sequential)] struct INPUT { public uint type; public INPUTUNION u; }
114392
+ [DllImport("user32.dll", SetLastError=true)] static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
114393
+ [DllImport("user32.dll")] static extern int GetSystemMetrics(int nIndex);
114394
+ [DllImport("user32.dll")] static extern IntPtr SetProcessDpiAwarenessContext(IntPtr value);
114395
+
114396
+ const uint INPUT_MOUSE=0, INPUT_KEYBOARD=1;
114397
+ const uint MOVE=0x0001, ABSOLUTE=0x8000, VIRTUALDESK=0x4000;
114398
+ const uint LEFTDOWN=0x0002, LEFTUP=0x0004, RIGHTDOWN=0x0008, RIGHTUP=0x0010, MIDDLEDOWN=0x0020, MIDDLEUP=0x0040, XDOWN=0x0080, XUP=0x0100, WHEEL=0x0800, HWHEEL=0x1000;
114399
+ const uint KEYUP=0x0002, UNICODE=0x0004, EXTENDED=0x0001;
114400
+ const uint XBUTTON1=0x0001, XBUTTON2=0x0002;
114401
+ const int SM_XVIRTUALSCREEN=76, SM_YVIRTUALSCREEN=77, SM_CXVIRTUALSCREEN=78, SM_CYVIRTUALSCREEN=79;
114402
+ static readonly CultureInfo CI = CultureInfo.InvariantCulture;
114403
+
114404
+ static void Send(INPUT[] a){ SendInput((uint)a.Length, a, Marshal.SizeOf(typeof(INPUT))); }
114405
+ static INPUT Mouse(uint flags, int dx, int dy, uint data){ INPUT i=new INPUT(); i.type=INPUT_MOUSE; i.u.mi.dx=dx; i.u.mi.dy=dy; i.u.mi.mouseData=data; i.u.mi.dwFlags=flags; return i; }
114406
+ static INPUT Kbd(ushort vk, ushort scan, uint flags){ INPUT i=new INPUT(); i.type=INPUT_KEYBOARD; i.u.ki.wVk=vk; i.u.ki.wScan=scan; i.u.ki.dwFlags=flags; return i; }
114407
+
114408
+ static int Clamp(int v){ return v<0?0:(v>65535?65535:v); }
114409
+ static void MoveFraction(double fx, double fy){
114410
+ int nx=Clamp((int)Math.Round(fx*65535.0)); int ny=Clamp((int)Math.Round(fy*65535.0));
114411
+ Send(new INPUT[]{ Mouse(MOVE|ABSOLUTE|VIRTUALDESK, nx, ny, 0) });
114412
+ }
114413
+ static void MovePhysical(int px, int py){
114414
+ int vx=GetSystemMetrics(SM_XVIRTUALSCREEN), vy=GetSystemMetrics(SM_YVIRTUALSCREEN);
114415
+ int vw=GetSystemMetrics(SM_CXVIRTUALSCREEN), vh=GetSystemMetrics(SM_CYVIRTUALSCREEN);
114416
+ if(vw<1) vw=1; if(vh<1) vh=1;
114417
+ MoveFraction((px-vx)/(double)vw, (py-vy)/(double)vh);
114418
+ }
114419
+ static void Button(string b, bool down){
114420
+ switch(b){
114421
+ case "L": Send(new INPUT[]{ Mouse(down?LEFTDOWN:LEFTUP,0,0,0) }); break;
114422
+ case "R": Send(new INPUT[]{ Mouse(down?RIGHTDOWN:RIGHTUP,0,0,0) }); break;
114423
+ case "M": Send(new INPUT[]{ Mouse(down?MIDDLEDOWN:MIDDLEUP,0,0,0) }); break;
114424
+ case "1": Send(new INPUT[]{ Mouse(down?XDOWN:XUP,0,0,XBUTTON1) }); break;
114425
+ case "2": Send(new INPUT[]{ Mouse(down?XDOWN:XUP,0,0,XBUTTON2) }); break;
114426
+ }
114427
+ }
114428
+ static void Wheel(int dx, int dy){
114429
+ if(dy!=0) Send(new INPUT[]{ Mouse(WHEEL,0,0,(uint)dy) });
114430
+ if(dx!=0) Send(new INPUT[]{ Mouse(HWHEEL,0,0,(uint)dx) });
114431
+ }
114432
+ static bool IsExtended(ushort vk){
114433
+ switch(vk){
114434
+ case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28:
114435
+ case 0x2d: case 0x2e: case 0x5b: case 0x5c: case 0x5d: case 0xa3: case 0xa5: case 0x2c: return true;
114436
+ default: return false;
114437
+ }
114438
+ }
114439
+ static void Key(ushort vk, bool down){
114440
+ uint flags = down?0u:KEYUP;
114441
+ if(IsExtended(vk)) flags |= EXTENDED;
114442
+ Send(new INPUT[]{ Kbd(vk,0,flags) });
114443
+ }
114444
+ static void Unicode(string[] p, int start){
114445
+ for(int i=start;i<p.Length;i++){
114446
+ ushort u;
114447
+ if(!ushort.TryParse(p[i], NumberStyles.HexNumber, CI, out u)) continue;
114448
+ Send(new INPUT[]{ Kbd(0,u,UNICODE), Kbd(0,u,UNICODE|KEYUP) });
114449
+ }
114450
+ }
114451
+ static void Dispatch(string line){
114452
+ if(line.Length==0) return;
114453
+ string[] p = line.Split(' ');
114454
+ switch(p[0]){
114455
+ case "MF": MoveFraction(double.Parse(p[1],CI), double.Parse(p[2],CI)); break;
114456
+ case "M": MovePhysical(int.Parse(p[1],CI), int.Parse(p[2],CI)); break;
114457
+ case "B": Button(p[1], p[2]=="1"); break;
114458
+ case "W": Wheel(int.Parse(p[1],CI), int.Parse(p[2],CI)); break;
114459
+ case "K": Key(ushort.Parse(p[1],CI), p[2]=="1"); break;
114460
+ case "U": Unicode(p, 1); break;
114461
+ case "PING": Console.Out.WriteLine("PONG"); Console.Out.Flush(); break;
114462
+ }
114463
+ }
114464
+ public static void Pump(){
114465
+ try { SetProcessDpiAwarenessContext(new IntPtr(-4)); } catch {} // PER_MONITOR_AWARE_V2; best-effort
114466
+ Console.Out.WriteLine("READY"); Console.Out.Flush();
114467
+ string line;
114468
+ while((line = Console.In.ReadLine()) != null){
114469
+ try { Dispatch(line); } catch { /* one bad command must never kill the pump */ }
114470
+ }
114471
+ }
114472
+ }
114473
+ }
114474
+ `;
114475
+ var PS_SCRIPT = `$ErrorActionPreference='Stop'; Add-Type -TypeDefinition @'
114476
+ ${CSHARP}
114477
+ '@ ; [Tess.Input]::Pump()`;
114478
+ var WinInputImpl = class {
114479
+ child = null;
114480
+ starting = null;
114481
+ queue = [];
114482
+ readyResolvers = [];
114483
+ stdoutBuf = "";
114484
+ stopped = false;
114485
+ log;
114486
+ constructor(log10) {
114487
+ this.log = log10 ?? (() => {
114488
+ });
114489
+ }
114490
+ /** Start the helper if it isn't running. Idempotent and lazy — a crashed helper is relaunched
114491
+ * the next time a command is sent (the queued command flushes once it's up). */
114492
+ ensure() {
114493
+ if (this.stopped || this.child)
114494
+ return;
114495
+ const b642 = Buffer.from(PS_SCRIPT, "utf16le").toString("base64");
114496
+ let child;
114497
+ try {
114498
+ child = spawn10("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", b642], {
114499
+ windowsHide: true,
114500
+ stdio: ["pipe", "pipe", "pipe"]
114501
+ });
114502
+ } catch (e) {
114503
+ this.log(`win-input: could not spawn powershell: ${e instanceof Error ? e.message : String(e)}`);
114504
+ return;
114505
+ }
114506
+ this.child = child;
114507
+ child.stdout.setEncoding("utf8");
114508
+ child.stdout.on("data", (d) => {
114509
+ this.stdoutBuf += d;
114510
+ if (this.stdoutBuf.includes("READY")) {
114511
+ this.stdoutBuf = "";
114512
+ const r = this.readyResolvers;
114513
+ this.readyResolvers = [];
114514
+ for (const res of r) {
114515
+ try {
114516
+ res();
114517
+ } catch {
114518
+ }
114519
+ }
114520
+ }
114521
+ });
114522
+ child.stderr.setEncoding("utf8");
114523
+ child.stderr.on("data", (d) => {
114524
+ const s = d.trim();
114525
+ if (s)
114526
+ this.log(`win-input stderr: ${s.slice(0, 300)}`);
114527
+ });
114528
+ child.on("error", (e) => {
114529
+ this.log(`win-input: helper error: ${e instanceof Error ? e.message : String(e)}`);
114530
+ this.child = null;
114531
+ });
114532
+ child.on("exit", (code, signal2) => {
114533
+ if (!this.stopped)
114534
+ this.log(`win-input: helper exited (code=${code ?? "?"} signal=${signal2 ?? "-"}) \u2014 will relaunch on next input`);
114535
+ this.child = null;
114536
+ });
114537
+ if (this.queue.length) {
114538
+ const q = this.queue;
114539
+ this.queue = [];
114540
+ for (const line of q)
114541
+ this.rawWrite(line);
114542
+ }
114543
+ }
114544
+ rawWrite(line) {
114545
+ const c = this.child;
114546
+ if (!c || !c.stdin.writable) {
114547
+ this.queue.push(line);
114548
+ if (this.queue.length > 512)
114549
+ this.queue.shift();
114550
+ return;
114551
+ }
114552
+ try {
114553
+ c.stdin.write(line + "\n");
114554
+ } catch (e) {
114555
+ this.log(`win-input: write failed: ${e instanceof Error ? e.message : String(e)}`);
114556
+ }
114557
+ }
114558
+ send(line) {
114559
+ this.ensure();
114560
+ this.rawWrite(line);
114561
+ }
114562
+ async warm() {
114563
+ if (this.stopped)
114564
+ return;
114565
+ this.ensure();
114566
+ if (!this.child)
114567
+ return;
114568
+ await new Promise((resolve16) => {
114569
+ const t = setTimeout(() => {
114570
+ this.readyResolvers = this.readyResolvers.filter((r) => r !== resolve16);
114571
+ resolve16();
114572
+ }, 6e3);
114573
+ t.unref?.();
114574
+ this.readyResolvers.push(() => {
114575
+ clearTimeout(t);
114576
+ resolve16();
114577
+ });
114578
+ });
114579
+ }
114580
+ async moveAbsolute(fx, fy) {
114581
+ this.send(`MF ${clampFrac(fx)} ${clampFrac(fy)}`);
114582
+ }
114583
+ async moveAbsolutePhysical(px, py) {
114584
+ this.send(`M ${Math.round(px)} ${Math.round(py)}`);
114585
+ }
114586
+ async button(btn, down) {
114587
+ this.send(`B ${BTN_CODE[btn]} ${down ? 1 : 0}`);
114588
+ }
114589
+ async wheel(dx, dy) {
114590
+ this.send(`W ${Math.round(dx)} ${Math.round(dy)}`);
114591
+ }
114592
+ async keyByVk(vk, down) {
114593
+ if (vk > 0)
114594
+ this.send(`K ${vk | 0} ${down ? 1 : 0}`);
114595
+ }
114596
+ async typeUnicode(text13) {
114597
+ if (!text13)
114598
+ return;
114599
+ const units = [];
114600
+ for (let i = 0; i < text13.length; i++)
114601
+ units.push(text13.charCodeAt(i).toString(16));
114602
+ for (let i = 0; i < units.length; i += 64)
114603
+ this.send(`U ${units.slice(i, i + 64).join(" ")}`);
114604
+ }
114605
+ async stop() {
114606
+ this.stopped = true;
114607
+ const c = this.child;
114608
+ this.child = null;
114609
+ this.queue = [];
114610
+ if (!c)
114611
+ return;
114612
+ try {
114613
+ c.stdin.end();
114614
+ } catch {
114615
+ }
114616
+ await new Promise((resolve16) => {
114617
+ let done = false;
114618
+ const finish2 = () => {
114619
+ if (done)
114620
+ return;
114621
+ done = true;
114622
+ resolve16();
114623
+ };
114624
+ c.on("exit", finish2);
114625
+ const t = setTimeout(() => {
114626
+ try {
114627
+ c.kill();
114628
+ } catch {
114629
+ }
114630
+ finish2();
114631
+ }, 800);
114632
+ t.unref?.();
114633
+ });
114634
+ }
114635
+ };
114636
+ function clampFrac(v) {
114637
+ if (!Number.isFinite(v))
114638
+ return 0;
114639
+ return v < 0 ? 0 : v > 1 ? 1 : Math.round(v * 1e6) / 1e6;
114640
+ }
114641
+ function createWinInput(opts = {}) {
114642
+ if (process.platform !== "win32")
114643
+ return null;
114644
+ return new WinInputImpl(opts.log);
114645
+ }
114646
+
114086
114647
  // ../../packages/host-core/dist/rdesk/input-injector.js
114087
114648
  var BUTTON = { 0: "left", 1: "middle", 2: "right" };
114649
+ var WIN_BUTTON = { 0: "L", 1: "M", 2: "R", 3: "X1", 4: "X2" };
114088
114650
  function normalizeKey(key2) {
114089
114651
  const named = {
114090
114652
  " ": "space",
@@ -114120,26 +114682,91 @@ function isComboKey(key2, modifiers = 0) {
114120
114682
  const hasHardMod = (modifiers & (1 | 2 | 4)) !== 0;
114121
114683
  return key2.length !== 1 || hasHardMod;
114122
114684
  }
114685
+ var COMBO_TOKEN_VK = {
114686
+ ctrl: 17,
114687
+ control: 17,
114688
+ alt: 18,
114689
+ option: 18,
114690
+ opt: 18,
114691
+ shift: 16,
114692
+ cmd: 91,
114693
+ command: 91,
114694
+ meta: 91,
114695
+ win: 91,
114696
+ super: 91,
114697
+ enter: 13,
114698
+ return: 13,
114699
+ tab: 9,
114700
+ escape: 27,
114701
+ esc: 27,
114702
+ space: 32,
114703
+ backspace: 8,
114704
+ delete: 46,
114705
+ del: 46,
114706
+ insert: 45,
114707
+ up: 38,
114708
+ down: 40,
114709
+ left: 37,
114710
+ right: 39,
114711
+ home: 36,
114712
+ end: 35,
114713
+ pageup: 33,
114714
+ pgup: 33,
114715
+ pagedown: 34,
114716
+ pgdn: 34
114717
+ };
114718
+ function comboTokenVk(token4) {
114719
+ const t = token4.trim().toLowerCase();
114720
+ if (COMBO_TOKEN_VK[t] !== void 0)
114721
+ return COMBO_TOKEN_VK[t];
114722
+ if (/^f([1-9]|1[0-9]|2[0-4])$/.test(t))
114723
+ return 112 + (Number(t.slice(1)) - 1);
114724
+ if (t.length === 1) {
114725
+ const c = t.toUpperCase().charCodeAt(0);
114726
+ if (c >= 65 && c <= 90 || c >= 48 && c <= 57)
114727
+ return c;
114728
+ }
114729
+ return 0;
114730
+ }
114123
114731
  var RdeskInputInjector = class {
114124
114732
  runner;
114125
114733
  origin;
114126
114734
  access;
114127
- constructor(runner2, origin, access) {
114735
+ win;
114736
+ /** VKs currently held down on the Windows path — released on view/disconnect/blur so a
114737
+ * modifier or key never "sticks" on the remote machine after control ends. */
114738
+ heldVks = /* @__PURE__ */ new Set();
114739
+ constructor(runner2, origin, access, win3 = null) {
114128
114740
  this.runner = runner2;
114129
114741
  this.origin = origin;
114130
114742
  this.access = access;
114743
+ this.win = win3;
114131
114744
  }
114132
114745
  setAccess(level) {
114746
+ if (this.access === "control" && level !== "control")
114747
+ this.releaseAll();
114133
114748
  this.access = level;
114134
114749
  }
114135
114750
  setOrigin(origin) {
114136
114751
  this.origin = origin;
114137
114752
  }
114753
+ /** Release every held key on the Windows path. Called on access change, disconnect and blur. */
114754
+ releaseAll() {
114755
+ if (!this.win) {
114756
+ this.heldVks.clear();
114757
+ return;
114758
+ }
114759
+ for (const vk of this.heldVks)
114760
+ void this.win.api.keyByVk(vk, false);
114761
+ this.heldVks.clear();
114762
+ }
114138
114763
  /** Apply one viewer input event, or drop it when the session is view-only. */
114139
114764
  async apply(ev) {
114140
114765
  if (this.access !== "control")
114141
114766
  return;
114142
114767
  const at = (x, y) => ({ x: this.origin.x + x, y: this.origin.y + y });
114768
+ if (this.win)
114769
+ return this.applyWin(ev, at);
114143
114770
  if (ev.kind === "mouse") {
114144
114771
  if (ev.action === "move")
114145
114772
  return void this.runner.mouseMove(at(ev.x, ev.y));
@@ -114160,686 +114787,134 @@ var RdeskInputInjector = class {
114160
114787
  return void this.runner.pressKeys(keyCombo(ev.key, ev.modifiers));
114161
114788
  }
114162
114789
  }
114163
- };
114164
-
114165
- // ../../packages/host-core/dist/rdesk/wayland-input.js
114166
- function isWaylandSession() {
114167
- return process.platform === "linux" && (process.env.XDG_SESSION_TYPE === "wayland" || !!process.env.WAYLAND_DISPLAY);
114168
- }
114169
- var BTN = { left: 272, right: 273, middle: 274 };
114170
- async function createWaylandInputProvider(onUnavailable) {
114171
- const fail4 = (reason) => {
114172
- onUnavailable?.(reason);
114173
- return null;
114174
- };
114175
- if (!isWaylandSession())
114176
- return fail4("not a Wayland session");
114177
- let dbus = null;
114178
- try {
114179
- dbus = await import("dbus-next");
114180
- } catch (e) {
114181
- return fail4(`dbus-next could not be loaded: ${e instanceof Error ? e.message : String(e)}`);
114182
- }
114183
- try {
114184
- const bus = dbus.sessionBus();
114185
- const obj = await bus.getProxyObject("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop");
114186
- const portal = obj.getInterface("org.freedesktop.portal.RemoteDesktop");
114187
- const sessionHandle = await establishSession(portal);
114188
- if (!sessionHandle)
114189
- return fail4(`the RemoteDesktop portal did not grant a session${lastSessionError ? ` (${lastSessionError})` : " (Start was refused, or nobody answered its consent prompt on that machine)"}`);
114190
- const opts = {};
114191
- return {
114192
- pointerMotionAbsolute: (x, y, w, h) => void portal.NotifyPointerMotionAbsolute(sessionHandle, opts, 0, x / Math.max(1, w), y / Math.max(1, h)).catch(() => {
114193
- }),
114194
- pointerButton: (b, down) => void portal.NotifyPointerButton(sessionHandle, opts, b, down ? 1 : 0).catch(() => {
114195
- }),
114196
- pointerAxis: (dx, dy) => void portal.NotifyPointerAxis(sessionHandle, opts, dx, dy).catch(() => {
114197
- }),
114198
- keyboardKeycode: (k, down) => void portal.NotifyKeyboardKeycode(sessionHandle, opts, k, down ? 1 : 0).catch(() => {
114199
- }),
114200
- keyboardKeysym: (ks, down) => void portal.NotifyKeyboardKeysym(sessionHandle, opts, ks, down ? 1 : 0).catch(() => {
114201
- }),
114202
- close: () => {
114790
+ /** The Windows input path (win32 only). Absolute virtual-desktop mouse, Unicode text, real
114791
+ * key down/up by virtual-key code. */
114792
+ applyWin(ev, at) {
114793
+ const api = this.win.api;
114794
+ if (ev.kind === "mouse") {
114795
+ const f = this.win.toFraction(at(ev.x, ev.y));
114796
+ void api.moveAbsolute(f.fx, f.fy);
114797
+ const btn = WIN_BUTTON[ev.button ?? 0] ?? "L";
114798
+ if (ev.action === "down")
114799
+ void api.button(btn, true);
114800
+ else if (ev.action === "up")
114801
+ void api.button(btn, false);
114802
+ else if (ev.action === "click") {
114803
+ void api.button(btn, true);
114804
+ void api.button(btn, false);
114203
114805
  }
114204
- };
114205
- } catch (e) {
114206
- return fail4(`the RemoteDesktop portal is not reachable: ${e instanceof Error ? e.message : String(e)}`);
114207
- }
114208
- }
114209
- var lastSessionError = "";
114210
- async function establishSession(portal) {
114211
- try {
114212
- const token4 = "tesseract" + Math.floor(Date.now() % 1e6);
114213
- await portal.CreateSession({ session_handle_token: token4, handle_token: token4 });
114214
- const session = `/org/freedesktop/portal/desktop/session/${token4}`;
114215
- await portal.SelectDevices(session, { types: 3, handle_token: token4 + "s" });
114216
- await portal.Start(session, "", { handle_token: token4 + "g" });
114217
- return session;
114218
- } catch (e) {
114219
- lastSessionError = e instanceof Error ? e.message : String(e);
114220
- return null;
114221
- }
114222
- }
114223
- var KEYSYM = {
114224
- // Modifiers
114225
- shift: 65505,
114226
- ctrl: 65507,
114227
- alt: 65513,
114228
- cmd: 65515,
114229
- // Shift_L, Control_L, Alt_L, Super_L
114230
- // Named keys
114231
- enter: 65293,
114232
- tab: 65289,
114233
- escape: 65307,
114234
- backspace: 65288,
114235
- delete: 65535,
114236
- space: 32,
114237
- up: 65362,
114238
- down: 65364,
114239
- left: 65361,
114240
- right: 65363,
114241
- home: 65360,
114242
- end: 65367,
114243
- pageup: 65365,
114244
- pagedown: 65366,
114245
- insert: 65379,
114246
- f1: 65470,
114247
- f2: 65471,
114248
- f3: 65472,
114249
- f4: 65473,
114250
- f5: 65474,
114251
- f6: 65475,
114252
- f7: 65476,
114253
- f8: 65477,
114254
- f9: 65478,
114255
- f10: 65479,
114256
- f11: 65480,
114257
- f12: 65481
114258
- };
114259
- function charKeysym(ch) {
114260
- const cp2 = ch.codePointAt(0) ?? 0;
114261
- if (cp2 === 0)
114262
- return 0;
114263
- return cp2 <= 255 ? cp2 : 16777216 + cp2;
114264
- }
114265
- function tokenKeysym(token4) {
114266
- const t = token4.trim().toLowerCase();
114267
- if (KEYSYM[t] !== void 0)
114268
- return KEYSYM[t];
114269
- return t.length === 1 ? charKeysym(t) : 0;
114270
- }
114271
- function waylandInputRunner(p, screen) {
114272
- const w = () => screen.w, h = () => screen.h;
114273
- return {
114274
- async mouseMove(pt) {
114275
- p.pointerMotionAbsolute(pt.x, pt.y, w(), h());
114276
- },
114277
- async mouseClick(o) {
114278
- if (o.at)
114279
- p.pointerMotionAbsolute(o.at.x, o.at.y, w(), h());
114280
- const b = BTN[o.button ?? "left"];
114281
- const click = () => {
114282
- p.pointerButton(b, true);
114283
- p.pointerButton(b, false);
114284
- };
114285
- click();
114286
- if (o.double)
114287
- click();
114288
- },
114289
- async scroll(o) {
114290
- p.pointerAxis(o.dx ?? 0, o.dy ?? 0);
114291
- },
114292
- async typeText(text13) {
114293
- for (const ch of text13) {
114294
- const ks = charKeysym(ch);
114295
- if (!ks)
114296
- continue;
114297
- p.keyboardKeysym(ks, true);
114298
- p.keyboardKeysym(ks, false);
114806
+ return;
114807
+ }
114808
+ if (ev.kind === "wheel") {
114809
+ void api.moveAbsolute(...fracOf(this.win.toFraction(at(ev.x, ev.y))));
114810
+ void api.wheel(-Math.round(ev.deltaX), -Math.round(ev.deltaY));
114811
+ return;
114812
+ }
114813
+ if (ev.kind === "key") {
114814
+ if (ev.action === "char") {
114815
+ if (ev.text)
114816
+ void api.typeUnicode(ev.text);
114817
+ return;
114299
114818
  }
114300
- },
114301
- async pressKeys(combo) {
114302
- const parts = combo.split("+").map((x) => x.trim()).filter(Boolean);
114303
- if (!parts.length)
114819
+ if (ev.key.includes("+")) {
114820
+ if (ev.action === "down")
114821
+ this.pressWinCombo(ev.key);
114304
114822
  return;
114305
- const key2 = tokenKeysym(parts[parts.length - 1]);
114306
- const mods = parts.slice(0, -1).map(tokenKeysym).filter((k) => k > 0);
114307
- if (!key2)
114823
+ }
114824
+ const vk = vkForCode(ev.code, ev.key);
114825
+ if (!vk)
114308
114826
  return;
114309
- for (const m of mods)
114310
- p.keyboardKeysym(m, true);
114311
- p.keyboardKeysym(key2, true);
114312
- p.keyboardKeysym(key2, false);
114313
- for (const m of [...mods].reverse())
114314
- p.keyboardKeysym(m, false);
114315
- }
114316
- };
114317
- }
114318
-
114319
- // ../../packages/host-core/dist/rdesk/host-clipboard.js
114320
- init_dist2();
114321
- import { statSync as statSync19 } from "node:fs";
114322
- import { basename as basename13 } from "node:path";
114323
- function readHostClipboardFilePaths() {
114324
- const el2 = electronModule();
114325
- const clip2 = el2?.clipboard;
114326
- if (!clip2)
114327
- return [];
114328
- try {
114329
- return readFilePathsWith((f) => clip2.read(f));
114330
- } catch {
114331
- return [];
114332
- }
114333
- }
114334
- function describeClipFiles(paths) {
114335
- const out = [];
114336
- for (const p of paths) {
114337
- try {
114338
- const st = statSync19(p);
114339
- out.push({ path: p, name: basename13(p), isDir: st.isDirectory(), size: st.isDirectory() ? 0 : st.size });
114340
- } catch {
114827
+ const mods = ev.modifiers ?? 0;
114828
+ if (ev.action === "down") {
114829
+ if (ev.key.length === 1 && (mods & (2 | 4)) === 0)
114830
+ return;
114831
+ void api.keyByVk(vk, true);
114832
+ this.heldVks.add(vk);
114833
+ } else if (ev.action === "up") {
114834
+ void api.keyByVk(vk, false);
114835
+ this.heldVks.delete(vk);
114836
+ }
114341
114837
  }
114342
114838
  }
114343
- return out;
114344
- }
114345
- function watchHostClipboardFiles(onChange, intervalMs = 1500) {
114346
- let last = "";
114347
- let stopped2 = false;
114348
- const tick = () => {
114349
- if (stopped2)
114350
- return;
114351
- const paths = readHostClipboardFilePaths();
114352
- const key2 = paths.join("\0");
114353
- if (key2 === last)
114839
+ /** Inject a named chord ("ctrl+alt+delete") as a real down/up sequence on the Windows path. */
114840
+ pressWinCombo(combo) {
114841
+ const api = this.win.api;
114842
+ const toks = combo.split("+").map((t) => t.trim()).filter(Boolean);
114843
+ const vks = toks.map(comboTokenVk).filter((v) => v > 0);
114844
+ if (!vks.length)
114354
114845
  return;
114355
- last = key2;
114356
- onChange(paths.length ? describeClipFiles(paths) : []);
114357
- };
114358
- const timer2 = setInterval(tick, intervalMs);
114359
- timer2.unref?.();
114360
- return () => {
114361
- stopped2 = true;
114362
- clearInterval(timer2);
114363
- };
114846
+ for (const vk of vks)
114847
+ void api.keyByVk(vk, true);
114848
+ for (const vk of [...vks].reverse())
114849
+ void api.keyByVk(vk, false);
114850
+ }
114851
+ };
114852
+ function fracOf(f) {
114853
+ return [f.fx, f.fy];
114364
114854
  }
114365
114855
 
114366
- // ../../packages/host-core/dist/rdesk/tesseract-engine.js
114367
- var TesseractEngine = class {
114368
- id = "tesseract";
114369
- ctx;
114370
- deps;
114371
- pub = null;
114372
- injector = null;
114373
- /** Stops the host-clipboard file watcher for this session (see host-clipboard.ts). */
114374
- stopClipWatch = null;
114375
- monitorId = "";
114376
- sourceId = "";
114377
- // active capture source (screen or window)
114378
- quality = "auto";
114379
- systemAudio = false;
114380
- access;
114381
- constructor(ctx2, deps) {
114382
- this.ctx = ctx2;
114383
- this.deps = deps;
114384
- this.access = ctx2.accessLevel;
114856
+ // ../../packages/host-core/dist/system-events.js
114857
+ import { execFile as execFile8 } from "node:child_process";
114858
+ import { networkInterfaces, platform as platform2 } from "node:os";
114859
+
114860
+ // ../../packages/host-core/dist/participants.js
114861
+ var DAEMON_PARTICIPANT = "daemon";
114862
+ var ParticipantRegistry = class {
114863
+ byId = /* @__PURE__ */ new Map();
114864
+ /** Declare a participant. Idempotent — re-joining an existing id updates its label and keeps its
114865
+ * state, which is what a reconnect is. */
114866
+ join(id, label, now2) {
114867
+ const existing = this.byId.get(id);
114868
+ if (existing) {
114869
+ existing.label = label;
114870
+ return null;
114871
+ }
114872
+ this.byId.set(id, { id, label, active: false, focused: false, joinedAt: now2 });
114873
+ return { kind: "joined", id };
114874
+ }
114875
+ /** It went away — closed, crashed, or its socket dropped. Anything it claimed goes with it: a
114876
+ * process that died is not frontmost, and leaving that behind would make the machine look
114877
+ * permanently attended. */
114878
+ leave(id) {
114879
+ return this.byId.delete(id) ? { kind: "left", id } : null;
114880
+ }
114881
+ /** Frontmost, as reported by the participant itself. */
114882
+ setActive(id, active2) {
114883
+ const p = this.byId.get(id);
114884
+ if (!p || p.active === active2)
114885
+ return null;
114886
+ p.active = active2;
114887
+ return { kind: active2 ? "activated" : "deactivated", id };
114888
+ }
114889
+ setFocused(id, focused) {
114890
+ const p = this.byId.get(id);
114891
+ if (!p || p.focused === focused)
114892
+ return null;
114893
+ p.focused = focused;
114894
+ return { kind: focused ? "window-focus" : "window-blur", id };
114385
114895
  }
114386
114896
  /**
114387
- * What this session can ACTUALLY do, derived at runtime.
114897
+ * Is anybody at this machine?
114388
114898
  *
114389
- * This used to be a hardcoded constant returned identically on every platform, so a
114390
- * viewer was told `input: true` by a Wayland host that cannot inject a keystroke, and
114391
- * offered a system-audio toggle on macOS where the capture is known to be silent. A
114392
- * capability flag that doesn't track reality is worse than no flag: the viewer renders
114393
- * a control that quietly does nothing.
114899
+ * The union, deliberately. With a single boolean, two participants taking and losing focus in
114900
+ * sequence leave the answer set by whichever spoke last — so the desktop app blurring would
114901
+ * report an empty machine while somebody sits in front of the Remote Desktop window.
114394
114902
  */
114395
- capabilities() {
114396
- return {
114397
- // False until an input runner was actually obtained (see start()): on Wayland
114398
- // without the portal, control is impossible and the session is view-only.
114399
- input: !!this.injector,
114400
- clipboard: true,
114401
- // No drag/drop or upload surface exists inside an rdesk session today. It was
114402
- // advertised as if it did; nothing in either viewer ever read it.
114403
- fileTransfer: false,
114404
- multiMonitor: true,
114405
- windowCapture: true,
114406
- // share a single window/app (privacy)
114407
- // Voice now rides this session's own peer connection (setAv).
114408
- bidiAudioVideo: true,
114409
- // Windows/Linux capture desktop audio natively; macOS needs a loopback we don't
114410
- // ship, so claiming it there just produces a toggle that yields silence.
114411
- systemAudio: process.platform !== "darwin"
114412
- };
114413
- }
114414
- listMonitors() {
114415
- return listPublisherMonitors();
114416
- }
114417
- listSources() {
114418
- return listPublisherSources();
114903
+ anyonePresent() {
114904
+ for (const p of this.byId.values())
114905
+ if (p.active || p.focused)
114906
+ return true;
114907
+ return false;
114419
114908
  }
114420
- startOpts(iceServers) {
114421
- return { iceServers, quality: this.quality, systemAudio: this.systemAudio };
114909
+ /** Who is accounted for. Lets a caller tell "nobody is here" from "nobody has reported". */
114910
+ participants() {
114911
+ return [...this.byId.values()].map((p) => ({ ...p }));
114422
114912
  }
114423
- async setSource(sourceId) {
114424
- this.sourceId = sourceId;
114425
- const origin = sourceId.startsWith("screen:") ? this.originForActiveScreen() : { x: 0, y: 0 };
114426
- this.injector?.setOrigin(origin);
114427
- const iceServers = await this.deps.getIceServers().catch(() => []);
114428
- await this.pub?.setSource(sourceId, this.startOpts(iceServers));
114913
+ has(id) {
114914
+ return this.byId.has(id);
114429
114915
  }
114430
- originForActiveScreen() {
114431
- const b = displayBoundsById(this.monitorId);
114432
- return b ? { x: b.x, y: b.y } : { x: 0, y: 0 };
114433
- }
114434
- async start(opts) {
114435
- const monitors = await listPublisherMonitors();
114436
- this.monitorId = opts.monitorId ?? monitors.find((m) => m.primary)?.id ?? monitors[0]?.id ?? "";
114437
- this.quality = opts.quality ?? "auto";
114438
- this.sourceId = await sourceIdForMonitor(this.monitorId).catch(() => "");
114439
- const origin = displayBoundsById(this.monitorId) ?? { x: 0, y: 0, width: 0, height: 0 };
114440
- let runner2;
114441
- if (this.deps.makeRunner) {
114442
- runner2 = this.deps.makeRunner();
114443
- } else if (isWaylandSession()) {
114444
- let waylandReason = "";
114445
- const wp = await createWaylandInputProvider((r) => {
114446
- waylandReason = r;
114447
- });
114448
- if (wp) {
114449
- const m = monitors.find((x) => x.id === this.monitorId) ?? monitors[0];
114450
- runner2 = waylandInputRunner(wp, { w: m?.width ?? origin.width, h: m?.height ?? origin.height });
114451
- this.ctx.log("rdesk: Wayland input via XDG RemoteDesktop portal.");
114452
- } else {
114453
- this.ctx.log(`rdesk: Wayland \u2014 keyboard and mouse unavailable, streaming view-only. Reason: ${waylandReason || "unknown"}`);
114454
- runner2 = createDaemonOsControlRunner();
114455
- }
114456
- } else {
114457
- runner2 = createDaemonOsControlRunner();
114458
- }
114459
- this.injector = new RdeskInputInjector(runner2, { x: origin.x, y: origin.y }, this.ctx.accessLevel);
114460
- const iceServers = await this.deps.getIceServers().catch(() => []);
114461
- const pub = new PublisherWindow(this.ctx.sessionId, {
114462
- onOffer: (sdp) => this.ctx.sendSignal({ type: "signal:offer", sdp }),
114463
- onIce: (candidate) => this.ctx.sendSignal({ type: "signal:ice", candidate }),
114464
- onInput: (ev) => void this.injector?.apply(ev),
114465
- // Clipboard sync (Phase 6): write the viewer's clipboard onto the host so ⌘V pastes
114466
- // into the remote machine. Gated by control access (view-only never writes).
114467
- onClipboard: (text13) => {
114468
- if (this.access === "control") {
114469
- const el2 = electronModule();
114470
- try {
114471
- el2?.clipboard?.writeText(text13);
114472
- } catch {
114473
- }
114474
- }
114475
- },
114476
- onReady: () => this.ctx.log("publisher connected"),
114477
- onError: (err2) => {
114478
- this.ctx.log(`publisher error: ${err2}`);
114479
- this.ctx.sendControl({ type: "rdesk:closed", reason: "network" });
114480
- },
114481
- // Media is direct-first; log which path actually won so a regression to relay
114482
- // (which costs real bandwidth) is visible instead of silent. Types only, no IPs.
114483
- onCapture: (ok, message) => {
114484
- this.ctx.log(`capture ${ok ? "ok" : `LOST \u2014 ${message}`}`);
114485
- this.ctx.onCapture?.(ok, message);
114486
- },
114487
- onPath: (path6, d) => {
114488
- this.ctx.log(`media path=${path6} (local=${d.localType ?? "?"} remote=${d.remoteType ?? "?"})`);
114489
- this.ctx.onPath?.(path6, d);
114490
- }
114491
- });
114492
- this.pub = pub;
114493
- this.stopClipWatch = watchHostClipboardFiles((files) => {
114494
- if (this.access !== "control")
114495
- return;
114496
- this.pub?.sendData({ t: "clipboard-files", files });
114497
- });
114498
- await pub.start({
114499
- iceServers,
114500
- monitorId: this.monitorId,
114501
- quality: this.quality,
114502
- systemAudio: this.systemAudio
114503
- });
114504
- }
114505
- onSignal(msg) {
114506
- if (!this.pub)
114507
- return;
114508
- if (msg.type === "signal:answer")
114509
- this.pub.answer(msg.sdp);
114510
- else if (msg.type === "signal:ice")
114511
- this.pub.addIce(msg.candidate);
114512
- }
114513
- applyInput(ev) {
114514
- void this.injector?.apply(ev);
114515
- }
114516
- setAccessLevel(level) {
114517
- this.access = level;
114518
- this.injector?.setAccess(level);
114519
- }
114520
- async setMonitor(monitorId) {
114521
- this.monitorId = monitorId;
114522
- this.sourceId = await sourceIdForMonitor(monitorId).catch(() => this.sourceId);
114523
- const origin = displayBoundsById(monitorId);
114524
- if (origin && this.injector)
114525
- this.injector.setOrigin({ x: origin.x, y: origin.y });
114526
- const iceServers = await this.deps.getIceServers().catch(() => []);
114527
- await this.pub?.setMonitor(monitorId, this.startOpts(iceServers));
114528
- }
114529
- setAv(on) {
114530
- this.pub?.setAv(on);
114531
- }
114532
- setQuality(preset) {
114533
- this.quality = preset;
114534
- this.pub?.setQuality(preset);
114535
- }
114536
- setMute(m) {
114537
- if (typeof m.systemAudio === "boolean" && m.systemAudio !== this.systemAudio && this.sourceId) {
114538
- this.systemAudio = m.systemAudio;
114539
- void this.deps.getIceServers().catch(() => []).then((ice) => this.pub?.setSource(this.sourceId, this.startOpts(ice)));
114540
- }
114541
- }
114542
- async stop() {
114543
- this.stopClipWatch?.();
114544
- this.stopClipWatch = null;
114545
- await this.pub?.stop();
114546
- this.pub = null;
114547
- this.injector = null;
114548
- }
114549
- };
114550
- function registerTesseractEngine(deps) {
114551
- registerRdeskEngine("tesseract", (ctx2) => new TesseractEngine(ctx2, deps));
114552
- }
114553
-
114554
- // ../../packages/host-core/dist/local-server.js
114555
- import { timingSafeEqual as timingSafeEqual5 } from "node:crypto";
114556
-
114557
- // ../../packages/host-core/dist/in-process-link.js
114558
- var OPEN = 1;
114559
- var CLOSED = 3;
114560
- var InProcSocket = class {
114561
- OPEN = OPEN;
114562
- CLOSED = CLOSED;
114563
- readyState = OPEN;
114564
- peer = null;
114565
- listeners = /* @__PURE__ */ new Map();
114566
- on(event, cb) {
114567
- let s = this.listeners.get(event);
114568
- if (!s) {
114569
- s = /* @__PURE__ */ new Set();
114570
- this.listeners.set(event, s);
114571
- }
114572
- s.add(cb);
114573
- return this;
114574
- }
114575
- once(event, cb) {
114576
- const wrap = (...a) => {
114577
- this.off(event, wrap);
114578
- cb(...a);
114579
- };
114580
- return this.on(event, wrap);
114581
- }
114582
- off(event, cb) {
114583
- this.listeners.get(event)?.delete(cb);
114584
- return this;
114585
- }
114586
- removeListener(event, cb) {
114587
- return this.off(event, cb);
114588
- }
114589
- fire(event, ...args) {
114590
- const s = this.listeners.get(event);
114591
- if (!s)
114592
- return;
114593
- for (const cb of [...s]) {
114594
- try {
114595
- cb(...args);
114596
- } catch {
114597
- }
114598
- }
114599
- }
114600
- /** Deliver `data` to the PEER's 'message' listeners on the next macrotask. */
114601
- send(data) {
114602
- const peer = this.peer;
114603
- if (!peer || peer.readyState !== OPEN)
114604
- return;
114605
- setImmediate(() => {
114606
- if (peer.readyState === OPEN)
114607
- peer.fire("message", data);
114608
- });
114609
- }
114610
- close(_code, _reason) {
114611
- if (this.readyState === CLOSED)
114612
- return;
114613
- this.readyState = CLOSED;
114614
- const peer = this.peer;
114615
- setImmediate(() => this.fire("close"));
114616
- if (peer && peer.readyState !== CLOSED) {
114617
- peer.readyState = CLOSED;
114618
- setImmediate(() => peer.fire("close"));
114619
- }
114620
- }
114621
- /** @internal wire this end to its peer. */
114622
- _link(peer) {
114623
- this.peer = peer;
114624
- }
114625
- /** @internal fire 'open' on the next macrotask (client end only; the server never awaits it). */
114626
- _fireOpen() {
114627
- setImmediate(() => this.fire("open"));
114628
- }
114629
- };
114630
- function makeInProcPair() {
114631
- const serverEnd = new InProcSocket();
114632
- const clientEnd = new InProcSocket();
114633
- serverEnd._link(clientEnd);
114634
- clientEnd._link(serverEnd);
114635
- return { serverEnd, clientEnd };
114636
- }
114637
- var connector = null;
114638
- function setInProcConnector(fn) {
114639
- connector = fn;
114640
- }
114641
-
114642
- // ../../packages/host-core/dist/rdesk/server-prefs.js
114643
- init_credentials();
114644
- import { readFileSync as readFileSync44, writeFileSync as writeFileSync29 } from "node:fs";
114645
- import { join as join72 } from "node:path";
114646
- function rdeskServerPrefsPath() {
114647
- return join72(daemonDir2(), "rdesk-server.json");
114648
- }
114649
- var DEFAULTS2 = {
114650
- enabled: true,
114651
- defaultAccess: "control",
114652
- quality: "auto",
114653
- autostart: true,
114654
- hideTray: false,
114655
- noticeShownAt: 0
114656
- };
114657
- function readRdeskServerPrefs() {
114658
- try {
114659
- const p = JSON.parse(readFileSync44(rdeskServerPrefsPath(), "utf8"));
114660
- return {
114661
- // `!== false` (not `?? true`): only an explicit opt-out disables sharing, so a
114662
- // partially-written or older prefs file never silently turns the machine off.
114663
- enabled: p.enabled !== false,
114664
- defaultAccess: p.defaultAccess === "view" ? "view" : "control",
114665
- quality: p.quality ?? DEFAULTS2.quality,
114666
- autostart: p.autostart ?? DEFAULTS2.autostart,
114667
- hideTray: p.hideTray ?? DEFAULTS2.hideTray,
114668
- noticeShownAt: typeof p.noticeShownAt === "number" ? p.noticeShownAt : 0
114669
- };
114670
- } catch {
114671
- return { ...DEFAULTS2 };
114672
- }
114673
- }
114674
- function writeRdeskServerPrefs(patch) {
114675
- try {
114676
- const next = { ...readRdeskServerPrefs(), ...patch };
114677
- writeFileSync29(rdeskServerPrefsPath(), JSON.stringify(next), { mode: 384 });
114678
- } catch {
114679
- }
114680
- }
114681
- function rdeskSharingEnabled() {
114682
- if (process.env.CCB_RDESK_ENABLED === "0")
114683
- return false;
114684
- if (process.env.CCB_RDESK_ENABLED === "1")
114685
- return true;
114686
- return readRdeskServerPrefs().enabled;
114687
- }
114688
-
114689
- // ../../packages/host-core/dist/rdesk/password.js
114690
- init_credentials();
114691
- import { scryptSync, randomBytes as randomBytes5, timingSafeEqual as timingSafeEqual4, randomInt } from "node:crypto";
114692
- import { readFileSync as readFileSync45, writeFileSync as writeFileSync30, existsSync as existsSync32, unlinkSync as unlinkSync4 } from "node:fs";
114693
- import { join as join73 } from "node:path";
114694
- function pwFile() {
114695
- return join73(daemonDir2(), "rdesk-password.json");
114696
- }
114697
- function pwPlainFile() {
114698
- return join73(daemonDir2(), "rdesk-password.enc");
114699
- }
114700
- var PW_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
114701
- var PW_LENGTH = 16;
114702
- function generatePassword() {
114703
- let out = "";
114704
- for (let i = 0; i < PW_LENGTH; i++)
114705
- out += PW_ALPHABET[randomInt(PW_ALPHABET.length)];
114706
- return (out.match(/.{1,4}/g) ?? [out]).join("-");
114707
- }
114708
- function writePlain(password) {
114709
- try {
114710
- const safe2 = electronModule()?.safeStorage;
114711
- if (!safe2?.isEncryptionAvailable())
114712
- return;
114713
- writeFileSync30(pwPlainFile(), safe2.encryptString(password), { mode: 384 });
114714
- } catch {
114715
- }
114716
- }
114717
- function canRevealRdeskPassword() {
114718
- try {
114719
- const safe2 = electronModule()?.safeStorage;
114720
- return !!safe2?.isEncryptionAvailable() && existsSync32(pwPlainFile());
114721
- } catch {
114722
- return false;
114723
- }
114724
- }
114725
- function readRdeskPasswordPlain() {
114726
- try {
114727
- const safe2 = electronModule()?.safeStorage;
114728
- if (!safe2?.isEncryptionAvailable())
114729
- return null;
114730
- return safe2.decryptString(readFileSync45(pwPlainFile()));
114731
- } catch {
114732
- return null;
114733
- }
114734
- }
114735
- function setRdeskPassword(password) {
114736
- if (!password) {
114737
- try {
114738
- unlinkSync4(pwFile());
114739
- } catch {
114740
- }
114741
- try {
114742
- unlinkSync4(pwPlainFile());
114743
- } catch {
114744
- }
114745
- return;
114746
- }
114747
- const salt = randomBytes5(16);
114748
- const hash2 = scryptSync(password, salt, 32);
114749
- writeFileSync30(pwFile(), JSON.stringify({ salt: salt.toString("hex"), hash: hash2.toString("hex") }), { mode: 384 });
114750
- writePlain(password);
114751
- }
114752
- function regenerateRdeskPassword() {
114753
- const pw = generatePassword();
114754
- setRdeskPassword(pw);
114755
- return pw;
114756
- }
114757
- function ensureRdeskPassword() {
114758
- if (hasRdeskPassword())
114759
- return null;
114760
- return regenerateRdeskPassword();
114761
- }
114762
- function hasRdeskPassword() {
114763
- return !!process.env.CCB_RDESK_PASSWORD || existsSync32(pwFile());
114764
- }
114765
- function verifyRdeskPassword(password) {
114766
- if (!password)
114767
- return false;
114768
- const override3 = process.env.CCB_RDESK_PASSWORD;
114769
- if (override3)
114770
- return password.length === override3.length && timingSafeEqual4(Buffer.from(password), Buffer.from(override3));
114771
- try {
114772
- const { salt, hash: hash2 } = JSON.parse(readFileSync45(pwFile(), "utf8"));
114773
- const got = scryptSync(password, Buffer.from(salt, "hex"), 32);
114774
- const want = Buffer.from(hash2, "hex");
114775
- return got.length === want.length && timingSafeEqual4(got, want);
114776
- } catch {
114777
- return false;
114778
- }
114779
- }
114780
-
114781
- // ../../packages/host-core/dist/system-events.js
114782
- import { execFile as execFile8 } from "node:child_process";
114783
- import { networkInterfaces, platform as platform2 } from "node:os";
114784
-
114785
- // ../../packages/host-core/dist/participants.js
114786
- var DAEMON_PARTICIPANT = "daemon";
114787
- var ParticipantRegistry = class {
114788
- byId = /* @__PURE__ */ new Map();
114789
- /** Declare a participant. Idempotent — re-joining an existing id updates its label and keeps its
114790
- * state, which is what a reconnect is. */
114791
- join(id, label, now2) {
114792
- const existing = this.byId.get(id);
114793
- if (existing) {
114794
- existing.label = label;
114795
- return null;
114796
- }
114797
- this.byId.set(id, { id, label, active: false, focused: false, joinedAt: now2 });
114798
- return { kind: "joined", id };
114799
- }
114800
- /** It went away — closed, crashed, or its socket dropped. Anything it claimed goes with it: a
114801
- * process that died is not frontmost, and leaving that behind would make the machine look
114802
- * permanently attended. */
114803
- leave(id) {
114804
- return this.byId.delete(id) ? { kind: "left", id } : null;
114805
- }
114806
- /** Frontmost, as reported by the participant itself. */
114807
- setActive(id, active2) {
114808
- const p = this.byId.get(id);
114809
- if (!p || p.active === active2)
114810
- return null;
114811
- p.active = active2;
114812
- return { kind: active2 ? "activated" : "deactivated", id };
114813
- }
114814
- setFocused(id, focused) {
114815
- const p = this.byId.get(id);
114816
- if (!p || p.focused === focused)
114817
- return null;
114818
- p.focused = focused;
114819
- return { kind: focused ? "window-focus" : "window-blur", id };
114820
- }
114821
- /**
114822
- * Is anybody at this machine?
114823
- *
114824
- * The union, deliberately. With a single boolean, two participants taking and losing focus in
114825
- * sequence leave the answer set by whichever spoke last — so the desktop app blurring would
114826
- * report an empty machine while somebody sits in front of the Remote Desktop window.
114827
- */
114828
- anyonePresent() {
114829
- for (const p of this.byId.values())
114830
- if (p.active || p.focused)
114831
- return true;
114832
- return false;
114833
- }
114834
- /** Who is accounted for. Lets a caller tell "nobody is here" from "nobody has reported". */
114835
- participants() {
114836
- return [...this.byId.values()].map((p) => ({ ...p }));
114837
- }
114838
- has(id) {
114839
- return this.byId.has(id);
114840
- }
114841
- clear() {
114842
- this.byId.clear();
114916
+ clear() {
114917
+ this.byId.clear();
114843
114918
  }
114844
114919
  };
114845
114920
 
@@ -114981,269 +115056,930 @@ async function probeOnce(url2) {
114981
115056
  }
114982
115057
  };
114983
115058
  try {
114984
- const req = en.request({ method: "HEAD", url: url2 });
114985
- const t = setTimeout(() => {
114986
- try {
114987
- req.abort();
114988
- } catch {
114989
- }
114990
- done(false);
114991
- }, PROBE_TIMEOUT_MS2);
114992
- t.unref?.();
114993
- req.on("response", () => {
114994
- clearTimeout(t);
114995
- done(true);
114996
- });
114997
- req.on("error", () => {
114998
- clearTimeout(t);
114999
- done(false);
115000
- });
115001
- req.end();
115059
+ const req = en.request({ method: "HEAD", url: url2 });
115060
+ const t = setTimeout(() => {
115061
+ try {
115062
+ req.abort();
115063
+ } catch {
115064
+ }
115065
+ done(false);
115066
+ }, PROBE_TIMEOUT_MS2);
115067
+ t.unref?.();
115068
+ req.on("response", () => {
115069
+ clearTimeout(t);
115070
+ done(true);
115071
+ });
115072
+ req.on("error", () => {
115073
+ clearTimeout(t);
115074
+ done(false);
115075
+ });
115076
+ req.end();
115077
+ } catch {
115078
+ done(false);
115079
+ }
115080
+ });
115081
+ }
115082
+ try {
115083
+ await fetch(url2, { method: "HEAD", signal: AbortSignal.timeout(PROBE_TIMEOUT_MS2) });
115084
+ return true;
115085
+ } catch {
115086
+ return false;
115087
+ }
115088
+ }
115089
+ async function probeNetwork() {
115090
+ const started2 = Date.now();
115091
+ const results = await Promise.all(PROBE_URLS.map((u) => probeOnce(u)));
115092
+ const reachable = results.some(Boolean);
115093
+ emitSystemEvent({ type: "net:probe", reachable, ms: Date.now() - started2 });
115094
+ if (reachable !== state2.online)
115095
+ emitSystemEvent({ type: reachable ? "net:online" : "net:offline", evidence: "probed" });
115096
+ return reachable;
115097
+ }
115098
+ function startNetworkProbing(requester = "daemon") {
115099
+ wanted.add(requester);
115100
+ if (probing)
115101
+ return;
115102
+ probing = true;
115103
+ probeAttempt = 0;
115104
+ scheduleProbe(0);
115105
+ }
115106
+ function stopNetworkProbing(requester) {
115107
+ if (requester !== void 0) {
115108
+ wanted.delete(requester);
115109
+ if (wanted.size > 0)
115110
+ return;
115111
+ } else {
115112
+ wanted.clear();
115113
+ }
115114
+ probing = false;
115115
+ if (probeTimer) {
115116
+ clearTimeout(probeTimer);
115117
+ probeTimer = null;
115118
+ }
115119
+ probeAttempt = 0;
115120
+ }
115121
+ var probeTimer = null;
115122
+ var probeAttempt = 0;
115123
+ var probing = false;
115124
+ var wanted = /* @__PURE__ */ new Set();
115125
+ function scheduleProbe(delayMs) {
115126
+ if (probeTimer)
115127
+ clearTimeout(probeTimer);
115128
+ probeTimer = setTimeout(() => {
115129
+ void runProbeCycle();
115130
+ }, delayMs);
115131
+ probeTimer.unref?.();
115132
+ }
115133
+ async function runProbeCycle() {
115134
+ probeTimer = null;
115135
+ if (!probing)
115136
+ return;
115137
+ const ok = await probeNetwork();
115138
+ if (!probing)
115139
+ return;
115140
+ if (ok) {
115141
+ probing = false;
115142
+ probeAttempt = 0;
115143
+ wanted.clear();
115144
+ return;
115145
+ }
115146
+ scheduleProbe(probeDelayMs(probeAttempt++));
115147
+ }
115148
+ function addressFingerprint() {
115149
+ const out = [];
115150
+ for (const [name, addrs] of Object.entries(networkInterfaces())) {
115151
+ for (const a of addrs ?? [])
115152
+ if (!a.internal)
115153
+ out.push(`${name}:${a.address}`);
115154
+ }
115155
+ return out.sort();
115156
+ }
115157
+ function readLidClosed() {
115158
+ if (platform2() !== "darwin")
115159
+ return Promise.resolve(null);
115160
+ return new Promise((resolve16) => {
115161
+ execFile8("ioreg", ["-r", "-k", "AppleClamshellState", "-d", "4"], { timeout: 4e3 }, (err2, stdout) => {
115162
+ resolve16(err2 ? null : parseClamshell(stdout ?? ""));
115163
+ });
115164
+ });
115165
+ }
115166
+ function parseClamshell(stdout) {
115167
+ const m = /"AppleClamshellState"\s*=\s*(Yes|No)/.exec(stdout);
115168
+ return m ? m[1] === "Yes" : null;
115169
+ }
115170
+ var TICK_MS = 1e4;
115171
+ var CLOCK_JUMP_MS = TICK_MS * 3;
115172
+ var LID_EVERY_TICKS = 3;
115173
+ var stops = [];
115174
+ var started = false;
115175
+ function startSystemEvents(opts = {}) {
115176
+ if (started)
115177
+ return;
115178
+ started = true;
115179
+ const log10 = opts.log ?? (() => {
115180
+ });
115181
+ const el2 = electronModule();
115182
+ emitSystemEvent({ type: "process:started", pid: process.pid, version: opts.version ?? "dev", headless: !el2 });
115183
+ const pm = el2?.powerMonitor;
115184
+ if (pm?.on) {
115185
+ const wire = (name, make) => {
115186
+ const h = () => {
115187
+ emitSystemEvent(make());
115188
+ };
115189
+ try {
115190
+ pm.on(name, h);
115191
+ stops.push(() => {
115192
+ try {
115193
+ pm.removeListener(name, h);
115194
+ } catch {
115195
+ }
115196
+ });
115197
+ } catch {
115198
+ }
115199
+ };
115200
+ wire("suspend", () => ({ type: "power:suspend" }));
115201
+ wire("resume", () => ({ type: "power:resume" }));
115202
+ wire("on-ac", () => ({ type: "power:on-ac" }));
115203
+ wire("on-battery", () => ({ type: "power:on-battery" }));
115204
+ wire("lock-screen", () => ({ type: "power:lock-screen" }));
115205
+ wire("unlock-screen", () => ({ type: "power:unlock-screen" }));
115206
+ wire("user-did-become-active", () => ({ type: "power:user-active" }));
115207
+ wire("user-did-resign-active", () => ({ type: "power:user-idle", idleSeconds: pm.getSystemIdleTime?.() ?? 0 }));
115208
+ wire("shutdown", () => ({ type: "power:os-shutdown" }));
115209
+ wire("thermal-state-change", () => ({ type: "power:thermal", state: pm.getCurrentThermalState?.() ?? "unknown" }));
115210
+ if (typeof pm.onBatteryPower === "boolean")
115211
+ emitSystemEvent({ type: pm.onBatteryPower ? "power:on-battery" : "power:on-ac" });
115212
+ }
115213
+ if (el2?.screen?.on) {
115214
+ const count = () => {
115215
+ try {
115216
+ return el2.screen.getAllDisplays().length;
115217
+ } catch {
115218
+ return 0;
115219
+ }
115220
+ };
115221
+ const wireScreen = (name, type) => {
115222
+ const h = () => {
115223
+ emitSystemEvent({ type, count: count() });
115224
+ };
115225
+ try {
115226
+ el2.screen.on(name, h);
115227
+ stops.push(() => {
115228
+ try {
115229
+ el2.screen.removeListener?.(name, h);
115230
+ } catch {
115231
+ }
115232
+ });
115233
+ } catch {
115234
+ }
115235
+ };
115236
+ wireScreen("display-added", "display:added");
115237
+ wireScreen("display-removed", "display:removed");
115238
+ wireScreen("display-metrics-changed", "display:changed");
115239
+ state2.displays = count();
115240
+ }
115241
+ if (el2?.app?.on) {
115242
+ const wireApp = (name, fire) => {
115243
+ try {
115244
+ el2.app.on(name, fire);
115245
+ stops.push(() => {
115246
+ try {
115247
+ el2.app.removeListener?.(name, fire);
115248
+ } catch {
115249
+ }
115250
+ });
115251
+ } catch {
115252
+ }
115253
+ };
115254
+ joinParticipant(DAEMON_PARTICIPANT, "Tesseract daemon");
115255
+ wireApp("activate", () => reportParticipantActive(DAEMON_PARTICIPANT, true));
115256
+ wireApp("did-become-active", () => reportParticipantActive(DAEMON_PARTICIPANT, true));
115257
+ wireApp("did-resign-active", () => reportParticipantActive(DAEMON_PARTICIPANT, false));
115258
+ wireApp("browser-window-focus", () => reportParticipantFocus(DAEMON_PARTICIPANT, true));
115259
+ wireApp("browser-window-blur", () => reportParticipantFocus(DAEMON_PARTICIPANT, false));
115260
+ }
115261
+ if (el2?.nativeTheme?.on) {
115262
+ const h = () => {
115263
+ emitSystemEvent({ type: "theme:changed", dark: !!el2.nativeTheme?.shouldUseDarkColors });
115264
+ };
115265
+ try {
115266
+ el2.nativeTheme.on("updated", h);
115267
+ stops.push(() => {
115268
+ try {
115269
+ el2.nativeTheme?.removeListener?.("updated", h);
115270
+ } catch {
115271
+ }
115272
+ });
115273
+ } catch {
115274
+ }
115275
+ }
115276
+ let lastTick = Date.now();
115277
+ let addresses = addressFingerprint();
115278
+ let ticks = 0;
115279
+ const tick = setInterval(() => {
115280
+ const now2 = Date.now();
115281
+ const drift = now2 - lastTick;
115282
+ lastTick = now2;
115283
+ if (drift > CLOCK_JUMP_MS)
115284
+ emitSystemEvent({ type: "power:clock-jump", missedMs: drift - TICK_MS });
115285
+ const next = addressFingerprint();
115286
+ if (next.join("|") !== addresses.join("|")) {
115287
+ addresses = next;
115288
+ emitSystemEvent({ type: "net:interfaces-changed", addresses: next });
115289
+ void probeNetwork().catch(() => {
115290
+ });
115291
+ }
115292
+ if (++ticks % LID_EVERY_TICKS === 0) {
115293
+ void readLidClosed().then((closed) => {
115294
+ if (closed === null || closed === state2.lidClosed)
115295
+ return;
115296
+ emitSystemEvent({ type: closed ? "lid:closed" : "lid:opened" });
115297
+ }).catch(() => {
115298
+ });
115299
+ }
115300
+ }, TICK_MS);
115301
+ tick.unref?.();
115302
+ stops.push(() => clearInterval(tick));
115303
+ try {
115304
+ const osOnline = el2?.net?.isOnline?.();
115305
+ if (typeof osOnline === "boolean")
115306
+ reportOsOnline(osOnline);
115307
+ } catch {
115308
+ }
115309
+ log10(`system events: watching (${el2 ? "electron" : "headless"})`);
115310
+ }
115311
+ function stopSystemEvents() {
115312
+ for (const s of stops) {
115313
+ try {
115314
+ s();
115315
+ } catch {
115316
+ }
115317
+ }
115318
+ stops = [];
115319
+ stopNetworkProbing();
115320
+ leaveParticipant(DAEMON_PARTICIPANT);
115321
+ started = false;
115322
+ }
115323
+
115324
+ // ../../packages/host-core/dist/rdesk/wayland-input.js
115325
+ function isWaylandSession() {
115326
+ return process.platform === "linux" && (process.env.XDG_SESSION_TYPE === "wayland" || !!process.env.WAYLAND_DISPLAY);
115327
+ }
115328
+ var BTN = { left: 272, right: 273, middle: 274 };
115329
+ async function createWaylandInputProvider(onUnavailable) {
115330
+ const fail4 = (reason) => {
115331
+ onUnavailable?.(reason);
115332
+ return null;
115333
+ };
115334
+ if (!isWaylandSession())
115335
+ return fail4("not a Wayland session");
115336
+ let dbus = null;
115337
+ try {
115338
+ dbus = await import("dbus-next");
115339
+ } catch (e) {
115340
+ return fail4(`dbus-next could not be loaded: ${e instanceof Error ? e.message : String(e)}`);
115341
+ }
115342
+ try {
115343
+ const bus = dbus.sessionBus();
115344
+ const obj = await bus.getProxyObject("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop");
115345
+ const portal = obj.getInterface("org.freedesktop.portal.RemoteDesktop");
115346
+ const sessionHandle = await establishSession(portal);
115347
+ if (!sessionHandle)
115348
+ return fail4(`the RemoteDesktop portal did not grant a session${lastSessionError ? ` (${lastSessionError})` : " (Start was refused, or nobody answered its consent prompt on that machine)"}`);
115349
+ const opts = {};
115350
+ return {
115351
+ pointerMotionAbsolute: (x, y, w, h) => void portal.NotifyPointerMotionAbsolute(sessionHandle, opts, 0, x / Math.max(1, w), y / Math.max(1, h)).catch(() => {
115352
+ }),
115353
+ pointerButton: (b, down) => void portal.NotifyPointerButton(sessionHandle, opts, b, down ? 1 : 0).catch(() => {
115354
+ }),
115355
+ pointerAxis: (dx, dy) => void portal.NotifyPointerAxis(sessionHandle, opts, dx, dy).catch(() => {
115356
+ }),
115357
+ keyboardKeycode: (k, down) => void portal.NotifyKeyboardKeycode(sessionHandle, opts, k, down ? 1 : 0).catch(() => {
115358
+ }),
115359
+ keyboardKeysym: (ks, down) => void portal.NotifyKeyboardKeysym(sessionHandle, opts, ks, down ? 1 : 0).catch(() => {
115360
+ }),
115361
+ close: () => {
115362
+ }
115363
+ };
115364
+ } catch (e) {
115365
+ return fail4(`the RemoteDesktop portal is not reachable: ${e instanceof Error ? e.message : String(e)}`);
115366
+ }
115367
+ }
115368
+ var lastSessionError = "";
115369
+ async function establishSession(portal) {
115370
+ try {
115371
+ const token4 = "tesseract" + Math.floor(Date.now() % 1e6);
115372
+ await portal.CreateSession({ session_handle_token: token4, handle_token: token4 });
115373
+ const session = `/org/freedesktop/portal/desktop/session/${token4}`;
115374
+ await portal.SelectDevices(session, { types: 3, handle_token: token4 + "s" });
115375
+ await portal.Start(session, "", { handle_token: token4 + "g" });
115376
+ return session;
115377
+ } catch (e) {
115378
+ lastSessionError = e instanceof Error ? e.message : String(e);
115379
+ return null;
115380
+ }
115381
+ }
115382
+ var KEYSYM = {
115383
+ // Modifiers
115384
+ shift: 65505,
115385
+ ctrl: 65507,
115386
+ alt: 65513,
115387
+ cmd: 65515,
115388
+ // Shift_L, Control_L, Alt_L, Super_L
115389
+ // Named keys
115390
+ enter: 65293,
115391
+ tab: 65289,
115392
+ escape: 65307,
115393
+ backspace: 65288,
115394
+ delete: 65535,
115395
+ space: 32,
115396
+ up: 65362,
115397
+ down: 65364,
115398
+ left: 65361,
115399
+ right: 65363,
115400
+ home: 65360,
115401
+ end: 65367,
115402
+ pageup: 65365,
115403
+ pagedown: 65366,
115404
+ insert: 65379,
115405
+ f1: 65470,
115406
+ f2: 65471,
115407
+ f3: 65472,
115408
+ f4: 65473,
115409
+ f5: 65474,
115410
+ f6: 65475,
115411
+ f7: 65476,
115412
+ f8: 65477,
115413
+ f9: 65478,
115414
+ f10: 65479,
115415
+ f11: 65480,
115416
+ f12: 65481
115417
+ };
115418
+ function charKeysym(ch) {
115419
+ const cp2 = ch.codePointAt(0) ?? 0;
115420
+ if (cp2 === 0)
115421
+ return 0;
115422
+ return cp2 <= 255 ? cp2 : 16777216 + cp2;
115423
+ }
115424
+ function tokenKeysym(token4) {
115425
+ const t = token4.trim().toLowerCase();
115426
+ if (KEYSYM[t] !== void 0)
115427
+ return KEYSYM[t];
115428
+ return t.length === 1 ? charKeysym(t) : 0;
115429
+ }
115430
+ function waylandInputRunner(p, screen) {
115431
+ const w = () => screen.w, h = () => screen.h;
115432
+ return {
115433
+ async mouseMove(pt) {
115434
+ p.pointerMotionAbsolute(pt.x, pt.y, w(), h());
115435
+ },
115436
+ async mouseClick(o) {
115437
+ if (o.at)
115438
+ p.pointerMotionAbsolute(o.at.x, o.at.y, w(), h());
115439
+ const b = BTN[o.button ?? "left"];
115440
+ const click = () => {
115441
+ p.pointerButton(b, true);
115442
+ p.pointerButton(b, false);
115443
+ };
115444
+ click();
115445
+ if (o.double)
115446
+ click();
115447
+ },
115448
+ async scroll(o) {
115449
+ p.pointerAxis(o.dx ?? 0, o.dy ?? 0);
115450
+ },
115451
+ async typeText(text13) {
115452
+ for (const ch of text13) {
115453
+ const ks = charKeysym(ch);
115454
+ if (!ks)
115455
+ continue;
115456
+ p.keyboardKeysym(ks, true);
115457
+ p.keyboardKeysym(ks, false);
115458
+ }
115459
+ },
115460
+ async pressKeys(combo) {
115461
+ const parts = combo.split("+").map((x) => x.trim()).filter(Boolean);
115462
+ if (!parts.length)
115463
+ return;
115464
+ const key2 = tokenKeysym(parts[parts.length - 1]);
115465
+ const mods = parts.slice(0, -1).map(tokenKeysym).filter((k) => k > 0);
115466
+ if (!key2)
115467
+ return;
115468
+ for (const m of mods)
115469
+ p.keyboardKeysym(m, true);
115470
+ p.keyboardKeysym(key2, true);
115471
+ p.keyboardKeysym(key2, false);
115472
+ for (const m of [...mods].reverse())
115473
+ p.keyboardKeysym(m, false);
115474
+ }
115475
+ };
115476
+ }
115477
+
115478
+ // ../../packages/host-core/dist/rdesk/host-clipboard.js
115479
+ init_dist2();
115480
+ import { statSync as statSync19 } from "node:fs";
115481
+ import { basename as basename13 } from "node:path";
115482
+ function readHostClipboardFilePaths() {
115483
+ const el2 = electronModule();
115484
+ const clip2 = el2?.clipboard;
115485
+ if (!clip2)
115486
+ return [];
115487
+ try {
115488
+ return readFilePathsWith((f) => clip2.read(f));
115489
+ } catch {
115490
+ return [];
115491
+ }
115492
+ }
115493
+ function describeClipFiles(paths) {
115494
+ const out = [];
115495
+ for (const p of paths) {
115496
+ try {
115497
+ const st = statSync19(p);
115498
+ out.push({ path: p, name: basename13(p), isDir: st.isDirectory(), size: st.isDirectory() ? 0 : st.size });
115499
+ } catch {
115500
+ }
115501
+ }
115502
+ return out;
115503
+ }
115504
+ function watchHostClipboardFiles(onChange, intervalMs = 1500) {
115505
+ let last = "";
115506
+ let stopped2 = false;
115507
+ const tick = () => {
115508
+ if (stopped2)
115509
+ return;
115510
+ const paths = readHostClipboardFilePaths();
115511
+ const key2 = paths.join("\0");
115512
+ if (key2 === last)
115513
+ return;
115514
+ last = key2;
115515
+ onChange(paths.length ? describeClipFiles(paths) : []);
115516
+ };
115517
+ const timer2 = setInterval(tick, intervalMs);
115518
+ timer2.unref?.();
115519
+ return () => {
115520
+ stopped2 = true;
115521
+ clearInterval(timer2);
115522
+ };
115523
+ }
115524
+
115525
+ // ../../packages/host-core/dist/rdesk/tesseract-engine.js
115526
+ var TesseractEngine = class {
115527
+ id = "tesseract";
115528
+ ctx;
115529
+ deps;
115530
+ pub = null;
115531
+ injector = null;
115532
+ /** Windows SendInput backend (win32 only) — the injector routes through it instead of libnut. */
115533
+ win = null;
115534
+ /** Unsubscribe from the machine lock/unlock watch (see start()). */
115535
+ stopLockWatch = null;
115536
+ /** Stops the host-clipboard file watcher for this session (see host-clipboard.ts). */
115537
+ stopClipWatch = null;
115538
+ monitorId = "";
115539
+ sourceId = "";
115540
+ // active capture source (screen or window)
115541
+ quality = "auto";
115542
+ systemAudio = false;
115543
+ access;
115544
+ constructor(ctx2, deps) {
115545
+ this.ctx = ctx2;
115546
+ this.deps = deps;
115547
+ this.access = ctx2.accessLevel;
115548
+ }
115549
+ /**
115550
+ * What this session can ACTUALLY do, derived at runtime.
115551
+ *
115552
+ * This used to be a hardcoded constant returned identically on every platform, so a
115553
+ * viewer was told `input: true` by a Wayland host that cannot inject a keystroke, and
115554
+ * offered a system-audio toggle on macOS where the capture is known to be silent. A
115555
+ * capability flag that doesn't track reality is worse than no flag: the viewer renders
115556
+ * a control that quietly does nothing.
115557
+ */
115558
+ capabilities() {
115559
+ return {
115560
+ // False until an input runner was actually obtained (see start()): on Wayland
115561
+ // without the portal, control is impossible and the session is view-only.
115562
+ input: !!this.injector,
115563
+ clipboard: true,
115564
+ // No drag/drop or upload surface exists inside an rdesk session today. It was
115565
+ // advertised as if it did; nothing in either viewer ever read it.
115566
+ fileTransfer: false,
115567
+ multiMonitor: true,
115568
+ windowCapture: true,
115569
+ // share a single window/app (privacy)
115570
+ // Voice now rides this session's own peer connection (setAv).
115571
+ bidiAudioVideo: true,
115572
+ // Windows/Linux capture desktop audio natively; macOS needs a loopback we don't
115573
+ // ship, so claiming it there just produces a toggle that yields silence.
115574
+ systemAudio: process.platform !== "darwin"
115575
+ };
115576
+ }
115577
+ listMonitors() {
115578
+ return listPublisherMonitors();
115579
+ }
115580
+ listSources() {
115581
+ return listPublisherSources();
115582
+ }
115583
+ startOpts(iceServers) {
115584
+ return { iceServers, quality: this.quality, systemAudio: this.systemAudio };
115585
+ }
115586
+ async setSource(sourceId) {
115587
+ this.sourceId = sourceId;
115588
+ const origin = sourceId.startsWith("screen:") ? this.originForActiveScreen() : { x: 0, y: 0 };
115589
+ this.injector?.setOrigin(origin);
115590
+ const iceServers = await this.deps.getIceServers().catch(() => []);
115591
+ await this.pub?.setSource(sourceId, this.startOpts(iceServers));
115592
+ }
115593
+ originForActiveScreen() {
115594
+ const b = displayBoundsById(this.monitorId);
115595
+ return b ? { x: b.x, y: b.y } : { x: 0, y: 0 };
115596
+ }
115597
+ /**
115598
+ * Tell the viewer the machine is LOCKED, using the daemon's existing lock/unlock watch.
115599
+ *
115600
+ * When Windows locks (or shows a UAC / Ctrl-Alt-Del secure desktop) capture goes black and any
115601
+ * injected input is silently dropped — a user process cannot reach the secure desktop. The viewer
115602
+ * would otherwise see a black rectangle and a "the display may be asleep" guess. Reporting an
115603
+ * accurate capture-lost with the real cause replaces that guess; on unlock we clear it. Held keys
115604
+ * are also released, so a modifier down when the lock hit doesn't stick. Enabled on every platform
115605
+ * (the events are cross-platform); the wording names the Windows secure desktop where it applies.
115606
+ */
115607
+ startLockWatch() {
115608
+ const lockedMsg = process.platform === "win32" ? "This computer is locked (Windows secure desktop). Its screen can\u2019t be shared and input is paused until someone signs in." : "This computer is locked. Its screen can\u2019t be shared until someone signs in.";
115609
+ try {
115610
+ if (systemState().screenLocked === true)
115611
+ this.ctx.onCapture?.(false, lockedMsg);
115612
+ } catch {
115613
+ }
115614
+ this.stopLockWatch = onSystemEvent((e) => {
115615
+ if (e.event.type === "power:lock-screen") {
115616
+ this.injector?.releaseAll();
115617
+ this.ctx.onCapture?.(false, lockedMsg);
115618
+ } else if (e.event.type === "power:unlock-screen") {
115619
+ this.ctx.onCapture?.(true, "");
115620
+ }
115621
+ });
115622
+ }
115623
+ async start(opts) {
115624
+ const monitors = await listPublisherMonitors();
115625
+ this.monitorId = opts.monitorId ?? monitors.find((m) => m.primary)?.id ?? monitors[0]?.id ?? "";
115626
+ this.quality = opts.quality ?? "auto";
115627
+ this.sourceId = await sourceIdForMonitor(this.monitorId).catch(() => "");
115628
+ const origin = displayBoundsById(this.monitorId) ?? { x: 0, y: 0, width: 0, height: 0 };
115629
+ let runner2;
115630
+ if (this.deps.makeRunner) {
115631
+ runner2 = this.deps.makeRunner();
115632
+ } else if (isWaylandSession()) {
115633
+ let waylandReason = "";
115634
+ const wp = await createWaylandInputProvider((r) => {
115635
+ waylandReason = r;
115636
+ });
115637
+ if (wp) {
115638
+ const m = monitors.find((x) => x.id === this.monitorId) ?? monitors[0];
115639
+ runner2 = waylandInputRunner(wp, { w: m?.width ?? origin.width, h: m?.height ?? origin.height });
115640
+ this.ctx.log("rdesk: Wayland input via XDG RemoteDesktop portal.");
115641
+ } else {
115642
+ this.ctx.log(`rdesk: Wayland \u2014 keyboard and mouse unavailable, streaming view-only. Reason: ${waylandReason || "unknown"}`);
115643
+ runner2 = createDaemonOsControlRunner();
115644
+ }
115645
+ } else {
115646
+ runner2 = createDaemonOsControlRunner();
115647
+ }
115648
+ let winBinding = null;
115649
+ if (!this.deps.makeRunner && process.platform === "win32") {
115650
+ const win3 = createWinInput({ log: (m) => this.ctx.log(m) });
115651
+ if (win3) {
115652
+ this.win = win3;
115653
+ void win3.warm();
115654
+ winBinding = { api: win3, toFraction: (p) => virtualDesktopFraction(p) };
115655
+ }
115656
+ }
115657
+ this.injector = new RdeskInputInjector(runner2, { x: origin.x, y: origin.y }, this.ctx.accessLevel, winBinding);
115658
+ this.startLockWatch();
115659
+ const iceServers = await this.deps.getIceServers().catch(() => []);
115660
+ const pub = new PublisherWindow(this.ctx.sessionId, {
115661
+ onOffer: (sdp) => this.ctx.sendSignal({ type: "signal:offer", sdp }),
115662
+ onIce: (candidate) => this.ctx.sendSignal({ type: "signal:ice", candidate }),
115663
+ onInput: (ev) => void this.injector?.apply(ev),
115664
+ // Clipboard sync (Phase 6): write the viewer's clipboard onto the host so ⌘V pastes
115665
+ // into the remote machine. Gated by control access (view-only never writes).
115666
+ onClipboard: (text13) => {
115667
+ if (this.access === "control") {
115668
+ const el2 = electronModule();
115669
+ try {
115670
+ el2?.clipboard?.writeText(text13);
115671
+ } catch {
115672
+ }
115673
+ }
115674
+ },
115675
+ onReady: () => this.ctx.log("publisher connected"),
115676
+ onError: (err2) => {
115677
+ this.ctx.log(`publisher error: ${err2}`);
115678
+ this.ctx.sendControl({ type: "rdesk:closed", reason: "network" });
115679
+ },
115680
+ // Media is direct-first; log which path actually won so a regression to relay
115681
+ // (which costs real bandwidth) is visible instead of silent. Types only, no IPs.
115682
+ onCapture: (ok, message) => {
115683
+ this.ctx.log(`capture ${ok ? "ok" : `LOST \u2014 ${message}`}`);
115684
+ this.ctx.onCapture?.(ok, message);
115685
+ },
115686
+ onPath: (path6, d) => {
115687
+ this.ctx.log(`media path=${path6} (local=${d.localType ?? "?"} remote=${d.remoteType ?? "?"})`);
115688
+ this.ctx.onPath?.(path6, d);
115689
+ }
115690
+ });
115691
+ this.pub = pub;
115692
+ this.stopClipWatch = watchHostClipboardFiles((files) => {
115693
+ if (this.access !== "control")
115694
+ return;
115695
+ this.pub?.sendData({ t: "clipboard-files", files });
115696
+ });
115697
+ await pub.start({
115698
+ iceServers,
115699
+ monitorId: this.monitorId,
115700
+ quality: this.quality,
115701
+ systemAudio: this.systemAudio
115702
+ });
115703
+ }
115704
+ onSignal(msg) {
115705
+ if (!this.pub)
115706
+ return;
115707
+ if (msg.type === "signal:answer")
115708
+ this.pub.answer(msg.sdp);
115709
+ else if (msg.type === "signal:ice")
115710
+ this.pub.addIce(msg.candidate);
115711
+ }
115712
+ applyInput(ev) {
115713
+ void this.injector?.apply(ev);
115714
+ }
115715
+ setAccessLevel(level) {
115716
+ this.access = level;
115717
+ this.injector?.setAccess(level);
115718
+ }
115719
+ async setMonitor(monitorId) {
115720
+ this.monitorId = monitorId;
115721
+ this.sourceId = await sourceIdForMonitor(monitorId).catch(() => this.sourceId);
115722
+ const origin = displayBoundsById(monitorId);
115723
+ if (origin && this.injector)
115724
+ this.injector.setOrigin({ x: origin.x, y: origin.y });
115725
+ const iceServers = await this.deps.getIceServers().catch(() => []);
115726
+ await this.pub?.setMonitor(monitorId, this.startOpts(iceServers));
115727
+ }
115728
+ setAv(on) {
115729
+ this.pub?.setAv(on);
115730
+ }
115731
+ setQuality(preset) {
115732
+ this.quality = preset;
115733
+ this.pub?.setQuality(preset);
115734
+ }
115735
+ setMute(m) {
115736
+ if (typeof m.systemAudio === "boolean" && m.systemAudio !== this.systemAudio && this.sourceId) {
115737
+ this.systemAudio = m.systemAudio;
115738
+ void this.deps.getIceServers().catch(() => []).then((ice) => this.pub?.setSource(this.sourceId, this.startOpts(ice)));
115739
+ }
115740
+ }
115741
+ async stop() {
115742
+ this.stopClipWatch?.();
115743
+ this.stopClipWatch = null;
115744
+ this.stopLockWatch?.();
115745
+ this.stopLockWatch = null;
115746
+ this.injector?.releaseAll();
115747
+ await this.pub?.stop();
115748
+ this.pub = null;
115749
+ this.injector = null;
115750
+ await this.win?.stop();
115751
+ this.win = null;
115752
+ }
115753
+ };
115754
+ function registerTesseractEngine(deps) {
115755
+ registerRdeskEngine("tesseract", (ctx2) => new TesseractEngine(ctx2, deps));
115756
+ }
115757
+
115758
+ // ../../packages/host-core/dist/local-server.js
115759
+ import { timingSafeEqual as timingSafeEqual5 } from "node:crypto";
115760
+
115761
+ // ../../packages/host-core/dist/in-process-link.js
115762
+ var OPEN = 1;
115763
+ var CLOSED = 3;
115764
+ var InProcSocket = class {
115765
+ OPEN = OPEN;
115766
+ CLOSED = CLOSED;
115767
+ readyState = OPEN;
115768
+ peer = null;
115769
+ listeners = /* @__PURE__ */ new Map();
115770
+ on(event, cb) {
115771
+ let s = this.listeners.get(event);
115772
+ if (!s) {
115773
+ s = /* @__PURE__ */ new Set();
115774
+ this.listeners.set(event, s);
115775
+ }
115776
+ s.add(cb);
115777
+ return this;
115778
+ }
115779
+ once(event, cb) {
115780
+ const wrap = (...a) => {
115781
+ this.off(event, wrap);
115782
+ cb(...a);
115783
+ };
115784
+ return this.on(event, wrap);
115785
+ }
115786
+ off(event, cb) {
115787
+ this.listeners.get(event)?.delete(cb);
115788
+ return this;
115789
+ }
115790
+ removeListener(event, cb) {
115791
+ return this.off(event, cb);
115792
+ }
115793
+ fire(event, ...args) {
115794
+ const s = this.listeners.get(event);
115795
+ if (!s)
115796
+ return;
115797
+ for (const cb of [...s]) {
115798
+ try {
115799
+ cb(...args);
115002
115800
  } catch {
115003
- done(false);
115004
115801
  }
115005
- });
115802
+ }
115006
115803
  }
115007
- try {
115008
- await fetch(url2, { method: "HEAD", signal: AbortSignal.timeout(PROBE_TIMEOUT_MS2) });
115009
- return true;
115010
- } catch {
115011
- return false;
115804
+ /** Deliver `data` to the PEER's 'message' listeners on the next macrotask. */
115805
+ send(data) {
115806
+ const peer = this.peer;
115807
+ if (!peer || peer.readyState !== OPEN)
115808
+ return;
115809
+ setImmediate(() => {
115810
+ if (peer.readyState === OPEN)
115811
+ peer.fire("message", data);
115812
+ });
115012
115813
  }
115013
- }
115014
- async function probeNetwork() {
115015
- const started2 = Date.now();
115016
- const results = await Promise.all(PROBE_URLS.map((u) => probeOnce(u)));
115017
- const reachable = results.some(Boolean);
115018
- emitSystemEvent({ type: "net:probe", reachable, ms: Date.now() - started2 });
115019
- if (reachable !== state2.online)
115020
- emitSystemEvent({ type: reachable ? "net:online" : "net:offline", evidence: "probed" });
115021
- return reachable;
115022
- }
115023
- function startNetworkProbing(requester = "daemon") {
115024
- wanted.add(requester);
115025
- if (probing)
115026
- return;
115027
- probing = true;
115028
- probeAttempt = 0;
115029
- scheduleProbe(0);
115030
- }
115031
- function stopNetworkProbing(requester) {
115032
- if (requester !== void 0) {
115033
- wanted.delete(requester);
115034
- if (wanted.size > 0)
115814
+ close(_code, _reason) {
115815
+ if (this.readyState === CLOSED)
115035
115816
  return;
115036
- } else {
115037
- wanted.clear();
115817
+ this.readyState = CLOSED;
115818
+ const peer = this.peer;
115819
+ setImmediate(() => this.fire("close"));
115820
+ if (peer && peer.readyState !== CLOSED) {
115821
+ peer.readyState = CLOSED;
115822
+ setImmediate(() => peer.fire("close"));
115823
+ }
115038
115824
  }
115039
- probing = false;
115040
- if (probeTimer) {
115041
- clearTimeout(probeTimer);
115042
- probeTimer = null;
115825
+ /** @internal wire this end to its peer. */
115826
+ _link(peer) {
115827
+ this.peer = peer;
115043
115828
  }
115044
- probeAttempt = 0;
115829
+ /** @internal fire 'open' on the next macrotask (client end only; the server never awaits it). */
115830
+ _fireOpen() {
115831
+ setImmediate(() => this.fire("open"));
115832
+ }
115833
+ };
115834
+ function makeInProcPair() {
115835
+ const serverEnd = new InProcSocket();
115836
+ const clientEnd = new InProcSocket();
115837
+ serverEnd._link(clientEnd);
115838
+ clientEnd._link(serverEnd);
115839
+ return { serverEnd, clientEnd };
115045
115840
  }
115046
- var probeTimer = null;
115047
- var probeAttempt = 0;
115048
- var probing = false;
115049
- var wanted = /* @__PURE__ */ new Set();
115050
- function scheduleProbe(delayMs) {
115051
- if (probeTimer)
115052
- clearTimeout(probeTimer);
115053
- probeTimer = setTimeout(() => {
115054
- void runProbeCycle();
115055
- }, delayMs);
115056
- probeTimer.unref?.();
115841
+ var connector = null;
115842
+ function setInProcConnector(fn) {
115843
+ connector = fn;
115057
115844
  }
115058
- async function runProbeCycle() {
115059
- probeTimer = null;
115060
- if (!probing)
115061
- return;
115062
- const ok = await probeNetwork();
115063
- if (!probing)
115064
- return;
115065
- if (ok) {
115066
- probing = false;
115067
- probeAttempt = 0;
115068
- wanted.clear();
115069
- return;
115845
+
115846
+ // ../../packages/host-core/dist/rdesk/server-prefs.js
115847
+ init_credentials();
115848
+ import { readFileSync as readFileSync44, writeFileSync as writeFileSync29 } from "node:fs";
115849
+ import { join as join72 } from "node:path";
115850
+ function rdeskServerPrefsPath() {
115851
+ return join72(daemonDir2(), "rdesk-server.json");
115852
+ }
115853
+ var DEFAULTS2 = {
115854
+ enabled: true,
115855
+ defaultAccess: "control",
115856
+ quality: "auto",
115857
+ autostart: true,
115858
+ hideTray: false,
115859
+ noticeShownAt: 0
115860
+ };
115861
+ function readRdeskServerPrefs() {
115862
+ try {
115863
+ const p = JSON.parse(readFileSync44(rdeskServerPrefsPath(), "utf8"));
115864
+ return {
115865
+ // `!== false` (not `?? true`): only an explicit opt-out disables sharing, so a
115866
+ // partially-written or older prefs file never silently turns the machine off.
115867
+ enabled: p.enabled !== false,
115868
+ defaultAccess: p.defaultAccess === "view" ? "view" : "control",
115869
+ quality: p.quality ?? DEFAULTS2.quality,
115870
+ autostart: p.autostart ?? DEFAULTS2.autostart,
115871
+ hideTray: p.hideTray ?? DEFAULTS2.hideTray,
115872
+ noticeShownAt: typeof p.noticeShownAt === "number" ? p.noticeShownAt : 0
115873
+ };
115874
+ } catch {
115875
+ return { ...DEFAULTS2 };
115070
115876
  }
115071
- scheduleProbe(probeDelayMs(probeAttempt++));
115072
115877
  }
115073
- function addressFingerprint() {
115074
- const out = [];
115075
- for (const [name, addrs] of Object.entries(networkInterfaces())) {
115076
- for (const a of addrs ?? [])
115077
- if (!a.internal)
115078
- out.push(`${name}:${a.address}`);
115878
+ function writeRdeskServerPrefs(patch) {
115879
+ try {
115880
+ const next = { ...readRdeskServerPrefs(), ...patch };
115881
+ writeFileSync29(rdeskServerPrefsPath(), JSON.stringify(next), { mode: 384 });
115882
+ } catch {
115079
115883
  }
115080
- return out.sort();
115081
115884
  }
115082
- function readLidClosed() {
115083
- if (platform2() !== "darwin")
115084
- return Promise.resolve(null);
115085
- return new Promise((resolve16) => {
115086
- execFile8("ioreg", ["-r", "-k", "AppleClamshellState", "-d", "4"], { timeout: 4e3 }, (err2, stdout) => {
115087
- resolve16(err2 ? null : parseClamshell(stdout ?? ""));
115088
- });
115089
- });
115885
+ function rdeskSharingEnabled() {
115886
+ if (process.env.CCB_RDESK_ENABLED === "0")
115887
+ return false;
115888
+ if (process.env.CCB_RDESK_ENABLED === "1")
115889
+ return true;
115890
+ return readRdeskServerPrefs().enabled;
115090
115891
  }
115091
- function parseClamshell(stdout) {
115092
- const m = /"AppleClamshellState"\s*=\s*(Yes|No)/.exec(stdout);
115093
- return m ? m[1] === "Yes" : null;
115892
+
115893
+ // ../../packages/host-core/dist/rdesk/password.js
115894
+ init_credentials();
115895
+ import { scryptSync, randomBytes as randomBytes5, timingSafeEqual as timingSafeEqual4, randomInt } from "node:crypto";
115896
+ import { readFileSync as readFileSync45, writeFileSync as writeFileSync30, existsSync as existsSync32, unlinkSync as unlinkSync4 } from "node:fs";
115897
+ import { join as join73 } from "node:path";
115898
+ function pwFile() {
115899
+ return join73(daemonDir2(), "rdesk-password.json");
115094
115900
  }
115095
- var TICK_MS = 1e4;
115096
- var CLOCK_JUMP_MS = TICK_MS * 3;
115097
- var LID_EVERY_TICKS = 3;
115098
- var stops = [];
115099
- var started = false;
115100
- function startSystemEvents(opts = {}) {
115101
- if (started)
115102
- return;
115103
- started = true;
115104
- const log10 = opts.log ?? (() => {
115105
- });
115106
- const el2 = electronModule();
115107
- emitSystemEvent({ type: "process:started", pid: process.pid, version: opts.version ?? "dev", headless: !el2 });
115108
- const pm = el2?.powerMonitor;
115109
- if (pm?.on) {
115110
- const wire = (name, make) => {
115111
- const h = () => {
115112
- emitSystemEvent(make());
115113
- };
115114
- try {
115115
- pm.on(name, h);
115116
- stops.push(() => {
115117
- try {
115118
- pm.removeListener(name, h);
115119
- } catch {
115120
- }
115121
- });
115122
- } catch {
115123
- }
115124
- };
115125
- wire("suspend", () => ({ type: "power:suspend" }));
115126
- wire("resume", () => ({ type: "power:resume" }));
115127
- wire("on-ac", () => ({ type: "power:on-ac" }));
115128
- wire("on-battery", () => ({ type: "power:on-battery" }));
115129
- wire("lock-screen", () => ({ type: "power:lock-screen" }));
115130
- wire("unlock-screen", () => ({ type: "power:unlock-screen" }));
115131
- wire("user-did-become-active", () => ({ type: "power:user-active" }));
115132
- wire("user-did-resign-active", () => ({ type: "power:user-idle", idleSeconds: pm.getSystemIdleTime?.() ?? 0 }));
115133
- wire("shutdown", () => ({ type: "power:os-shutdown" }));
115134
- wire("thermal-state-change", () => ({ type: "power:thermal", state: pm.getCurrentThermalState?.() ?? "unknown" }));
115135
- if (typeof pm.onBatteryPower === "boolean")
115136
- emitSystemEvent({ type: pm.onBatteryPower ? "power:on-battery" : "power:on-ac" });
115137
- }
115138
- if (el2?.screen?.on) {
115139
- const count = () => {
115140
- try {
115141
- return el2.screen.getAllDisplays().length;
115142
- } catch {
115143
- return 0;
115144
- }
115145
- };
115146
- const wireScreen = (name, type) => {
115147
- const h = () => {
115148
- emitSystemEvent({ type, count: count() });
115149
- };
115150
- try {
115151
- el2.screen.on(name, h);
115152
- stops.push(() => {
115153
- try {
115154
- el2.screen.removeListener?.(name, h);
115155
- } catch {
115156
- }
115157
- });
115158
- } catch {
115159
- }
115160
- };
115161
- wireScreen("display-added", "display:added");
115162
- wireScreen("display-removed", "display:removed");
115163
- wireScreen("display-metrics-changed", "display:changed");
115164
- state2.displays = count();
115165
- }
115166
- if (el2?.app?.on) {
115167
- const wireApp = (name, fire) => {
115168
- try {
115169
- el2.app.on(name, fire);
115170
- stops.push(() => {
115171
- try {
115172
- el2.app.removeListener?.(name, fire);
115173
- } catch {
115174
- }
115175
- });
115176
- } catch {
115177
- }
115178
- };
115179
- joinParticipant(DAEMON_PARTICIPANT, "Tesseract daemon");
115180
- wireApp("activate", () => reportParticipantActive(DAEMON_PARTICIPANT, true));
115181
- wireApp("did-become-active", () => reportParticipantActive(DAEMON_PARTICIPANT, true));
115182
- wireApp("did-resign-active", () => reportParticipantActive(DAEMON_PARTICIPANT, false));
115183
- wireApp("browser-window-focus", () => reportParticipantFocus(DAEMON_PARTICIPANT, true));
115184
- wireApp("browser-window-blur", () => reportParticipantFocus(DAEMON_PARTICIPANT, false));
115901
+ function pwPlainFile() {
115902
+ return join73(daemonDir2(), "rdesk-password.enc");
115903
+ }
115904
+ var PW_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
115905
+ var PW_LENGTH = 16;
115906
+ function generatePassword() {
115907
+ let out = "";
115908
+ for (let i = 0; i < PW_LENGTH; i++)
115909
+ out += PW_ALPHABET[randomInt(PW_ALPHABET.length)];
115910
+ return (out.match(/.{1,4}/g) ?? [out]).join("-");
115911
+ }
115912
+ function writePlain(password) {
115913
+ try {
115914
+ const safe2 = electronModule()?.safeStorage;
115915
+ if (!safe2?.isEncryptionAvailable())
115916
+ return;
115917
+ writeFileSync30(pwPlainFile(), safe2.encryptString(password), { mode: 384 });
115918
+ } catch {
115185
115919
  }
115186
- if (el2?.nativeTheme?.on) {
115187
- const h = () => {
115188
- emitSystemEvent({ type: "theme:changed", dark: !!el2.nativeTheme?.shouldUseDarkColors });
115189
- };
115190
- try {
115191
- el2.nativeTheme.on("updated", h);
115192
- stops.push(() => {
115193
- try {
115194
- el2.nativeTheme?.removeListener?.("updated", h);
115195
- } catch {
115196
- }
115197
- });
115198
- } catch {
115199
- }
115920
+ }
115921
+ function canRevealRdeskPassword() {
115922
+ try {
115923
+ const safe2 = electronModule()?.safeStorage;
115924
+ return !!safe2?.isEncryptionAvailable() && existsSync32(pwPlainFile());
115925
+ } catch {
115926
+ return false;
115200
115927
  }
115201
- let lastTick = Date.now();
115202
- let addresses = addressFingerprint();
115203
- let ticks = 0;
115204
- const tick = setInterval(() => {
115205
- const now2 = Date.now();
115206
- const drift = now2 - lastTick;
115207
- lastTick = now2;
115208
- if (drift > CLOCK_JUMP_MS)
115209
- emitSystemEvent({ type: "power:clock-jump", missedMs: drift - TICK_MS });
115210
- const next = addressFingerprint();
115211
- if (next.join("|") !== addresses.join("|")) {
115212
- addresses = next;
115213
- emitSystemEvent({ type: "net:interfaces-changed", addresses: next });
115214
- void probeNetwork().catch(() => {
115215
- });
115216
- }
115217
- if (++ticks % LID_EVERY_TICKS === 0) {
115218
- void readLidClosed().then((closed) => {
115219
- if (closed === null || closed === state2.lidClosed)
115220
- return;
115221
- emitSystemEvent({ type: closed ? "lid:closed" : "lid:opened" });
115222
- }).catch(() => {
115223
- });
115224
- }
115225
- }, TICK_MS);
115226
- tick.unref?.();
115227
- stops.push(() => clearInterval(tick));
115928
+ }
115929
+ function readRdeskPasswordPlain() {
115228
115930
  try {
115229
- const osOnline = el2?.net?.isOnline?.();
115230
- if (typeof osOnline === "boolean")
115231
- reportOsOnline(osOnline);
115931
+ const safe2 = electronModule()?.safeStorage;
115932
+ if (!safe2?.isEncryptionAvailable())
115933
+ return null;
115934
+ return safe2.decryptString(readFileSync45(pwPlainFile()));
115232
115935
  } catch {
115936
+ return null;
115233
115937
  }
115234
- log10(`system events: watching (${el2 ? "electron" : "headless"})`);
115235
115938
  }
115236
- function stopSystemEvents() {
115237
- for (const s of stops) {
115939
+ function setRdeskPassword(password) {
115940
+ if (!password) {
115238
115941
  try {
115239
- s();
115942
+ unlinkSync4(pwFile());
115943
+ } catch {
115944
+ }
115945
+ try {
115946
+ unlinkSync4(pwPlainFile());
115240
115947
  } catch {
115241
115948
  }
115949
+ return;
115950
+ }
115951
+ const salt = randomBytes5(16);
115952
+ const hash2 = scryptSync(password, salt, 32);
115953
+ writeFileSync30(pwFile(), JSON.stringify({ salt: salt.toString("hex"), hash: hash2.toString("hex") }), { mode: 384 });
115954
+ writePlain(password);
115955
+ }
115956
+ function regenerateRdeskPassword() {
115957
+ const pw = generatePassword();
115958
+ setRdeskPassword(pw);
115959
+ return pw;
115960
+ }
115961
+ function ensureRdeskPassword() {
115962
+ if (hasRdeskPassword())
115963
+ return null;
115964
+ return regenerateRdeskPassword();
115965
+ }
115966
+ function hasRdeskPassword() {
115967
+ return !!process.env.CCB_RDESK_PASSWORD || existsSync32(pwFile());
115968
+ }
115969
+ function verifyRdeskPassword(password) {
115970
+ if (!password)
115971
+ return false;
115972
+ const override3 = process.env.CCB_RDESK_PASSWORD;
115973
+ if (override3)
115974
+ return password.length === override3.length && timingSafeEqual4(Buffer.from(password), Buffer.from(override3));
115975
+ try {
115976
+ const { salt, hash: hash2 } = JSON.parse(readFileSync45(pwFile(), "utf8"));
115977
+ const got = scryptSync(password, Buffer.from(salt, "hex"), 32);
115978
+ const want = Buffer.from(hash2, "hex");
115979
+ return got.length === want.length && timingSafeEqual4(got, want);
115980
+ } catch {
115981
+ return false;
115242
115982
  }
115243
- stops = [];
115244
- stopNetworkProbing();
115245
- leaveParticipant(DAEMON_PARTICIPANT);
115246
- started = false;
115247
115983
  }
115248
115984
 
115249
115985
  // ../../packages/host-core/dist/local-server.js
@@ -117859,7 +118595,9 @@ function uninstallCliAgent(log10 = console.log) {
117859
118595
  log10("[cli-agent] uninstalling the tesseract-agent npm package");
117860
118596
  execFileSync4(process.platform === "win32" ? "npm.cmd" : "npm", ["uninstall", "-g", "tesseract-agent"], {
117861
118597
  stdio: ["ignore", "pipe", "pipe"],
117862
- timeout: 12e4
118598
+ timeout: 12e4,
118599
+ shell: process.platform === "win32",
118600
+ windowsHide: true
117863
118601
  });
117864
118602
  log10("[cli-agent] uninstalled");
117865
118603
  return true;
@@ -120295,6 +121033,7 @@ var realHost = () => hostname5();
120295
121033
 
120296
121034
  // ../../packages/host-core/dist/cli-link/install.js
120297
121035
  import { chmodSync as chmodSync6, existsSync as existsSync40, mkdirSync as mkdirSync39, readFileSync as readFileSync53, writeFileSync as writeFileSync37 } from "node:fs";
121036
+ import { spawnSync as spawnSync3 } from "node:child_process";
120298
121037
  import { dirname as dirname33 } from "node:path";
120299
121038
 
120300
121039
  // ../../packages/host-core/dist/cli-link/plan.js
@@ -120363,6 +121102,42 @@ function planCliLink(args) {
120363
121102
 
120364
121103
  // ../../packages/host-core/dist/cli-link/install.js
120365
121104
  init_patched_resources();
121105
+ function readUserPath() {
121106
+ const r = spawnSync3("reg", ["query", "HKCU\\Environment", "/v", "Path"], { encoding: "utf8", windowsHide: true });
121107
+ if (r.status !== 0 || !r.stdout)
121108
+ return "";
121109
+ const m = r.stdout.match(/^\s*Path\s+REG_(?:EXPAND_)?SZ\s+(.*)$/im);
121110
+ return m ? m[1].trim() : "";
121111
+ }
121112
+ function pathHasEntry(pathValue, dir2) {
121113
+ const norm = (s) => s.trim().replace(/\\+$/, "").toLowerCase();
121114
+ const want = norm(dir2);
121115
+ return pathValue.split(";").some((e) => e.trim() !== "" && norm(e) === want);
121116
+ }
121117
+ function broadcastEnvChange() {
121118
+ const ps2 = [
121119
+ `$s = '[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);';`,
121120
+ "$t = Add-Type -MemberDefinition $s -Name Win32Env -Namespace Tess -PassThru;",
121121
+ "$r = [UIntPtr]::Zero;",
121122
+ // HWND_BROADCAST=0xffff, WM_SETTINGCHANGE=0x1A, SMTO_ABORTIFHUNG=0x2, 5s timeout.
121123
+ "[void]$t::SendMessageTimeout([IntPtr]0xffff, 0x1A, [UIntPtr]::Zero, 'Environment', 0x2, 5000, [ref]$r);"
121124
+ ].join(" ");
121125
+ try {
121126
+ spawnSync3("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps2], { windowsHide: true, timeout: 15e3 });
121127
+ } catch {
121128
+ }
121129
+ }
121130
+ function ensureWindowsPathEntry(dir2) {
121131
+ const current = readUserPath();
121132
+ if (pathHasEntry(current, dir2))
121133
+ return { ok: true, added: false };
121134
+ const next = current ? `${current.replace(/;+$/, "")};${dir2}` : dir2;
121135
+ const w = spawnSync3("reg", ["add", "HKCU\\Environment", "/v", "Path", "/t", "REG_EXPAND_SZ", "/d", next, "/f"], { encoding: "utf8", windowsHide: true });
121136
+ if (w.status !== 0)
121137
+ return { ok: false, added: false, reason: (w.stderr || w.stdout || "reg add failed").trim() };
121138
+ broadcastEnvChange();
121139
+ return { ok: true, added: true };
121140
+ }
120366
121141
  function installCliShims(args) {
120367
121142
  const plan = planCliLink(args);
120368
121143
  if (!plan)
@@ -120401,6 +121176,12 @@ function installCliShims(args) {
120401
121176
  }
120402
121177
  }
120403
121178
  }
121179
+ if (plan.needsPathEntry && process.platform === "win32" && installed.length > 0) {
121180
+ const pathRes = ensureWindowsPathEntry(plan.dir);
121181
+ if (!pathRes.ok) {
121182
+ return { ok: false, dir: plan.dir, installed, skipped, reason: `shims written but PATH update failed: ${pathRes.reason ?? "unknown"}` };
121183
+ }
121184
+ }
120404
121185
  return { ok: installed.length > 0, dir: plan.dir, installed, skipped };
120405
121186
  }
120406
121187
  function ensureCliOnPath(log10) {
@@ -121577,7 +122358,7 @@ function startRdeskChannelClient(deps) {
121577
122358
  if (sock?.readyState === wrapper_default.OPEN)
121578
122359
  sock.send(JSON.stringify({ channel: "rdeskhost", payload }));
121579
122360
  };
121580
- const scheduleRetry = () => {
122361
+ const scheduleRetry2 = () => {
121581
122362
  if (stopped2 || retry)
121582
122363
  return;
121583
122364
  retry = setTimeout(() => {
@@ -121591,7 +122372,7 @@ function startRdeskChannelClient(deps) {
121591
122372
  return;
121592
122373
  const url2 = readEndpoint(deps.daemonDir(), deps.selfPid);
121593
122374
  if (!url2) {
121594
- scheduleRetry();
122375
+ scheduleRetry2();
121595
122376
  return;
121596
122377
  }
121597
122378
  const ws = new wrapper_default(url2);
@@ -121646,7 +122427,7 @@ function startRdeskChannelClient(deps) {
121646
122427
  return;
121647
122428
  sock = null;
121648
122429
  setHolding(false, "disconnected");
121649
- scheduleRetry();
122430
+ scheduleRetry2();
121650
122431
  };
121651
122432
  ws.on("close", drop);
121652
122433
  ws.on("error", () => {
@@ -122281,11 +123062,28 @@ async function homeFor(hostId) {
122281
123062
  }
122282
123063
  return null;
122283
123064
  }
122284
- async function resolveTarget(urlPath) {
122285
- const segs = urlPath.split("/").filter(Boolean).map((s) => decodeURIComponent(s));
122286
- if (segs.length === 0 || segs[0] !== token2)
123065
+ function safeDecode3(s) {
123066
+ try {
123067
+ return decodeURIComponent(s);
123068
+ } catch {
122287
123069
  return null;
122288
- if (segs.some((seg) => seg === ".." || seg === "." || seg.includes("\0")))
123070
+ }
123071
+ }
123072
+ function decodeSegs(urlPath) {
123073
+ const segs = [];
123074
+ for (const raw of urlPath.split("/").filter(Boolean)) {
123075
+ const d = safeDecode3(raw);
123076
+ if (d === null)
123077
+ return null;
123078
+ if (d === ".." || d === "." || /[\\/:\0]/.test(d))
123079
+ return null;
123080
+ segs.push(d);
123081
+ }
123082
+ return segs;
123083
+ }
123084
+ async function resolveTarget(urlPath) {
123085
+ const segs = decodeSegs(urlPath);
123086
+ if (!segs || segs.length === 0 || segs[0] !== token2)
122289
123087
  return null;
122290
123088
  const rest = segs.slice(1);
122291
123089
  if (rest.length === 0)
@@ -122300,7 +123098,11 @@ async function resolveTarget(urlPath) {
122300
123098
  if (!home)
122301
123099
  return null;
122302
123100
  const rel = rest.slice(1);
122303
- return { kind: "path", hostId, label, remote: `${home.replace(/\/+$/, "")}/${rel.join("/")}`, rel };
123101
+ const base = home.replace(/\/+$/, "");
123102
+ const remote = `${base}/${rel.join("/")}`;
123103
+ if (remote !== base && !remote.startsWith(`${base}/`))
123104
+ return null;
123105
+ return { kind: "path", hostId, label, remote, rel };
122304
123106
  }
122305
123107
  async function remotePathOf(t) {
122306
123108
  if (t.kind === "machine")
@@ -122322,10 +123124,8 @@ var href = (segs) => "/" + segs.map((s) => encodeURIComponent(s)).join("/");
122322
123124
  function propfindXml(entries) {
122323
123125
  const body = entries.map((e) => {
122324
123126
  const mtime = new Date(e.mtimeMs ?? Date.now()).toUTCString();
122325
- const iso = new Date(e.mtimeMs ?? Date.now()).toISOString();
122326
- const name = e.segs[e.segs.length - 1] ?? "Tesseract";
122327
123127
  const props = e.isDir ? `<D:resourcetype><D:collection/></D:resourcetype>` : `<D:resourcetype/><D:getcontentlength>${e.size ?? 0}</D:getcontentlength>`;
122328
- return `<D:response><D:href>${xmlEscape(href(e.segs))}${e.isDir ? "/" : ""}</D:href><D:propstat><D:prop><D:displayname>${xmlEscape(name)}</D:displayname><D:getlastmodified>${mtime}</D:getlastmodified><D:creationdate>${iso}</D:creationdate>` + props + `</D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>`;
123128
+ return `<D:response><D:href>${xmlEscape(href(e.segs))}${e.isDir ? "/" : ""}</D:href><D:propstat><D:prop><D:getlastmodified>${mtime}</D:getlastmodified>` + props + `</D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response>`;
122329
123129
  }).join("");
122330
123130
  return `<?xml version="1.0" encoding="utf-8"?><D:multistatus xmlns:D="DAV:">${body}</D:multistatus>`;
122331
123131
  }
@@ -122517,7 +123317,7 @@ async function route2(req, res) {
122517
123317
  send4(res, 404, "not found", DAV_HEADERS);
122518
123318
  return;
122519
123319
  }
122520
- const urlSegs = urlPath.split("/").filter(Boolean).map((s) => decodeURIComponent(s));
123320
+ const urlSegs = decodeSegs(urlPath) ?? [];
122521
123321
  switch (method) {
122522
123322
  case "PROPFIND":
122523
123323
  await handlePropfind(req, res, t, urlSegs);
@@ -122595,7 +123395,7 @@ function bind(i) {
122595
123395
  s.on("listening", () => {
122596
123396
  server = s;
122597
123397
  boundPort = port;
122598
- log8(`file-share (WebDAV) listening on http://127.0.0.1:${port}/${token2}/`);
123398
+ log8(`file-share (WebDAV) listening on http://127.0.0.1:${port}/ (token redacted)`);
122599
123399
  });
122600
123400
  s.on("error", (e) => {
122601
123401
  if (e.code === "EADDRINUSE") {
@@ -122648,6 +123448,15 @@ var run3 = promisify6(execFile10);
122648
123448
  var OPTS = { timeout: 2e4, windowsHide: true };
122649
123449
  var mounted = false;
122650
123450
  var mountedAt = null;
123451
+ var RETRY_MS = 6e4;
123452
+ var MAX_ATTEMPTS = 5;
123453
+ var retryTimer = null;
123454
+ var attempts = 0;
123455
+ function redact(s) {
123456
+ const tok = fileShareToken();
123457
+ const out = tok ? s.split(tok).join("<token>") : s;
123458
+ return out.replace(/([/\\])[0-9a-f]{32}(?=[/\\"'\s]|$)/gi, "$1<token>");
123459
+ }
122651
123460
  function davUri() {
122652
123461
  const port = fileSharePort();
122653
123462
  const token4 = fileShareToken();
@@ -122675,44 +123484,135 @@ async function mountLinux(log10) {
122675
123484
  const msg = String(e.message || "");
122676
123485
  if (/already mounted/i.test(msg)) {
122677
123486
  } else if (/not found|ENOENT/i.test(msg)) {
122678
- log10(`file-share: gio not available \u2014 install gvfs-backends to mount ${uri} in Files`);
123487
+ log10(`file-share: gio not available \u2014 install gvfs-backends to mount ${redact(uri)} in Files`);
122679
123488
  return false;
122680
123489
  } else if (/No such interface|not supported|dav/i.test(msg)) {
122681
- log10(`file-share: GVfs has no dav backend \u2014 install gvfs-backends (${msg.split("\n")[0]})`);
123490
+ log10(`file-share: GVfs has no dav backend \u2014 install gvfs-backends (${redact(msg.split("\n")[0])})`);
122682
123491
  return false;
122683
123492
  } else {
122684
- log10(`file-share: gio mount failed: ${msg.split("\n")[0]}`);
123493
+ log10(`file-share: gio mount failed: ${redact(msg.split("\n")[0])}`);
122685
123494
  return false;
122686
123495
  }
122687
123496
  }
122688
123497
  addGtkBookmark(uri, "Tesseract");
122689
123498
  mountedAt = uri;
122690
- log10(`file-share: mounted in Files (GVfs) \u2192 ${uri}`);
123499
+ log10(`file-share: mounted in Files (GVfs) \u2192 ${redact(uri)}`);
122691
123500
  return true;
122692
123501
  }
123502
+ function runWin(cmd, args) {
123503
+ return new Promise((resolve16) => {
123504
+ execFile10(cmd, args, { ...OPTS, encoding: "buffer", maxBuffer: 8 * 1024 * 1024 }, (err2, stdout, stderr) => {
123505
+ const e = err2;
123506
+ const combined = Buffer.concat([stderr ?? Buffer.alloc(0), stdout ?? Buffer.alloc(0)]).toString("latin1").trim();
123507
+ const code = e && typeof e.code === "number" ? e.code : null;
123508
+ const out = combined || (e && typeof e.code === "string" ? e.code : "");
123509
+ resolve16({ ok: !e, code, out });
123510
+ });
123511
+ });
123512
+ }
123513
+ function ourUnc(port) {
123514
+ return `\\\\127.0.0.1@${port}\\${fileShareToken()}`.toLowerCase();
123515
+ }
123516
+ async function mappedDisks() {
123517
+ const r = await runWin("powershell.exe", [
123518
+ "-NoProfile",
123519
+ "-NonInteractive",
123520
+ "-Command",
123521
+ 'Get-CimInstance Win32_MappedLogicalDisk | ForEach-Object { "$($_.DeviceID)`t$($_.ProviderName)" }'
123522
+ ]);
123523
+ if (!r.ok)
123524
+ return [];
123525
+ const out = [];
123526
+ for (const line of r.out.split(/\r?\n/)) {
123527
+ const tab = line.indexOf(" ");
123528
+ if (tab < 0)
123529
+ continue;
123530
+ const device = line.slice(0, tab).trim();
123531
+ const provider = line.slice(tab + 1).trim();
123532
+ if (/^[A-Za-z]:$/.test(device))
123533
+ out.push({ device, provider });
123534
+ }
123535
+ return out;
123536
+ }
123537
+ async function usedDriveLetters() {
123538
+ const used = /* @__PURE__ */ new Set();
123539
+ const r = await runWin("powershell.exe", [
123540
+ "-NoProfile",
123541
+ "-NonInteractive",
123542
+ "-Command",
123543
+ '(Get-PSDrive -PSProvider FileSystem | Select-Object -ExpandProperty Name) -join ","'
123544
+ ]);
123545
+ if (r.ok)
123546
+ for (const n of r.out.split(",")) {
123547
+ const L = n.trim().toUpperCase();
123548
+ if (/^[A-Z]$/.test(L))
123549
+ used.add(L);
123550
+ }
123551
+ return used;
123552
+ }
123553
+ function pickFreeLetter(used) {
123554
+ for (let c = "Z".charCodeAt(0); c >= "D".charCodeAt(0); c--) {
123555
+ const L = String.fromCharCode(c);
123556
+ if (!used.has(L))
123557
+ return L;
123558
+ }
123559
+ return null;
123560
+ }
122693
123561
  async function mountWindows(log10) {
122694
123562
  const url2 = fileShareUrl();
122695
- if (!url2)
123563
+ const port = fileSharePort();
123564
+ if (!url2 || !port)
122696
123565
  return false;
122697
- try {
122698
- await run3("sc", ["config", "WebClient", "start=", "auto"], OPTS);
122699
- } catch {
122700
- }
122701
- try {
122702
- await run3("net", ["start", "WebClient"], OPTS);
122703
- } catch {
123566
+ const disks = await mappedDisks();
123567
+ const want = ourUnc(port);
123568
+ const tokenLc = fileShareToken().toLowerCase();
123569
+ let reuse = null;
123570
+ for (const d of disks) {
123571
+ const prov = d.provider.toLowerCase();
123572
+ const isOurs2 = prov.startsWith("\\\\127.0.0.1@") && prov.includes(`\\${tokenLc}`);
123573
+ if (!isOurs2)
123574
+ continue;
123575
+ if (prov === want || prov === `${want}\\`) {
123576
+ reuse = d.device;
123577
+ continue;
123578
+ }
123579
+ const del = await runWin("net", ["use", d.device, "/delete", "/y"]);
123580
+ log10(del.ok ? `file-share: removed a stale mapping on ${d.device}` : `file-share: could not remove stale mapping on ${d.device} (${redact(del.out) || `exit ${del.code}`})`);
122704
123581
  }
122705
- try {
122706
- const { stdout } = await run3("net", ["use", "*", url2, "/persistent:no"], OPTS);
122707
- const drive = /([A-Za-z]:)/.exec(stdout)?.[1] ?? null;
122708
- mountedAt = drive ?? url2;
122709
- log10(`file-share: mapped in Explorer \u2192 ${mountedAt}`);
123582
+ if (reuse) {
123583
+ mountedAt = reuse;
123584
+ log10(`file-share: reusing existing mapping on ${reuse}`);
122710
123585
  return true;
122711
- } catch (e) {
122712
- const msg = String(e.message || "").split("\n").find((l) => l.trim()) ?? "";
122713
- log10(`file-share: net use failed (${msg.trim()}) \u2014 is the WebClient service running?`);
123586
+ }
123587
+ const used = await usedDriveLetters();
123588
+ for (const d of disks)
123589
+ used.add(d.device[0].toUpperCase());
123590
+ const letter = pickFreeLetter(used);
123591
+ if (!letter) {
123592
+ log10("file-share: no free drive letter to map the share");
122714
123593
  return false;
122715
123594
  }
123595
+ const res = await runWin("net", ["use", `${letter}:`, url2, "/persistent:no"]);
123596
+ if (res.ok) {
123597
+ mountedAt = `${letter}:`;
123598
+ log10(`file-share: mapped in Explorer \u2192 ${letter}:`);
123599
+ return true;
123600
+ }
123601
+ const detail = redact(res.out).split(/\r?\n/).map((l) => l.trim()).filter(Boolean).join(" ");
123602
+ const sysErr = /System error (\d+)/i.exec(res.out)?.[1] ?? null;
123603
+ const hint = sysErr === "1058" ? " \u2014 the WebClient service is disabled; enable it (services.msc \u2192 WebClient, Manual/Automatic) so `net use` can trigger-start it" : sysErr === "53" || sysErr === "67" || sysErr === "1231" || sysErr === "3" ? " \u2014 WebClient could not be reached; it may be stopped or absent (net use normally trigger-starts it)" : "";
123604
+ log10(`file-share: net use failed (exit ${res.code}${sysErr ? `, System error ${sysErr}` : ""}: ${detail || "no output"})${hint}`);
123605
+ return false;
123606
+ }
123607
+ function scheduleRetry(log10) {
123608
+ if (mounted || retryTimer || attempts >= MAX_ATTEMPTS)
123609
+ return;
123610
+ attempts++;
123611
+ retryTimer = setTimeout(() => {
123612
+ retryTimer = null;
123613
+ void mountFileShare(log10);
123614
+ }, RETRY_MS);
123615
+ retryTimer.unref?.();
122716
123616
  }
122717
123617
  async function mountFileShare(log10 = () => {
122718
123618
  }) {
@@ -122724,18 +123624,34 @@ async function mountFileShare(log10 = () => {
122724
123624
  return false;
122725
123625
  const ok = process.platform === "linux" ? await mountLinux(log10) : process.platform === "win32" ? await mountWindows(log10) : false;
122726
123626
  mounted = ok;
123627
+ if (ok) {
123628
+ attempts = 0;
123629
+ if (retryTimer) {
123630
+ clearTimeout(retryTimer);
123631
+ retryTimer = null;
123632
+ }
123633
+ } else
123634
+ scheduleRetry(log10);
122727
123635
  return ok;
122728
123636
  }
122729
123637
  async function unmountFileShare(log10 = () => {
122730
123638
  }) {
122731
- if (!mounted || !mountedAt)
123639
+ if (retryTimer) {
123640
+ clearTimeout(retryTimer);
123641
+ retryTimer = null;
123642
+ }
123643
+ attempts = 0;
123644
+ if (!mounted || !mountedAt) {
123645
+ mounted = false;
122732
123646
  return;
123647
+ }
123648
+ const at = mountedAt;
122733
123649
  try {
122734
123650
  if (process.platform === "linux")
122735
- await run3("gio", ["mount", "-u", mountedAt], OPTS);
123651
+ await run3("gio", ["mount", "-u", at], OPTS);
122736
123652
  else if (process.platform === "win32")
122737
- await run3("net", ["use", mountedAt, "/delete", "/y"], OPTS);
122738
- log10(`file-share: unmounted ${mountedAt}`);
123653
+ await run3("net", ["use", at, "/delete", "/y"], OPTS);
123654
+ log10(`file-share: unmounted ${redact(at)}`);
122739
123655
  } catch {
122740
123656
  }
122741
123657
  mounted = false;
@@ -123115,9 +124031,10 @@ function writeLauncher(daemonDir3, bridge, runtime) {
123115
124031
  const launcher = join89(daemonDir3, isWin ? "companion-host.cmd" : "companion-host.sh");
123116
124032
  if (isWin) {
123117
124033
  writeFileSync41(launcher, `@echo off\r
124034
+ chcp 65001>nul\r
123118
124035
  set ELECTRON_RUN_AS_NODE=1\r
123119
124036
  "${runtime}" "${bridge}" %*\r
123120
- `);
124037
+ `, { encoding: "utf8" });
123121
124038
  } else {
123122
124039
  writeFileSync41(launcher, `#!/bin/sh
123123
124040
  # Tesseract Companion native-messaging launcher (auto-written by the daemon).
@@ -123170,7 +124087,7 @@ function registerWindows(daemonDir3, manifestJson, log10) {
123170
124087
  writeFileSync41(manifestPath, manifestJson);
123171
124088
  for (const root of ["Software\\Google\\Chrome", "Software\\Microsoft\\Edge", "Software\\BraveSoftware\\Brave-Browser"]) {
123172
124089
  try {
123173
- execFileSync5("reg", ["add", `HKCU\\${root}\\NativeMessagingHosts\\${HOST_NAME}`, "/ve", "/t", "REG_SZ", "/d", manifestPath, "/f"], { stdio: "ignore" });
124090
+ execFileSync5("reg", ["add", `HKCU\\${root}\\NativeMessagingHosts\\${HOST_NAME}`, "/ve", "/t", "REG_SZ", "/d", manifestPath, "/f"], { stdio: "ignore", windowsHide: true });
123174
124091
  } catch {
123175
124092
  }
123176
124093
  }
@@ -123228,6 +124145,37 @@ function loadMacAx2() {
123228
124145
  })();
123229
124146
  return macAxPromise2;
123230
124147
  }
124148
+ var windowingPromise;
124149
+ function loadWindowing() {
124150
+ if (!windowingPromise)
124151
+ windowingPromise = loadLibnutWindowing();
124152
+ return windowingPromise;
124153
+ }
124154
+ function appNameFromWindowTitle(title) {
124155
+ const parts = title.split(/\s+[-—|]\s+/).map((s) => s.trim()).filter(Boolean);
124156
+ return parts.length ? parts[parts.length - 1] : title.trim();
124157
+ }
124158
+ async function detectFrontmost() {
124159
+ if (process.platform === "darwin") {
124160
+ const ax = await loadMacAx2();
124161
+ return ax?.frontmostApp() ?? null;
124162
+ }
124163
+ const win3 = await loadWindowing();
124164
+ if (!win3)
124165
+ return null;
124166
+ try {
124167
+ const handle3 = win3.getActiveWindow();
124168
+ if (!handle3 || handle3 <= 0)
124169
+ return null;
124170
+ const title = (win3.getWindowTitle(handle3) ?? "").trim();
124171
+ if (!title)
124172
+ return null;
124173
+ const name = appNameFromWindowTitle(title);
124174
+ return name ? { name, bundleId: "", pid: handle3 } : null;
124175
+ } catch {
124176
+ return null;
124177
+ }
124178
+ }
123231
124179
  function createActivityTracker(opts = {}) {
123232
124180
  const intervalMs = opts.intervalMs ?? 5e3;
123233
124181
  const flushEvery = opts.flushEvery ?? 12;
@@ -123257,8 +124205,7 @@ function createActivityTracker(opts = {}) {
123257
124205
  }
123258
124206
  async function sample() {
123259
124207
  try {
123260
- const ax = await loadMacAx2();
123261
- const front = ax?.frontmostApp() ?? null;
124208
+ const front = await detectFrontmost();
123262
124209
  if (!front || !front.name)
123263
124210
  return;
123264
124211
  const key2 = front.bundleId || front.name;
@@ -123430,7 +124377,7 @@ function writeJsonId(file2, deviceId3) {
123430
124377
  }
123431
124378
  function haveTool(bin) {
123432
124379
  try {
123433
- execFileSync6(process.platform === "win32" ? "where" : "which", [bin], { timeout: TOOL_TIMEOUT_MS, stdio: "pipe" });
124380
+ execFileSync6(process.platform === "win32" ? "where" : "which", [bin], { timeout: TOOL_TIMEOUT_MS, stdio: "pipe", windowsHide: true });
123434
124381
  return true;
123435
124382
  } catch {
123436
124383
  return false;
@@ -123672,6 +124619,14 @@ function backoff2() {
123672
124619
  // ../../packages/host-core/dist/daemon-runtime.js
123673
124620
  var daemonClipboardSync = null;
123674
124621
  var REFRESH_INTERVAL_MS2 = 12 * 60 * 60 * 1e3;
124622
+ function releaseDaemonLock() {
124623
+ try {
124624
+ const f = join93(daemonDir2(), "daemon.lock");
124625
+ if (existsSync50(f) && Number(readFileSync60(f, "utf-8").trim()) === process.pid)
124626
+ unlinkSync8(f);
124627
+ } catch {
124628
+ }
124629
+ }
123675
124630
  async function runDaemon(opts = {}) {
123676
124631
  const VERSION2 = opts.version ?? process.env.CCB_DAEMON_VERSION ?? "dev";
123677
124632
  const TAG = opts.tag ?? "host-daemon";
@@ -123692,10 +124647,31 @@ async function runDaemon(opts = {}) {
123692
124647
  return false;
123693
124648
  }
123694
124649
  };
124650
+ const readOwnerStatus = () => {
124651
+ try {
124652
+ const s = JSON.parse(readFileSync60(statusFile2(), "utf-8"));
124653
+ const pid = Number(s.pid);
124654
+ if (!Number.isInteger(pid) || pid <= 0 || s.stopped === true)
124655
+ return null;
124656
+ return { pid, startedAt: typeof s.startedAt === "string" ? s.startedAt : void 0 };
124657
+ } catch {
124658
+ return null;
124659
+ }
124660
+ };
124661
+ const lockOwnerIsLive = (pid) => {
124662
+ if (!isAlive2(pid))
124663
+ return false;
124664
+ if (pid === process.pid)
124665
+ return true;
124666
+ const owner = readOwnerStatus();
124667
+ if (owner && owner.pid === pid && owner.startedAt)
124668
+ return pidStillMatches(pid, { startedAt: owner.startedAt });
124669
+ return true;
124670
+ };
123695
124671
  const lockHeldByAnother = () => {
123696
124672
  try {
123697
124673
  const pid = Number(readFileSync60(pidfile2(), "utf-8").trim());
123698
- return Number.isFinite(pid) && pid > 0 && pid !== process.pid && isAlive2(pid);
124674
+ return Number.isFinite(pid) && pid > 0 && pid !== process.pid && lockOwnerIsLive(pid);
123699
124675
  } catch {
123700
124676
  return false;
123701
124677
  }
@@ -123703,7 +124679,7 @@ async function runDaemon(opts = {}) {
123703
124679
  const lockHeldByYieldingSubset = () => {
123704
124680
  try {
123705
124681
  const pid = Number(readFileSync60(pidfile2(), "utf-8").trim());
123706
- if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid || !isAlive2(pid))
124682
+ if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid || !lockOwnerIsLive(pid))
123707
124683
  return false;
123708
124684
  return liveDaemons().some((d) => d.pid === pid && d.rdeskOnly === true);
123709
124685
  } catch {
@@ -123726,7 +124702,7 @@ async function runDaemon(opts = {}) {
123726
124702
  const prev = Number(readFileSync60(f, "utf-8").trim());
123727
124703
  if (!prev)
123728
124704
  return false;
123729
- if (prev !== process.pid && isAlive2(prev))
124705
+ if (prev !== process.pid && lockOwnerIsLive(prev))
123730
124706
  return false;
123731
124707
  unlinkSync8(f);
123732
124708
  } catch {
@@ -123736,14 +124712,7 @@ async function runDaemon(opts = {}) {
123736
124712
  }
123737
124713
  return false;
123738
124714
  }
123739
- function releaseLock() {
123740
- try {
123741
- const f = pidfile2();
123742
- if (existsSync50(f) && Number(readFileSync60(f, "utf-8").trim()) === process.pid)
123743
- unlinkSync8(f);
123744
- } catch {
123745
- }
123746
- }
124715
+ const releaseLock = () => releaseDaemonLock();
123747
124716
  const WE_ARE_RDESK_ONLY = process.env.CCB_RDESK_ONLY === "1" || process.env.CCB_MESH_ONLY === "1";
123748
124717
  function writeStatus2(s) {
123749
124718
  if (!ownsMachine)
@@ -124024,6 +124993,10 @@ async function runDaemon(opts = {}) {
124024
124993
  }
124025
124994
  if (mergedProcess) {
124026
124995
  if (deliberateMergedQuit()) {
124996
+ try {
124997
+ releaseLock();
124998
+ } catch {
124999
+ }
124027
125000
  try {
124028
125001
  log10("APP before-quit \u2014 allowed (merged: deliberate quit)");
124029
125002
  } catch {
@@ -124647,7 +125620,7 @@ async function runDaemon(opts = {}) {
124647
125620
  }
124648
125621
  const holder = lockHolder();
124649
125622
  const fullDaemonAlive = liveDaemons().some((d) => d.pid !== process.pid && d.rdeskOnly !== true);
124650
- if (holder > 0 && isAlive2(holder) || fullDaemonAlive) {
125623
+ if (holder > 0 && lockOwnerIsLive(holder) || fullDaemonAlive) {
124651
125624
  freeTicks = 0;
124652
125625
  return;
124653
125626
  }
@@ -125081,7 +126054,7 @@ import { dirname as dirname43, join as join97 } from "node:path";
125081
126054
  import { promisify as promisify12 } from "node:util";
125082
126055
 
125083
126056
  // ../../packages/host-core/dist/net-service/launchd-system.js
125084
- import { spawn as spawn9 } from "node:child_process";
126057
+ import { spawn as spawn11 } from "node:child_process";
125085
126058
  import { existsSync as existsSync52, mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync46, rmSync as rmSync11, readFileSync as readFileSync61 } from "node:fs";
125086
126059
  import { tmpdir as tmpdir6 } from "node:os";
125087
126060
  import { join as join95 } from "node:path";
@@ -125249,7 +126222,7 @@ ${args}
125249
126222
  }
125250
126223
  function runForCode(cmd, args) {
125251
126224
  return new Promise((resolve16) => {
125252
- const p = spawn9(cmd, args, { stdio: "inherit" });
126225
+ const p = spawn11(cmd, args, { stdio: "inherit" });
125253
126226
  p.on("error", () => resolve16(127));
125254
126227
  p.on("close", (code) => resolve16(code ?? 1));
125255
126228
  });
@@ -125352,16 +126325,16 @@ ${plist}TESSERACT_PLIST_EOF`,
125352
126325
  }
125353
126326
 
125354
126327
  // ../../packages/host-core/dist/net-service/systemd-system.js
125355
- import { spawn as spawn11 } from "node:child_process";
126328
+ import { spawn as spawn13 } from "node:child_process";
125356
126329
  import { existsSync as existsSync53, readFileSync as readFileSync62 } from "node:fs";
125357
126330
 
125358
126331
  // ../../packages/host-core/dist/service/exec.js
125359
- import { execFile as execFile13, spawn as spawn10 } from "node:child_process";
126332
+ import { execFile as execFile13, spawn as spawn12 } from "node:child_process";
125360
126333
  import { promisify as promisify9 } from "node:util";
125361
126334
  var run6 = promisify9(execFile13);
125362
126335
  function runInherit(cmd, args) {
125363
126336
  return new Promise((resolve16) => {
125364
- const p = spawn10(cmd, args, { stdio: "inherit" });
126337
+ const p = spawn12(cmd, args, { stdio: "inherit" });
125365
126338
  p.on("error", () => resolve16());
125366
126339
  p.on("close", () => resolve16());
125367
126340
  });
@@ -125370,7 +126343,7 @@ function runInherit(cmd, args) {
125370
126343
  // ../../packages/host-core/dist/net-service/systemd-system.js
125371
126344
  function runForCode2(cmd, args) {
125372
126345
  return new Promise((resolve16) => {
125373
- const p = spawn11(cmd, args, { stdio: "inherit" });
126346
+ const p = spawn13(cmd, args, { stdio: "inherit" });
125374
126347
  p.on("error", () => resolve16(127));
125375
126348
  p.on("close", (code) => resolve16(code ?? 1));
125376
126349
  });
@@ -125556,7 +126529,7 @@ function sh2(s) {
125556
126529
  }
125557
126530
 
125558
126531
  // ../../packages/host-core/dist/net-service/windows-service.js
125559
- import { execFile as execFile14, spawn as spawn12 } from "node:child_process";
126532
+ import { execFile as execFile14, spawn as spawn14 } from "node:child_process";
125560
126533
  import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync47, rmSync as rmSync12 } from "node:fs";
125561
126534
  import { tmpdir as tmpdir7 } from "node:os";
125562
126535
  import { join as join96, win32 } from "node:path";
@@ -125599,7 +126572,7 @@ async function elevatePowerShell(script) {
125599
126572
  writeFileSync47(inner, script, { encoding: "utf8" });
125600
126573
  if (await alreadyElevated()) {
125601
126574
  return new Promise((resolve16) => {
125602
- const p = spawn12("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", inner], { stdio: "inherit" });
126575
+ const p = spawn14("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", inner], { stdio: "inherit" });
125603
126576
  const finish2 = (code) => {
125604
126577
  try {
125605
126578
  rmSync12(dir2, { recursive: true, force: true });
@@ -125622,7 +126595,7 @@ async function elevatePowerShell(script) {
125622
126595
  "}"
125623
126596
  ].join("\n"), { encoding: "utf8" });
125624
126597
  return new Promise((resolve16) => {
125625
- const p = spawn12("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", outer], { stdio: "inherit" });
126598
+ const p = spawn14("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", outer], { stdio: "inherit" });
125626
126599
  const finish2 = (code) => {
125627
126600
  try {
125628
126601
  rmSync12(dir2, { recursive: true, force: true });
@@ -126746,7 +127719,7 @@ function whoami() {
126746
127719
  }
126747
127720
 
126748
127721
  // src/self-update.ts
126749
- import { spawn as spawn13 } from "node:child_process";
127722
+ import { spawn as spawn15 } from "node:child_process";
126750
127723
  import { createPublicKey, verify as cryptoVerify } from "node:crypto";
126751
127724
  var RESTART_EXIT_CODE = 75;
126752
127725
  var REGISTRY_BASE = "https://registry.npmjs.org/tesseract-agent";
@@ -126821,7 +127794,7 @@ function installVersion(target) {
126821
127794
  log9(`installing tesseract-agent@${target} \u2026`);
126822
127795
  let child;
126823
127796
  try {
126824
- child = spawn13(npm, ["install", "-g", `tesseract-agent@${target}`], { stdio: ["ignore", "inherit", "inherit"] });
127797
+ child = spawn15(npm, ["install", "-g", `tesseract-agent@${target}`], { stdio: ["ignore", "inherit", "inherit"], shell: process.platform === "win32", windowsHide: true });
126825
127798
  } catch (e) {
126826
127799
  log9("npm spawn threw:", e instanceof Error ? e.message : String(e));
126827
127800
  resolve16(false);
@@ -127909,7 +128882,7 @@ async function superviseDaemon(cliEntry) {
127909
128882
  let stopping3 = false;
127910
128883
  let child = null;
127911
128884
  const spawnWorker = () => {
127912
- const c = spawn14(process.execPath, [cliEntry, "__worker"], { stdio: "inherit", env: process.env });
128885
+ const c = spawn16(process.execPath, [cliEntry, "__worker"], { stdio: "inherit", env: process.env });
127913
128886
  child = c;
127914
128887
  let settled = false;
127915
128888
  const settle2 = (fn) => {
@@ -128090,9 +129063,11 @@ async function captureClaudeSubscription(flags) {
128090
129063
  console.log("Open the authorization URL in any browser; paste the returned code here if Claude asks for it.");
128091
129064
  try {
128092
129065
  const code = await new Promise((resolveCode, reject) => {
128093
- const child = spawn14(binary, args, {
129066
+ const child = spawn16(binary, args, {
128094
129067
  env: { ...process.env, CLAUDE_CONFIG_DIR: home },
128095
- stdio: "inherit"
129068
+ stdio: "inherit",
129069
+ // An npm-installed engine CLI is a .cmd shim on Windows — spawn via a shell or Node >=18.20.2 throws EINVAL.
129070
+ shell: process.platform === "win32" && /\.(cmd|bat)$/i.test(binary)
128096
129071
  });
128097
129072
  child.once("error", reject);
128098
129073
  child.once("close", (exitCode, signal2) => resolveCode(signal2 ? 1 : exitCode ?? 1));