tesseract-agent 0.9.5 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist-bundle/cli.js +1795 -400
  2. package/package.json +1 -1
@@ -137,7 +137,7 @@ function sealBasePaths() {
137
137
  claudeConfigDir();
138
138
  return snapshotBasePaths();
139
139
  }
140
- var STARTUP_CWD, USER_HOME, TEMP_DIR, systemDirs, envRoot, peeking;
140
+ var STARTUP_CWD, USER_HOME, TEMP_DIR, systemDirs, envRoot, peeking, dropsDir;
141
141
  var init_base_paths = __esm({
142
142
  "../../packages/shared/dist/base-paths.js"() {
143
143
  "use strict";
@@ -147,6 +147,7 @@ var init_base_paths = __esm({
147
147
  systemDirs = deriveSystemDirs(USER_HOME);
148
148
  envRoot = memoEnvRoot();
149
149
  peeking = false;
150
+ dropsDir = () => join(systemDirs.downloads, "Tesseract");
150
151
  }
151
152
  });
152
153
 
@@ -2399,7 +2400,7 @@ var require_websocket = __commonJS({
2399
2400
  var net = __require("net");
2400
2401
  var tls2 = __require("tls");
2401
2402
  var { randomBytes: randomBytes9, createHash: createHash9 } = __require("crypto");
2402
- var { Duplex, Readable: Readable2 } = __require("stream");
2403
+ var { Duplex, Readable: Readable3 } = __require("stream");
2403
2404
  var { URL: URL2 } = __require("url");
2404
2405
  var PerMessageDeflate2 = require_permessage_deflate();
2405
2406
  var Receiver2 = require_receiver();
@@ -22264,10 +22265,10 @@ ${r?.url ?? ""}`);
22264
22265
  tool3("browser_screenshot", `Capture a PNG screenshot of the visible page (for visual inspection; use browser_snapshot to get actionable elements). ${REAL}`, {}, async () => {
22265
22266
  const r = await drive("screenshot");
22266
22267
  const dataUrl = r?.dataUrl ?? "";
22267
- const b64 = dataUrl.startsWith("data:") ? dataUrl.slice(dataUrl.indexOf(",") + 1) : dataUrl;
22268
- if (!b64)
22268
+ const b642 = dataUrl.startsWith("data:") ? dataUrl.slice(dataUrl.indexOf(",") + 1) : dataUrl;
22269
+ if (!b642)
22269
22270
  return text2("No screenshot data returned.", true);
22270
- return { content: [{ type: "image", data: b64, mimeType: "image/png" }] };
22271
+ return { content: [{ type: "image", data: b642, mimeType: "image/png" }] };
22271
22272
  }),
22272
22273
  tool3("browser_click", `Click an element by CSS selector (from browser_snapshot). ${REAL}`, { selector: external_exports.string().describe("CSS selector of the element to click.") }, async (a) => {
22273
22274
  const r = await drive("click", { selector: a.selector });
@@ -23214,11 +23215,11 @@ function makeApi(deps, toolId, tier) {
23214
23215
  };
23215
23216
  return api;
23216
23217
  }
23217
- function buildToolCtx(deps, sessionId, toolId, log5 = NOOP_LOG, tier = "third-party") {
23218
+ function buildToolCtx(deps, sessionId, toolId, log9 = NOOP_LOG, tier = "third-party") {
23218
23219
  const getSid = typeof sessionId === "function" ? sessionId : () => sessionId;
23219
23220
  return { ui: makeApi(deps, toolId, tier), get sessionId() {
23220
23221
  return getSid();
23221
- }, toolId, log: log5 };
23222
+ }, toolId, log: log9 };
23222
23223
  }
23223
23224
  function wrapCtxTool(tool18, ctx2) {
23224
23225
  const raw = tool18.__ctxHandler;
@@ -26463,9 +26464,9 @@ function sourcesEqual(a, b) {
26463
26464
  if (a.length !== b.length)
26464
26465
  return false;
26465
26466
  const as = [...a].sort();
26466
- const bs3 = [...b].sort();
26467
+ const bs4 = [...b].sort();
26467
26468
  for (let i = 0; i < as.length; i++)
26468
- if (as[i] !== bs3[i])
26469
+ if (as[i] !== bs4[i])
26469
26470
  return false;
26470
26471
  return true;
26471
26472
  }
@@ -36728,9 +36729,9 @@ var require_encoder = __commonJS({
36728
36729
  RGB_YUV_TABLE[i + 1792 >> 0] = -5329 * i;
36729
36730
  }
36730
36731
  }
36731
- function writeBits(bs3) {
36732
- var value = bs3[0];
36733
- var posval = bs3[1] - 1;
36732
+ function writeBits(bs4) {
36733
+ var value = bs4[0];
36734
+ var posval = bs4[1] - 1;
36734
36735
  while (posval >= 0) {
36735
36736
  if (value & 1 << posval) {
36736
36737
  bytenew |= 1 << bytepos;
@@ -58598,12 +58599,12 @@ import { createHash as createHash8 } from "node:crypto";
58598
58599
  import { execFileSync as execFileSync5, spawn as spawn12 } from "node:child_process";
58599
58600
  import { fileURLToPath as fileURLToPath4 } from "node:url";
58600
58601
  import { tmpdir as tmpdir4 } from "node:os";
58601
- import { dirname as dirname25, join as join56, resolve as resolve11 } from "node:path";
58602
+ import { dirname as dirname27, join as join60, resolve as resolve11 } from "node:path";
58602
58603
  import { createInterface } from "node:readline/promises";
58603
58604
 
58604
58605
  // ../../packages/host-core/dist/control-link.js
58605
58606
  import { hostname as hostname3 } from "node:os";
58606
- import { join as join41, basename as basename8 } from "node:path";
58607
+ import { join as join42, basename as basename9 } from "node:path";
58607
58608
  import { mkdirSync as mkdirSync18 } from "node:fs";
58608
58609
 
58609
58610
  // ../../node_modules/ws/wrapper.mjs
@@ -59030,13 +59031,27 @@ var E2ESessions = class {
59030
59031
 
59031
59032
  // ../../packages/e2e/dist/seal.js
59032
59033
  var SEAL_INFO = utf8("tesseract-seal-v1");
59034
+ async function openFromPrivateKey(recipientPrivate, sealed, aad = new Uint8Array()) {
59035
+ const shared = await ecdh(recipientPrivate, unb64u(sealed.epk));
59036
+ const key2 = await importAesKey(await hkdf(shared, new Uint8Array(), SEAL_INFO, 32));
59037
+ return aesOpen(key2, unb64u(sealed.nonce), unb64u(sealed.ct), aad);
59038
+ }
59033
59039
 
59034
59040
  // ../../packages/e2e/dist/attest.js
59035
59041
  var SEALING_ATTEST_CTX = utf8("tesseract-sealing-attest-v1");
59042
+ function attestMessage(sealingPubRaw) {
59043
+ const out = new Uint8Array(SEALING_ATTEST_CTX.length + sealingPubRaw.length);
59044
+ out.set(SEALING_ATTEST_CTX, 0);
59045
+ out.set(sealingPubRaw, SEALING_ATTEST_CTX.length);
59046
+ return out;
59047
+ }
59048
+ async function attestSealingKey(identityPrivate, sealingPubRaw) {
59049
+ return b64u(await sign(identityPrivate, attestMessage(sealingPubRaw)));
59050
+ }
59036
59051
 
59037
59052
  // ../../packages/host-core/dist/daemon-agent.js
59038
59053
  init_base_paths();
59039
- import { join as join29 } from "node:path";
59054
+ import { join as join30 } from "node:path";
59040
59055
 
59041
59056
  // ../../packages/host-core/dist/surface-hub.js
59042
59057
  init_dist();
@@ -60712,7 +60727,7 @@ function convertTool(t) {
60712
60727
  if (!t || typeof t.name !== "string" || typeof t.execute !== "function")
60713
60728
  return null;
60714
60729
  const shape = t.parameters?.shape ?? t.inputSchema?.shape ?? {};
60715
- const exec = t.execute;
60730
+ const exec2 = t.execute;
60716
60731
  return {
60717
60732
  name: t.name,
60718
60733
  description: t.description ?? t.name,
@@ -60720,12 +60735,12 @@ function convertTool(t) {
60720
60735
  // Fallback handler — used when no surface host is wired (off-daemon / no session): the
60721
60736
  // OpenClaw tool still runs with a DEGRADED ctx whose ui methods no-op (open → an
60722
60737
  // already-closed handle), so `ctx.ui.notify(...)` degrades instead of throwing on `{}`.
60723
- handler: async (args) => toCallToolResult(await exec(args, NOOP_UI_CTX)),
60738
+ handler: async (args) => toCallToolResult(await exec2(args, NOOP_UI_CTX)),
60724
60739
  // Ctx-aware path (Pillar 1 P4): agent-manager's wrapCtxTool injects a per-session ctx
60725
60740
  // ({ ui, sessionId, toolId }) here, so the OpenClaw tool's execute(args, ctx) can open
60726
60741
  // and drive surfaces via ctx.ui. Unknown ctx members (channel/secret/log) read as
60727
60742
  // undefined — graceful, matching the Proxy no-op contract.
60728
- __ctxHandler: async (args, ctx2) => toCallToolResult(await exec(args, ctx2))
60743
+ __ctxHandler: async (args, ctx2) => toCallToolResult(await exec2(args, ctx2))
60729
60744
  // Our narrow CallToolResult is a structural subset of the SDK's (which carries an
60730
60745
  // index signature + a richer content union); cast through unknown intentionally.
60731
60746
  };
@@ -62192,12 +62207,17 @@ async function closeEnginePage(browserId) {
62192
62207
  }
62193
62208
  }
62194
62209
 
62210
+ // ../../packages/host-core/dist/gui/windows.js
62211
+ import { randomUUID as randomUUID8 } from "node:crypto";
62212
+ import { join as join20 } from "node:path";
62213
+
62195
62214
  // ../../packages/host-core/dist/gui/daemon-gui.js
62196
62215
  import { join as join19, dirname as dirname8 } from "node:path";
62197
62216
  import { fileURLToPath as fileURLToPath2 } from "node:url";
62198
62217
  import { existsSync as existsSync9 } from "node:fs";
62199
62218
  var el = null;
62200
62219
  var guiReady = false;
62220
+ var log2 = (...a) => console.log("[daemon-gui]", ...a);
62201
62221
  var tray = null;
62202
62222
  function formatAccelerator(accel) {
62203
62223
  const sym = {
@@ -62256,6 +62276,68 @@ function buildTrayMenu(deps) {
62256
62276
  ]);
62257
62277
  }
62258
62278
  var rdeskPowerBlockerId = null;
62279
+ var trayActivity = null;
62280
+ function trayTooltip() {
62281
+ if (process.env.CCB_RDESK_ROLE === "server")
62282
+ return "Tesseract Remote Desktop \u2014 this machine";
62283
+ if (process.env.CCB_RDESK_ROLE === "client")
62284
+ return "Tesseract Remote Desktop";
62285
+ const bits = [];
62286
+ if (rdeskActive > 0)
62287
+ bits.push(`sharing screen (${rdeskActive})`);
62288
+ if (trayActivity)
62289
+ bits.push(trayActivity);
62290
+ return bits.length ? `Tesseract \u2014 ${bits.join(" \xB7 ")}` : "Tesseract";
62291
+ }
62292
+ function setTrayActivity(label) {
62293
+ trayActivity = label;
62294
+ if (!tray)
62295
+ return;
62296
+ try {
62297
+ tray.setToolTip(trayTooltip());
62298
+ } catch {
62299
+ }
62300
+ }
62301
+ function daemonNotify(opts) {
62302
+ if (!guiReady || !el)
62303
+ return false;
62304
+ try {
62305
+ if (!el.Notification.isSupported())
62306
+ return false;
62307
+ const actions = (opts.actions ?? []).slice(0, 3);
62308
+ const n = new el.Notification({
62309
+ title: opts.title,
62310
+ body: opts.body,
62311
+ silent: opts.silent,
62312
+ ...actions.length ? { actions: actions.map((a) => ({ type: "button", text: a.label })) } : {}
62313
+ });
62314
+ if (actions.length && opts.onAction) {
62315
+ n.on("action", (...a) => {
62316
+ const ix = typeof a[1] === "number" ? a[1] : -1;
62317
+ const chosen = actions[ix];
62318
+ if (chosen) {
62319
+ try {
62320
+ opts.onAction(chosen.id);
62321
+ } catch (e) {
62322
+ log2("notify action failed:", e instanceof Error ? e.message : String(e));
62323
+ }
62324
+ }
62325
+ });
62326
+ }
62327
+ if (opts.onClick)
62328
+ n.on("click", () => {
62329
+ try {
62330
+ opts.onClick();
62331
+ } catch {
62332
+ }
62333
+ });
62334
+ n.show();
62335
+ return true;
62336
+ } catch (e) {
62337
+ log2("notify failed:", e instanceof Error ? e.message : String(e));
62338
+ return false;
62339
+ }
62340
+ }
62259
62341
  function setRdeskTraySessions(count, onStopAll) {
62260
62342
  rdeskActive = count;
62261
62343
  rdeskStopAll = onStopAll;
@@ -62277,7 +62359,7 @@ function setRdeskTraySessions(count, onStopAll) {
62277
62359
  if (!tray || !trayDeps)
62278
62360
  return;
62279
62361
  try {
62280
- tray.setToolTip(count > 0 ? `Tesseract \u2014 sharing screen (${count})` : "Tesseract");
62362
+ tray.setToolTip(trayTooltip());
62281
62363
  tray.setContextMenu(buildTrayMenu(trayDeps));
62282
62364
  } catch {
62283
62365
  }
@@ -62352,7 +62434,7 @@ function daemonPreloadPath() {
62352
62434
  // ../../packages/host-core/dist/os-control-runner.js
62353
62435
  import { spawn as spawn6 } from "node:child_process";
62354
62436
  var IS_MAC = process.platform === "darwin";
62355
- var log2 = (...a) => console.log("[os-control]", ...a);
62437
+ var log3 = (...a) => console.log("[os-control]", ...a);
62356
62438
  function capture(command, args, opts = {}) {
62357
62439
  return new Promise((resolve12) => {
62358
62440
  const child = spawn6(command, args, { cwd: opts.cwd, windowsHide: true });
@@ -62499,7 +62581,7 @@ function createDaemonOsControlRunner() {
62499
62581
  if (o.at)
62500
62582
  n.moveMouse(Math.round(o.at.x), Math.round(o.at.y));
62501
62583
  n.mouseClick(NUT_BTN[o.button ?? "left"], o.double === true);
62502
- log2(`${o.button ?? "left"}${o.double ? "-double" : ""}-click${o.at ? ` @ ${Math.round(o.at.x)},${Math.round(o.at.y)}` : ""}`);
62584
+ log3(`${o.button ?? "left"}${o.double ? "-double" : ""}-click${o.at ? ` @ ${Math.round(o.at.x)},${Math.round(o.at.y)}` : ""}`);
62503
62585
  },
62504
62586
  async mouseDrag(from, to, o) {
62505
62587
  const n = await nutOrThrow();
@@ -62561,7 +62643,7 @@ function createDaemonOsControlRunner() {
62561
62643
  async typeText(t) {
62562
62644
  const n = await nutOrThrow();
62563
62645
  n.typeString(t);
62564
- log2(`type "${t.length > 50 ? `${t.slice(0, 50)}\u2026` : t}"`);
62646
+ log3(`type "${t.length > 50 ? `${t.slice(0, 50)}\u2026` : t}"`);
62565
62647
  },
62566
62648
  async pressKeys(combo) {
62567
62649
  const n = await nutOrThrow();
@@ -62569,7 +62651,7 @@ function createDaemonOsControlRunner() {
62569
62651
  if (!key2)
62570
62652
  throw new Error(`Empty key combo: "${combo}"`);
62571
62653
  n.keyTap(key2, modifiers);
62572
- log2(`key ${combo}`);
62654
+ log3(`key ${combo}`);
62573
62655
  },
62574
62656
  // App launch (the one non-input capability the voice path needs — shells `open`)
62575
62657
  async appLaunch(app) {
@@ -62581,6 +62663,83 @@ function createDaemonOsControlRunner() {
62581
62663
  }
62582
62664
 
62583
62665
  // ../../packages/host-core/dist/gui/windows.js
62666
+ function uiFile(name) {
62667
+ return join20(daemonUiDir(), name);
62668
+ }
62669
+ function notReady(what) {
62670
+ throw new Error(`${what} requires the on-screen co-worker (the GUI daemon) \u2014 it isn't active. Enable it (CCB_FORCE_DAEMON_GUI=1) on a signed build.`);
62671
+ }
62672
+ var ipcWired = false;
62673
+ var pendingInput = /* @__PURE__ */ new Map();
62674
+ function wireIpcOnce() {
62675
+ const el2 = electronModule();
62676
+ if (!el2 || ipcWired)
62677
+ return;
62678
+ ipcWired = true;
62679
+ el2.ipcMain.on("input:submit", (_e, payload) => {
62680
+ const p = payload;
62681
+ const resolve12 = pendingInput.get(p.reqId);
62682
+ if (resolve12) {
62683
+ pendingInput.delete(p.reqId);
62684
+ resolve12({ submitted: !!p.submitted, values: p.values ?? {} });
62685
+ }
62686
+ });
62687
+ }
62688
+ function showFormWindow(kind, payload, signal) {
62689
+ const el2 = electronModule();
62690
+ if (!el2)
62691
+ notReady("Asking for input");
62692
+ wireIpcOnce();
62693
+ const reqId = randomUUID8();
62694
+ const win3 = new el2.BrowserWindow({
62695
+ width: 460,
62696
+ height: 320,
62697
+ transparent: true,
62698
+ frame: false,
62699
+ resizable: false,
62700
+ alwaysOnTop: true,
62701
+ skipTaskbar: true,
62702
+ fullscreenable: false,
62703
+ show: false,
62704
+ webPreferences: { preload: daemonPreloadPath(), contextIsolation: true, sandbox: false }
62705
+ });
62706
+ win3.setAlwaysOnTop(true, "screen-saver");
62707
+ win3.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
62708
+ void win3.loadFile(uiFile("input.html"));
62709
+ win3.webContents.on("did-finish-load", () => {
62710
+ win3.webContents.send(`${kind}:show`, { reqId, ...payload });
62711
+ win3.center();
62712
+ win3.show();
62713
+ win3.focus();
62714
+ });
62715
+ return new Promise((resolve12) => {
62716
+ let done = false;
62717
+ const onAbort = () => finish({ submitted: false, values: {} });
62718
+ const finish = (r) => {
62719
+ if (done)
62720
+ return;
62721
+ done = true;
62722
+ signal?.removeEventListener("abort", onAbort);
62723
+ pendingInput.delete(reqId);
62724
+ try {
62725
+ if (!win3.isDestroyed())
62726
+ win3.close();
62727
+ } catch {
62728
+ }
62729
+ resolve12(r);
62730
+ };
62731
+ pendingInput.set(reqId, finish);
62732
+ win3.on("closed", () => finish({ submitted: false, values: {} }));
62733
+ if (signal?.aborted)
62734
+ onAbort();
62735
+ else
62736
+ signal?.addEventListener("abort", onAbort);
62737
+ });
62738
+ }
62739
+ async function confirmAction(spec, opts) {
62740
+ const r = await showFormWindow("confirm", { spec }, opts?.signal);
62741
+ return { confirmed: r.submitted };
62742
+ }
62584
62743
  var spotlightWin = null;
62585
62744
  function widgetDisplayRect() {
62586
62745
  return widgetDisplayBounds(spotlightWin) ?? null;
@@ -62598,15 +62757,15 @@ function widgetDisplayBounds(w) {
62598
62757
  }
62599
62758
 
62600
62759
  // ../../packages/host-core/dist/gui/ai-control.js
62601
- import { join as join20, dirname as dirname9 } from "node:path";
62602
- import { randomUUID as randomUUID8 } from "node:crypto";
62760
+ import { join as join21, dirname as dirname9 } from "node:path";
62761
+ import { randomUUID as randomUUID9 } from "node:crypto";
62603
62762
  init_dist4();
62604
62763
  var OWN_PID = process.pid;
62605
62764
  var runner = createDaemonOsControlRunner();
62606
62765
  var controlAbort = new AbortController();
62607
62766
  var IDLE_RELEASE_MS = 15 * 6e4;
62608
62767
  function uiPath(name) {
62609
- return join20(daemonUiDir(), name);
62768
+ return join21(daemonUiDir(), name);
62610
62769
  }
62611
62770
  var consentWin = null;
62612
62771
  var pendingConsent = /* @__PURE__ */ new Map();
@@ -62616,7 +62775,7 @@ function askRdeskConsent(opts) {
62616
62775
  return Promise.resolve(false);
62617
62776
  const disp = widgetDisplayRect() ?? el2.screen.getPrimaryDisplay().bounds;
62618
62777
  const W = 470, H = 320;
62619
- const win2 = new el2.BrowserWindow({
62778
+ const win3 = new el2.BrowserWindow({
62620
62779
  x: disp.x + Math.round((disp.width - W) / 2),
62621
62780
  y: disp.y + Math.round((disp.height - H) / 3),
62622
62781
  width: W,
@@ -62634,15 +62793,15 @@ function askRdeskConsent(opts) {
62634
62793
  backgroundColor: "#00000000",
62635
62794
  webPreferences: { preload: daemonPreloadPath(), contextIsolation: true, sandbox: false }
62636
62795
  });
62637
- win2.setAlwaysOnTop(true, "screen-saver");
62638
- win2.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
62639
- const reqId = randomUUID8();
62796
+ win3.setAlwaysOnTop(true, "screen-saver");
62797
+ win3.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
62798
+ const reqId = randomUUID9();
62640
62799
  const who = opts.viewerName ? `\u201C${opts.viewerName}\u201D` : "Someone";
62641
- void win2.loadFile(uiPath("consent.html")).then(() => {
62800
+ void win3.loadFile(uiPath("consent.html")).then(() => {
62642
62801
  try {
62643
- win2.webContents.send("consent:show", { reqId, title: "Allow remote desktop access?", body: `${who} wants to view and control this computer.` });
62644
- win2.show();
62645
- win2.focus();
62802
+ win3.webContents.send("consent:show", { reqId, title: "Allow remote desktop access?", body: `${who} wants to view and control this computer.` });
62803
+ win3.show();
62804
+ win3.focus();
62646
62805
  el2.app.focus?.({ steal: true });
62647
62806
  log("rdesk: consent dialog shown");
62648
62807
  } catch (e) {
@@ -62657,16 +62816,16 @@ function askRdeskConsent(opts) {
62657
62816
  done = true;
62658
62817
  pendingConsent.delete(reqId);
62659
62818
  try {
62660
- if (!win2.isDestroyed())
62661
- win2.close();
62819
+ if (!win3.isDestroyed())
62820
+ win3.close();
62662
62821
  } catch {
62663
62822
  }
62664
- if (consentWin === win2)
62823
+ if (consentWin === win3)
62665
62824
  consentWin = null;
62666
62825
  resolve12(r.ok);
62667
62826
  };
62668
62827
  pendingConsent.set(reqId, finish);
62669
- win2.on("closed", () => finish({ ok: false, remember: false }));
62828
+ win3.on("closed", () => finish({ ok: false, remember: false }));
62670
62829
  setTimeout(() => finish({ ok: false, remember: false }), 6e4);
62671
62830
  });
62672
62831
  }
@@ -62676,7 +62835,7 @@ var MOONDREAM_URL = (process.env.CCB_MOONDREAM_URL?.trim() || "http://127.0.0.1:
62676
62835
  // ../../packages/host-core/dist/voice-control.js
62677
62836
  init_base_paths();
62678
62837
  import { existsSync as existsSync10, readFileSync as readFileSync12 } from "node:fs";
62679
- import { basename as basename2, join as join21 } from "node:path";
62838
+ import { basename as basename2, join as join22 } from "node:path";
62680
62839
 
62681
62840
  // ../../packages/voice-core/dist/providers/tool-adapters.js
62682
62841
  function toELClientTool(t) {
@@ -62936,7 +63095,7 @@ async function createElevenLabsAgent(args) {
62936
63095
  }
62937
63096
 
62938
63097
  // ../../packages/voice-core/dist/orchestrator.js
62939
- import { randomUUID as randomUUID9 } from "node:crypto";
63098
+ import { randomUUID as randomUUID10 } from "node:crypto";
62940
63099
 
62941
63100
  // ../../packages/voice-core/dist/summarizer.js
62942
63101
  var TURN_SYSTEM = [
@@ -63294,7 +63453,7 @@ Additional context: ${context}` : task;
63294
63453
  this.emitFocusedState();
63295
63454
  const sessionId = track.sessionId || void 0;
63296
63455
  this.deps.emit({ type: "voice:dispatched", text: message, sessionId });
63297
- const clientRequestId = randomUUID9();
63456
+ const clientRequestId = randomUUID10();
63298
63457
  track.pendingClientRequestId = clientRequestId;
63299
63458
  try {
63300
63459
  const sid = await this.deps.agent.dispatch({ message, clientRequestId, sessionId });
@@ -63327,7 +63486,7 @@ Additional context: ${context}` : task;
63327
63486
  this.emitFocusedState();
63328
63487
  const sessionId = track.sessionId || void 0;
63329
63488
  this.deps.emit({ type: "voice:dispatched", text: message, sessionId });
63330
- const clientRequestId = randomUUID9();
63489
+ const clientRequestId = randomUUID10();
63331
63490
  track.pendingClientRequestId = clientRequestId;
63332
63491
  try {
63333
63492
  const sid = await this.deps.agent.dispatch({ message, clientRequestId, sessionId });
@@ -63350,7 +63509,7 @@ Additional context: ${context}` : task;
63350
63509
  this.voice?.respondToTool(inv.callId, "Got it \u2014 passing that along.");
63351
63510
  this.focusTrack(track);
63352
63511
  this.deps.emit({ type: "voice:dispatched", text: addendum, sessionId: track.sessionId });
63353
- const clientRequestId = randomUUID9();
63512
+ const clientRequestId = randomUUID10();
63354
63513
  track.pendingClientRequestId = clientRequestId;
63355
63514
  try {
63356
63515
  await this.deps.agent.dispatch({ message: addendum, sessionId: track.sessionId, clientRequestId });
@@ -63388,7 +63547,7 @@ Additional context: ${context}` : task;
63388
63547
  this.voice?.respondToTool(inv.callId, "Got it \u2014 passing that along.");
63389
63548
  this.focusTrack(track);
63390
63549
  this.deps.emit({ type: "voice:dispatched", text: answer, sessionId: track.sessionId });
63391
- const clientRequestId = randomUUID9();
63550
+ const clientRequestId = randomUUID10();
63392
63551
  track.pendingClientRequestId = clientRequestId;
63393
63552
  void this.deps.agent.dispatch({ message: answer, sessionId: track.sessionId, clientRequestId }).catch((err2) => this.say(`I couldn't send that: ${errMsg(err2)}.`, { urgent: true }));
63394
63553
  track.state = "working";
@@ -63921,7 +64080,7 @@ async function mintOpenAi(apiKey, model, voice) {
63921
64080
  function readGeminiKeyFile() {
63922
64081
  try {
63923
64082
  const home = tesseractHome();
63924
- const f = join21(home, "daemon", "gemini.key");
64083
+ const f = join22(home, "daemon", "gemini.key");
63925
64084
  if (existsSync10(f)) {
63926
64085
  const k = readFileSync12(f, "utf-8").trim();
63927
64086
  if (k)
@@ -64345,9 +64504,9 @@ import { mkdirSync as mkdirSync14 } from "node:fs";
64345
64504
 
64346
64505
  // ../../packages/host-core/dist/image-gen-runner.js
64347
64506
  init_base_paths();
64348
- import { randomUUID as randomUUID10 } from "node:crypto";
64507
+ import { randomUUID as randomUUID11 } from "node:crypto";
64349
64508
  import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync13 } from "node:fs";
64350
- import { join as join22 } from "node:path";
64509
+ import { join as join23 } from "node:path";
64351
64510
 
64352
64511
  // ../../packages/image-edit-core/dist/transparent.js
64353
64512
  var import_fast_png = __toESM(require_lib(), 1);
@@ -64552,8 +64711,8 @@ var ImageEditError = class extends Error {
64552
64711
  function toBase64(bytes2) {
64553
64712
  return Buffer.from(bytes2).toString("base64");
64554
64713
  }
64555
- function fromBase64(b64) {
64556
- return new Uint8Array(Buffer.from(b64, "base64"));
64714
+ function fromBase64(b642) {
64715
+ return new Uint8Array(Buffer.from(b642, "base64"));
64557
64716
  }
64558
64717
  function toBlob(img) {
64559
64718
  return new Blob([img.bytes], { type: img.mime });
@@ -64694,7 +64853,7 @@ var OpenAiImageProvider = class {
64694
64853
  if (!res.ok) {
64695
64854
  throw new ImageEditError(json2.error?.message ?? `OpenAI request failed (${res.status})`, this.id, res.status);
64696
64855
  }
64697
- const out = (json2.data ?? []).map((d) => d.b64_json).filter((b) => !!b).map((b64) => ({ bytes: fromBase64(b64), mime: "image/png" }));
64856
+ const out = (json2.data ?? []).map((d) => d.b64_json).filter((b) => !!b).map((b642) => ({ bytes: fromBase64(b642), mime: "image/png" }));
64698
64857
  if (!out.length)
64699
64858
  throw new ImageEditError("OpenAI returned no image", this.id, res.status);
64700
64859
  return out;
@@ -64723,10 +64882,10 @@ var OpenAiImageProvider = class {
64723
64882
  if (!res.ok) {
64724
64883
  throw new ImageEditError(json2.error?.message ?? `OpenAI request failed (${res.status})`, this.id, res.status);
64725
64884
  }
64726
- const b64 = json2.data?.[0]?.b64_json;
64727
- if (!b64)
64885
+ const b642 = json2.data?.[0]?.b64_json;
64886
+ if (!b642)
64728
64887
  throw new ImageEditError("OpenAI returned no image", this.id, res.status);
64729
- return { bytes: fromBase64(b64), mime: "image/png" };
64888
+ return { bytes: fromBase64(b642), mime: "image/png" };
64730
64889
  }
64731
64890
  };
64732
64891
 
@@ -64802,7 +64961,7 @@ function createImageEditProvider(id, cfg2) {
64802
64961
  // ../../packages/host-core/dist/image-gen-runner.js
64803
64962
  function generatedImagesDir() {
64804
64963
  const home = tesseractHome();
64805
- return join22(home, "generated-images");
64964
+ return join23(home, "generated-images");
64806
64965
  }
64807
64966
  function creds() {
64808
64967
  return readDaemonAgentSettings().imageGen;
@@ -64854,11 +65013,11 @@ function createDaemonImageGenRunner() {
64854
65013
  });
64855
65014
  if (!images.length)
64856
65015
  return { ok: false, error: "The provider returned no image." };
64857
- const dir2 = req.outDir ?? join22(generatedImagesDir(), randomUUID10());
65016
+ const dir2 = req.outDir ?? join23(generatedImagesDir(), randomUUID11());
64858
65017
  mkdirSync9(dir2, { recursive: true });
64859
65018
  const files = images.map((img, i) => {
64860
65019
  const ext = img.mime.split("/")[1]?.split("+")[0] || "png";
64861
- const path6 = join22(dir2, `${i + 1}.${ext}`);
65020
+ const path6 = join23(dir2, `${i + 1}.${ext}`);
64862
65021
  writeFileSync10(path6, img.bytes);
64863
65022
  return { path: path6, mime: img.mime };
64864
65023
  });
@@ -64908,8 +65067,8 @@ var VideoGenError = class extends Error {
64908
65067
  function toBase642(bytes2) {
64909
65068
  return Buffer.from(bytes2).toString("base64");
64910
65069
  }
64911
- function fromBase642(b64) {
64912
- return new Uint8Array(Buffer.from(b64, "base64"));
65070
+ function fromBase642(b642) {
65071
+ return new Uint8Array(Buffer.from(b642, "base64"));
64913
65072
  }
64914
65073
 
64915
65074
  // ../../packages/video-core/dist/gemini.js
@@ -66270,11 +66429,11 @@ function createVideoProvider(id, cfg2) {
66270
66429
  init_base_paths();
66271
66430
  import { mkdirSync as mkdirSync10, readdirSync as readdirSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync11, rmSync as rmSync3 } from "node:fs";
66272
66431
  import { mkdir as mkdir5, writeFile as writeFile6 } from "node:fs/promises";
66273
- import { dirname as dirname10, join as join23 } from "node:path";
66274
- import { randomUUID as randomUUID11 } from "node:crypto";
66432
+ import { dirname as dirname10, join as join24 } from "node:path";
66433
+ import { randomUUID as randomUUID12 } from "node:crypto";
66275
66434
  function mediaJobsDir() {
66276
66435
  const home = tesseractHome();
66277
- return join23(home, "media-jobs");
66436
+ return join24(home, "media-jobs");
66278
66437
  }
66279
66438
  var TERMINAL = /* @__PURE__ */ new Set(["succeeded", "failed", "canceled"]);
66280
66439
  var POLL_MS2 = 1e4;
@@ -66291,8 +66450,8 @@ var VideoJobRegistry = class {
66291
66450
  /** Submit a spec and start tracking it. Returns the persisted record. */
66292
66451
  async submit(provider, spec, meta4) {
66293
66452
  const handle3 = await provider.submit(spec);
66294
- const id = randomUUID11();
66295
- const outDir = meta4.outDir ?? join23(mediaJobsDir(), id);
66453
+ const id = randomUUID12();
66454
+ const outDir = meta4.outDir ?? join24(mediaJobsDir(), id);
66296
66455
  const job = {
66297
66456
  id,
66298
66457
  sessionId: meta4.sessionId,
@@ -66305,7 +66464,7 @@ var VideoJobRegistry = class {
66305
66464
  resolution: spec.resolution ?? "720p",
66306
66465
  estimateUsd: meta4.estimateUsd,
66307
66466
  state: "running",
66308
- outPath: join23(outDir, "video.mp4"),
66467
+ outPath: join24(outDir, "video.mp4"),
66309
66468
  createdAt: Date.now(),
66310
66469
  updatedAt: Date.now()
66311
66470
  };
@@ -66319,7 +66478,7 @@ var VideoJobRegistry = class {
66319
66478
  if (mem)
66320
66479
  return { ...mem };
66321
66480
  try {
66322
- const raw = readFileSync14(join23(mediaJobsDir(), `${jobId}.json`), "utf-8");
66481
+ const raw = readFileSync14(join24(mediaJobsDir(), `${jobId}.json`), "utf-8");
66323
66482
  return JSON.parse(raw);
66324
66483
  } catch {
66325
66484
  return null;
@@ -66369,7 +66528,7 @@ var VideoJobRegistry = class {
66369
66528
  }
66370
66529
  const now2 = Date.now();
66371
66530
  for (const f of files) {
66372
- const fp = join23(mediaJobsDir(), f);
66531
+ const fp = join24(mediaJobsDir(), f);
66373
66532
  try {
66374
66533
  const job = JSON.parse(readFileSync14(fp, "utf-8"));
66375
66534
  if (TERMINAL.has(job.state)) {
@@ -66492,7 +66651,7 @@ var VideoJobRegistry = class {
66492
66651
  this.jobs.set(job.id, job);
66493
66652
  try {
66494
66653
  mkdirSync10(mediaJobsDir(), { recursive: true });
66495
- writeFileSync11(join23(mediaJobsDir(), `${job.id}.json`), JSON.stringify(job, null, 2));
66654
+ writeFileSync11(join24(mediaJobsDir(), `${job.id}.json`), JSON.stringify(job, null, 2));
66496
66655
  } catch {
66497
66656
  }
66498
66657
  }
@@ -66646,15 +66805,15 @@ function createDaemonVideoGenRunner() {
66646
66805
  init_base_paths();
66647
66806
  import { existsSync as existsSync13, readFileSync as readFileSync20 } from "node:fs";
66648
66807
  import { mkdir as mkdir6, writeFile as writeFile7 } from "node:fs/promises";
66649
- import { join as join27, basename as basename4 } from "node:path";
66650
- import { randomUUID as randomUUID13 } from "node:crypto";
66808
+ import { join as join28, basename as basename4 } from "node:path";
66809
+ import { randomUUID as randomUUID14 } from "node:crypto";
66651
66810
 
66652
66811
  // ../../packages/host-core/dist/video-edit.js
66653
66812
  init_base_paths();
66654
66813
  import { spawn as spawn7 } from "node:child_process";
66655
66814
  import { mkdirSync as mkdirSync11, readFileSync as readFileSync16, rmSync as rmSync4, writeFileSync as writeFileSync12 } from "node:fs";
66656
- import { join as join24, isAbsolute as isAbsolute4 } from "node:path";
66657
- import { randomUUID as randomUUID12 } from "node:crypto";
66815
+ import { join as join25, isAbsolute as isAbsolute4 } from "node:path";
66816
+ import { randomUUID as randomUUID13 } from "node:crypto";
66658
66817
 
66659
66818
  // ../../packages/host-core/dist/ffmpeg.js
66660
66819
  import { existsSync as existsSync11 } from "node:fs";
@@ -66718,9 +66877,9 @@ function requireAbsolutePath(p, label = "path") {
66718
66877
  const raw = typeof p === "string" ? p.trim() : "";
66719
66878
  if (!raw)
66720
66879
  throw new Error(`${label} is required.`);
66721
- const expanded = raw === "~" ? userHome() : raw.startsWith("~/") || raw.startsWith("~\\") ? join24(userHome(), raw.slice(2)) : raw;
66880
+ const expanded = raw === "~" ? userHome() : raw.startsWith("~/") || raw.startsWith("~\\") ? join25(userHome(), raw.slice(2)) : raw;
66722
66881
  if (!isAbsolute4(expanded)) {
66723
- throw new Error(`${label} must be an ABSOLUTE path (got "${raw}"). A relative path would be resolved against the background service's own directory, not your project, so it could silently act on the wrong file. Pass the full path, e.g. ${join24(userHome(), "Movies", "clip.mp4")}.`);
66882
+ throw new Error(`${label} must be an ABSOLUTE path (got "${raw}"). A relative path would be resolved against the background service's own directory, not your project, so it could silently act on the wrong file. Pass the full path, e.g. ${join25(userHome(), "Movies", "clip.mp4")}.`);
66724
66883
  }
66725
66884
  return expanded;
66726
66885
  }
@@ -66734,7 +66893,7 @@ function mediaSpawnCwd() {
66734
66893
  }
66735
66894
  var workDir = () => {
66736
66895
  const home = tesseractHome();
66737
- const dir2 = join24(home, "media-work", randomUUID12());
66896
+ const dir2 = join25(home, "media-work", randomUUID13());
66738
66897
  mkdirSync11(dir2, { recursive: true });
66739
66898
  return dir2;
66740
66899
  };
@@ -66826,10 +66985,10 @@ async function renderEdl(edl, opts) {
66826
66985
  const dir2 = opts.outDir ?? workDir();
66827
66986
  mkdirSync11(dir2, { recursive: true });
66828
66987
  const format = edl.output?.format ?? "mp4";
66829
- const outPath = join24(dir2, `edit.${format}`);
66988
+ const outPath = join25(dir2, `edit.${format}`);
66830
66989
  let assFile;
66831
66990
  if (edl.captions?.cues.length) {
66832
- assFile = join24(dir2, "captions.ass");
66991
+ assFile = join25(dir2, "captions.ass");
66833
66992
  const W = edl.output?.width ?? 1920;
66834
66993
  const H = edl.output?.height ?? 1080;
66835
66994
  writeFileSync12(assFile, buildAssSubtitles(edl.captions.cues, edl.captions.style ?? "tiktok-bold", W, H));
@@ -66899,7 +67058,7 @@ async function enhanceVideo(path6, spec, opts) {
66899
67058
  }
66900
67059
  const dir2 = opts?.outDir ?? workDir();
66901
67060
  mkdirSync11(dir2, { recursive: true });
66902
- const outPath = join24(dir2, "enhanced.mp4");
67061
+ const outPath = join25(dir2, "enhanced.mp4");
66903
67062
  const audioArgs = probe.hasAudio ? ["-c:a", "copy"] : ["-an"];
66904
67063
  const args = [
66905
67064
  "-i",
@@ -66979,7 +67138,7 @@ async function extractFrames(path6, timestamps, outDir) {
66979
67138
  mkdirSync11(dir2, { recursive: true });
66980
67139
  const out = [];
66981
67140
  for (const [i, ts] of timestamps.slice(0, 20).entries()) {
66982
- const file2 = join24(dir2, `frame-${i + 1}-${Math.round(ts * 1e3)}ms.jpg`);
67141
+ const file2 = join25(dir2, `frame-${i + 1}-${Math.round(ts * 1e3)}ms.jpg`);
66983
67142
  const r = await run(resolveFfmpeg(), ["-y", "-ss", String(Math.max(0, ts)), "-i", path6, "-frames:v", "1", "-q:v", "2", file2]);
66984
67143
  if (!r.ok)
66985
67144
  throw new Error(`Frame extraction at ${ts}s failed: ${r.stderr.slice(-300)}`);
@@ -67012,7 +67171,7 @@ async function extractAudio(path6, format, outDir, opts) {
67012
67171
  path6 = requireAbsolutePath(path6, "path");
67013
67172
  const dir2 = outDir ?? workDir();
67014
67173
  mkdirSync11(dir2, { recursive: true });
67015
- const out = join24(dir2, `audio.${format}`);
67174
+ const out = join25(dir2, `audio.${format}`);
67016
67175
  const codec2 = format === "mp3" ? ["-c:a", "libmp3lame", "-q:a", "2"] : format === "wav" ? ["-c:a", "pcm_s16le"] : ["-c:a", "aac", "-b:a", "192k"];
67017
67176
  const gain = opts?.gainDb ? ["-af", `volume=${opts.gainDb}dB`] : [];
67018
67177
  const r = await run(resolveFfmpeg(), ["-y", "-i", path6, "-vn", ...gain, ...codec2, out], { timeoutMs: LONG_TIMEOUT_MS });
@@ -67028,7 +67187,7 @@ async function isolateSpeakerAudio(path6, windows2, outDir) {
67028
67187
  const enable = merged.map((w) => `between(t,${w.startSec},${w.endSec})`).join("+");
67029
67188
  const dir2 = outDir ?? workDir();
67030
67189
  mkdirSync11(dir2, { recursive: true });
67031
- const out = join24(dir2, "speaker.m4a");
67190
+ const out = join25(dir2, "speaker.m4a");
67032
67191
  const r = await run(resolveFfmpeg(), ["-y", "-i", path6, "-vn", "-af", `volume=enable='1-min(1,${enable})':volume=0`, "-c:a", "aac", "-b:a", "192k", out], {
67033
67192
  timeoutMs: LONG_TIMEOUT_MS
67034
67193
  });
@@ -67059,7 +67218,7 @@ async function extractAudioChunks(path6, opts) {
67059
67218
  const chunks = [];
67060
67219
  try {
67061
67220
  for (let start3 = 0, i = 0; start3 < total; start3 += chunkSec, i++) {
67062
- const out = join24(chunkDir, `chunk-${i}.m4a`);
67221
+ const out = join25(chunkDir, `chunk-${i}.m4a`);
67063
67222
  const len = Math.min(chunkSec + overlapSec, total - start3);
67064
67223
  const r = await run(resolveFfmpeg(), ["-y", "-ss", String(start3), "-t", String(len), "-i", path6, "-vn", "-c:a", "aac", "-b:a", "64k", "-ac", "1", out], {
67065
67224
  timeoutMs: LONG_TIMEOUT_MS
@@ -67088,7 +67247,7 @@ function readChunkBase64(path6) {
67088
67247
  init_base_paths();
67089
67248
  import { spawn as spawn8 } from "node:child_process";
67090
67249
  import { existsSync as existsSync12, mkdirSync as mkdirSync12, readFileSync as readFileSync17, readdirSync as readdirSync5 } from "node:fs";
67091
- import { join as join25, dirname as dirname11 } from "node:path";
67250
+ import { join as join26, dirname as dirname11 } from "node:path";
67092
67251
  var upscaleCwd;
67093
67252
  function upscaleSpawnCwd() {
67094
67253
  if (!upscaleCwd) {
@@ -67104,12 +67263,12 @@ function resolveRealesrgan() {
67104
67263
  const candidates = [
67105
67264
  envBin,
67106
67265
  // Packaged app: extraResources → Resources/vendor/realesrgan/<plat>/…
67107
- process.resourcesPath ? join25(process.resourcesPath, "vendor", "realesrgan", platDir, exe) : void 0
67266
+ process.resourcesPath ? join26(process.resourcesPath, "vendor", "realesrgan", platDir, exe) : void 0
67108
67267
  ].filter((p) => !!p && existsSync12(p));
67109
67268
  const bin = candidates[0] ?? fromPath2("realesrgan-ncnn-vulkan");
67110
67269
  if (!bin)
67111
67270
  return null;
67112
- const models = process.env.TESSERACT_REALESRGAN_MODELS?.trim() || join25(dirname11(bin), "models");
67271
+ const models = process.env.TESSERACT_REALESRGAN_MODELS?.trim() || join26(dirname11(bin), "models");
67113
67272
  return existsSync12(models) ? { bin, models } : null;
67114
67273
  }
67115
67274
  function fromPath2(cmd) {
@@ -67132,8 +67291,8 @@ function installedRealesrModels() {
67132
67291
  return [];
67133
67292
  return REALESRGAN_MODELS.filter((m) => {
67134
67293
  if (m.scales.length > 1)
67135
- return m.scales.some((s) => existsSync12(join25(re.models, `${m.id}-x${s}.bin`)));
67136
- return existsSync12(join25(re.models, `${m.id}.bin`));
67294
+ return m.scales.some((s) => existsSync12(join26(re.models, `${m.id}-x${s}.bin`)));
67295
+ return existsSync12(join26(re.models, `${m.id}.bin`));
67137
67296
  }).map((m) => m.id);
67138
67297
  }
67139
67298
  function upscaleAdvert() {
@@ -67152,7 +67311,7 @@ async function realesrganGpuWorks() {
67152
67311
  }
67153
67312
  try {
67154
67313
  const dir2 = workDir();
67155
- const inP = join25(dir2, "p.png"), outP = join25(dir2, "p2.png");
67314
+ const inP = join26(dir2, "p.png"), outP = join26(dir2, "p2.png");
67156
67315
  const gen = await run(resolveFfmpeg(), ["-y", "-f", "lavfi", "-i", "testsrc=size=64x64:duration=1", "-frames:v", "1", inP], { timeoutMs: 15e3 });
67157
67316
  if (!gen.ok || !existsSync12(inP)) {
67158
67317
  gpuProbe = false;
@@ -67191,7 +67350,7 @@ async function upscaleCapabilities(opts) {
67191
67350
  }
67192
67351
  function resolveFalKey() {
67193
67352
  const home = tesseractHome();
67194
- const file2 = join25(home, "daemon", "fal.key");
67353
+ const file2 = join26(home, "daemon", "fal.key");
67195
67354
  if (existsSync12(file2)) {
67196
67355
  const k = readFileSync17(file2, "utf-8").trim();
67197
67356
  if (k)
@@ -67239,19 +67398,19 @@ async function realesrganUpscale(path6, opts) {
67239
67398
  const model = opts.model ?? "realesr-animevideov3";
67240
67399
  const scale = opts.scale ?? 2;
67241
67400
  const dir2 = opts.outDir ?? workDir();
67242
- const fin = join25(dir2, "in"), fup = join25(dir2, "up");
67401
+ const fin = join26(dir2, "in"), fup = join26(dir2, "up");
67243
67402
  mkdirSync12(fin, { recursive: true });
67244
67403
  mkdirSync12(fup, { recursive: true });
67245
67404
  const fps = p.fps ?? 30;
67246
67405
  const src = path6;
67247
67406
  let extractSrc = src;
67248
67407
  if (opts.restore && Object.keys(opts.restore).length) {
67249
- const cleaned = await enhanceVideo(src, { ...opts.restore, sharpen: opts.restore.sharpen ?? "none" }, { outDir: join25(dir2, "restore") }).catch(() => null);
67408
+ const cleaned = await enhanceVideo(src, { ...opts.restore, sharpen: opts.restore.sharpen ?? "none" }, { outDir: join26(dir2, "restore") }).catch(() => null);
67250
67409
  if (cleaned)
67251
67410
  extractSrc = cleaned.path;
67252
67411
  }
67253
67412
  opts.onProgress?.(2, "Extracting frames\u2026");
67254
- const ext = await run(resolveFfmpeg(), ["-i", extractSrc, "-fps_mode", "passthrough", join25(fin, "f%08d.png")], { timeoutMs: LONG });
67413
+ const ext = await run(resolveFfmpeg(), ["-i", extractSrc, "-fps_mode", "passthrough", join26(fin, "f%08d.png")], { timeoutMs: LONG });
67255
67414
  if (!ext.ok)
67256
67415
  throw new Error(`Frame extraction failed: ${ext.stderr.slice(-300)}`);
67257
67416
  const nFrames = readdirSync5(fin).filter((f) => f.endsWith(".png")).length;
@@ -67277,14 +67436,14 @@ async function realesrganUpscale(path6, opts) {
67277
67436
  });
67278
67437
  });
67279
67438
  opts.onProgress?.(96, "Reassembling\u2026");
67280
- const outPath = join25(dir2, "enhanced.mp4");
67439
+ const outPath = join26(dir2, "enhanced.mp4");
67281
67440
  const vf = opts.targetHeight ? `scale=w=-2:h=${Math.round(opts.targetHeight / 2) * 2}:flags=lanczos` : "null";
67282
67441
  const audio = p.hasAudio ? ["-i", src, "-map", "0:v:0", "-map", "1:a:0?", "-c:a", "copy"] : [];
67283
67442
  const asm = await run(resolveFfmpeg(), [
67284
67443
  "-r",
67285
67444
  String(fps),
67286
67445
  "-i",
67287
- join25(fup, "f%08d.png"),
67446
+ join26(fup, "f%08d.png"),
67288
67447
  ...audio,
67289
67448
  "-vf",
67290
67449
  vf,
@@ -67336,7 +67495,7 @@ async function falTopazEnhance(path6, spec, opts) {
67336
67495
  throw new Error(`Downloading the fal result failed (HTTP ${dl.status}).`);
67337
67496
  const dir2 = opts.outDir ?? workDir();
67338
67497
  mkdirSync12(dir2, { recursive: true });
67339
- const outPath = join25(dir2, "enhanced.mp4");
67498
+ const outPath = join26(dir2, "enhanced.mp4");
67340
67499
  const { writeFileSync: writeFileSync29 } = await import("node:fs");
67341
67500
  writeFileSync29(outPath, Buffer.from(await dl.arrayBuffer()));
67342
67501
  const out = await probeMedia(outPath).catch(() => null);
@@ -67382,7 +67541,7 @@ async function smartEnhance(path6, args) {
67382
67541
 
67383
67542
  // ../../packages/host-core/dist/video-effects.js
67384
67543
  import { readFileSync as readFileSync18, mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "node:fs";
67385
- import { join as join26, basename as basename3, extname } from "node:path";
67544
+ import { join as join27, basename as basename3, extname } from "node:path";
67386
67545
  var IMG_MIME = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", heic: "image/heic" };
67387
67546
  var LONG2 = 6e5;
67388
67547
  function listEffects() {
@@ -67469,7 +67628,7 @@ async function applyEffect(effectId, inputs, opts) {
67469
67628
  const dir2 = opts?.outDir ?? workDir();
67470
67629
  mkdirSync13(dir2, { recursive: true });
67471
67630
  const ext = media.isVideo ? "mp4" : extname(new URL(media.url).pathname).slice(1) || "png";
67472
- const outPath = join26(dir2, `${effectId}.${ext}`);
67631
+ const outPath = join27(dir2, `${effectId}.${ext}`);
67473
67632
  writeFileSync13(outPath, Buffer.from(await dl.arrayBuffer()));
67474
67633
  opts?.onProgress?.(100, "Done");
67475
67634
  return { path: outPath, mime: media.isVideo ? "video/mp4" : IMG_MIME[ext] ?? "image/png", isVideo: media.isVideo, effectId, label: def.label };
@@ -67495,7 +67654,7 @@ ${inputs.prompt}` : "");
67495
67654
  const out = await runImageEdit(provider, { op: "prompt", imageB64: readFileSync18(imgPath).toString("base64"), mime: IMG_MIME[ext] ?? "image/jpeg", prompt });
67496
67655
  const dir2 = opts?.outDir ?? workDir();
67497
67656
  mkdirSync13(dir2, { recursive: true });
67498
- const outPath = join26(dir2, `${def.id}.png`);
67657
+ const outPath = join27(dir2, `${def.id}.png`);
67499
67658
  writeFileSync13(outPath, Buffer.from(out.bytes));
67500
67659
  opts?.onProgress?.(100, "Done");
67501
67660
  return { path: outPath, mime: out.mime || "image/png", isVideo: false, effectId: def.id, label: def.label };
@@ -67620,13 +67779,13 @@ var MODEL = "gemini-3.5-flash";
67620
67779
  var TIMEOUT_MS = 6e4;
67621
67780
  async function reviewFrameGemini(framePath, intent, apiKey) {
67622
67781
  try {
67623
- const b64 = readFileSync19(framePath).toString("base64");
67782
+ const b642 = readFileSync19(framePath).toString("base64");
67624
67783
  const prompt = `This is a frame from an AI-generated video clip meant to show: "${intent}". Judge ONLY for obvious defects: melted/warped faces or hands, garbled text, broken anatomy, severe artifacts. Reply with STRICT JSON: {"acceptable": <true|false>, "note": "<\u226412 words, only if not acceptable>"}.`;
67625
67784
  const res = await fetch(`${BASE_URL4}/models/${MODEL}:generateContent`, {
67626
67785
  method: "POST",
67627
67786
  headers: { "Content-Type": "application/json", "x-goog-api-key": apiKey },
67628
67787
  body: JSON.stringify({
67629
- contents: [{ role: "user", parts: [{ text: prompt }, { inlineData: { mimeType: "image/jpeg", data: b64 } }] }],
67788
+ contents: [{ role: "user", parts: [{ text: prompt }, { inlineData: { mimeType: "image/jpeg", data: b642 } }] }],
67630
67789
  generationConfig: { temperature: 0, responseMimeType: "application/json" }
67631
67790
  }),
67632
67791
  signal: AbortSignal.timeout(TIMEOUT_MS)
@@ -67656,7 +67815,7 @@ function heygenKey() {
67656
67815
  function readDaemonKeyFile(name) {
67657
67816
  try {
67658
67817
  const home = tesseractHome();
67659
- const f = join27(home, "daemon", name);
67818
+ const f = join28(home, "daemon", name);
67660
67819
  if (existsSync13(f)) {
67661
67820
  const k = readFileSync20(f, "utf-8").trim();
67662
67821
  if (k)
@@ -67670,7 +67829,7 @@ var HEYGEN_POLL_MS = 8e3;
67670
67829
  var HEYGEN_MAX_MS = 8 * 6e4;
67671
67830
  function musicDir() {
67672
67831
  const home = tesseractHome();
67673
- return join27(home, "generated-music");
67832
+ return join28(home, "generated-music");
67674
67833
  }
67675
67834
  function audioMimeForPath(p) {
67676
67835
  const lower = p.toLowerCase();
@@ -67786,10 +67945,10 @@ function createDaemonVideoEditRunner() {
67786
67945
  return { ok: false, error: "Music generation needs an ElevenLabs key \u2014 ask the user to add one in Settings \u2192 Voice." };
67787
67946
  try {
67788
67947
  const music = await generateMusicElevenLabs({ prompt, durationSec }, { apiKey });
67789
- const dir2 = outDir ?? join27(musicDir(), randomUUID13());
67948
+ const dir2 = outDir ?? join28(musicDir(), randomUUID14());
67790
67949
  await mkdir6(dir2, { recursive: true });
67791
67950
  const ext = music.mime.includes("wav") ? "wav" : music.mime.includes("mp4") ? "m4a" : "mp3";
67792
- const path6 = join27(dir2, `music.${ext}`);
67951
+ const path6 = join28(dir2, `music.${ext}`);
67793
67952
  await writeFile7(path6, music.bytes);
67794
67953
  return { ok: true, file: { path: path6, mime: music.mime } };
67795
67954
  } catch (e) {
@@ -67857,9 +68016,9 @@ function createDaemonVideoEditRunner() {
67857
68016
  if (!poll.videoUrl)
67858
68017
  return { ok: false, error: "HeyGen reported the render complete but returned no video URL." };
67859
68018
  const dl = await downloadHeyGenVideo(poll.videoUrl);
67860
- const dir2 = outDir ?? join27(musicDir(), randomUUID13());
68019
+ const dir2 = outDir ?? join28(musicDir(), randomUUID14());
67861
68020
  await mkdir6(dir2, { recursive: true });
67862
- const path6 = join27(dir2, "avatar.mp4");
68021
+ const path6 = join28(dir2, "avatar.mp4");
67863
68022
  await writeFile7(path6, dl.bytes);
67864
68023
  return { ok: true, file: { path: path6, mime: dl.mime } };
67865
68024
  }
@@ -67877,9 +68036,9 @@ function createDaemonVideoEditRunner() {
67877
68036
  return { ok: false, error: "Text-to-speech needs an ElevenLabs key (Settings \u2192 Voice)." };
67878
68037
  try {
67879
68038
  const audio = await synthesizeSpeechElevenLabs(text8, voiceId, { apiKey, stability: opts?.stability, similarityBoost: opts?.similarityBoost, speed: opts?.speed });
67880
- const dir2 = outDir ?? join27(musicDir(), randomUUID13());
68039
+ const dir2 = outDir ?? join28(musicDir(), randomUUID14());
67881
68040
  await mkdir6(dir2, { recursive: true });
67882
- const path6 = join27(dir2, "speech.mp3");
68041
+ const path6 = join28(dir2, "speech.mp3");
67883
68042
  await writeFile7(path6, audio.bytes);
67884
68043
  return { ok: true, file: { path: path6, mime: audio.mime } };
67885
68044
  } catch (e) {
@@ -67913,7 +68072,7 @@ function createDaemonVideoEditRunner() {
67913
68072
 
67914
68073
  // ../../packages/host-core/dist/support-submit.js
67915
68074
  import { openSync as openSync3, fstatSync, readSync as readSync2, closeSync as closeSync3 } from "node:fs";
67916
- import { join as join28 } from "node:path";
68075
+ import { join as join29 } from "node:path";
67917
68076
 
67918
68077
  // ../../packages/host-core/dist/host-metrics.js
67919
68078
  import os from "node:os";
@@ -67976,7 +68135,7 @@ function createDaemonSupportSubmit() {
67976
68135
  if (!creds3) {
67977
68136
  throw new Error("Not signed in on this machine \u2014 sign in (or share this machine) to file a report from chat.");
67978
68137
  }
67979
- const daemonLog = tail2(join28(daemonDir2(), "daemon.log"));
68138
+ const daemonLog = tail2(join29(daemonDir2(), "daemon.log"));
67980
68139
  const diagnostics = {
67981
68140
  collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
67982
68141
  source: "daemon",
@@ -68018,12 +68177,12 @@ function createDaemonSupportSubmit() {
68018
68177
  init_dist4();
68019
68178
 
68020
68179
  // ../../packages/host-core/dist/app-control-bridge.js
68021
- import { randomUUID as randomUUID14 } from "node:crypto";
68180
+ import { randomUUID as randomUUID15 } from "node:crypto";
68022
68181
  var DEFAULT_APP_REQUEST_TIMEOUT_MS = 2e4;
68023
68182
  var pending = /* @__PURE__ */ new Map();
68024
68183
  function requestDesktopApp(emit2, origin, capabilityId, input, timeoutMs = DEFAULT_APP_REQUEST_TIMEOUT_MS, sessionId) {
68025
68184
  return new Promise((resolve12, reject) => {
68026
- const requestId = `app:${randomUUID14()}`;
68185
+ const requestId = `app:${randomUUID15()}`;
68027
68186
  const timer = setTimeout(() => {
68028
68187
  pending.delete(requestId);
68029
68188
  reject(new Error("App request timed out \u2014 the Tesseract desktop app did not respond."));
@@ -68078,7 +68237,7 @@ var flushTimer = null;
68078
68237
  var keepalive = null;
68079
68238
  var gateTimer = null;
68080
68239
  var stageGraceTimer = null;
68081
- var log3 = () => {
68240
+ var log4 = () => {
68082
68241
  };
68083
68242
  function snapshot() {
68084
68243
  if (!state)
@@ -68179,7 +68338,7 @@ function attach(sub, hello) {
68179
68338
  if (!accepted)
68180
68339
  return;
68181
68340
  subs.set(sub.deviceId, s);
68182
- log3(`[present] attach ${sub.role} ${sub.deviceId} (${sub.name})`);
68341
+ log4(`[present] attach ${sub.role} ${sub.deviceId} (${sub.name})`);
68183
68342
  if (sub.role === "stage" && stageGraceTimer) {
68184
68343
  clearTimeout(stageGraceTimer);
68185
68344
  stageGraceTimer = null;
@@ -68199,7 +68358,7 @@ function detach(deviceId2) {
68199
68358
  if (!s)
68200
68359
  return;
68201
68360
  subs.delete(deviceId2);
68202
- log3(`[present] detach ${s.role} ${deviceId2}`);
68361
+ log4(`[present] detach ${s.role} ${deviceId2}`);
68203
68362
  if (s.role === "stage" && state) {
68204
68363
  state.phase = "idle";
68205
68364
  if (!stageGraceTimer) {
@@ -68263,7 +68422,7 @@ function start(msg, actorId) {
68263
68422
  ensureKeepalive();
68264
68423
  lastContextIndex = -1;
68265
68424
  scheduleContext();
68266
- log3(`[present] start "${msg.deckTitle}" epoch=${epoch} slides=${slides.length}`);
68425
+ log4(`[present] start "${msg.deckTitle}" epoch=${epoch} slides=${slides.length}`);
68267
68426
  for (const s of subs.values())
68268
68427
  sendSlides(s);
68269
68428
  bump();
@@ -68272,7 +68431,7 @@ function end(reason = "ended") {
68272
68431
  if (!presentId)
68273
68432
  return;
68274
68433
  const id = presentId;
68275
- log3(`[present] end (${reason})`);
68434
+ log4(`[present] end (${reason})`);
68276
68435
  for (const s of subs.values()) {
68277
68436
  try {
68278
68437
  s.send({ type: "present:closed", presentId: id, reason });
@@ -68311,7 +68470,7 @@ function gotoIndex(index, by = "remote") {
68311
68470
  state.build = 0;
68312
68471
  scheduleContext();
68313
68472
  }
68314
- log3(`[present] goto ${next} by=${by}`);
68473
+ log4(`[present] goto ${next} by=${by}`);
68315
68474
  return bump();
68316
68475
  }
68317
68476
  function gotoSlideId(slideId, by = "remote") {
@@ -68367,7 +68526,7 @@ function setAiListening(listening, holdMs) {
68367
68526
  gateTimer = setTimeout(() => {
68368
68527
  gateTimer = null;
68369
68528
  if (state?.ai.listening) {
68370
- log3("[present] AI input gate expired (dead-man) \u2014 closing");
68529
+ log4("[present] AI input gate expired (dead-man) \u2014 closing");
68371
68530
  state.ai.listening = false;
68372
68531
  bump();
68373
68532
  }
@@ -68478,7 +68637,7 @@ function onClientFrame(deviceId2, msg, send3, meta4) {
68478
68637
  return;
68479
68638
  }
68480
68639
  if (!sub) {
68481
- log3(`[present] auto-attaching ${deviceId2} on ${msg.type} \u2014 no hello, protocol assumed v${PRESENT_PROTOCOL_VERSION}`);
68640
+ log4(`[present] auto-attaching ${deviceId2} on ${msg.type} \u2014 no hello, protocol assumed v${PRESENT_PROTOCOL_VERSION}`);
68482
68641
  attach({ deviceId: deviceId2, role: meta4?.role ?? "remote", name: meta4?.name ?? deviceId2, send: send3 }, {
68483
68642
  presentId: msg.presentId ?? "",
68484
68643
  protocolVersion: PRESENT_PROTOCOL_VERSION
@@ -68976,7 +69135,7 @@ function buildSharedBackend(engineId) {
68976
69135
  },
68977
69136
  resolver,
68978
69137
  // Daemon plugins (review #3): operator-controlled <daemonDir>/plugins, env-var secrets.
68979
- buildDaemonPluginLoader(join29(daemonDir2(), "plugins"))
69138
+ buildDaemonPluginLoader(join30(daemonDir2(), "plugins"))
68980
69139
  ).then((b) => {
68981
69140
  const sendMessage = b.sendMessage.bind(b);
68982
69141
  b.sendMessage = (params) => {
@@ -69083,7 +69242,7 @@ function refreshDaemonScheduling(b) {
69083
69242
  }
69084
69243
  b.configureScheduling({
69085
69244
  enabled,
69086
- file: join29(dir2, "scheduled_tasks.json"),
69245
+ file: join30(dir2, "scheduled_tasks.json"),
69087
69246
  // projectDir omitted ⇒ fires track the daemon's currently-open project (setConfig).
69088
69247
  isOwner: schedulingLock ? () => schedulingLock.isOwner() : void 0
69089
69248
  });
@@ -69102,14 +69261,14 @@ var VOICE_GATEWAY = process.env.CCB_VOICE_GATEWAY !== "0";
69102
69261
  import { readdir as readdir3, readFile as readFile9, writeFile as writeFile8, mkdir as mkdir7, cp, rm, rename as renameAsync, stat as stat2 } from "node:fs/promises";
69103
69262
  import { openSync as openSync4, readSync as readSync3, writeSync, closeSync as closeSync4, statSync as statSync5, existsSync as existsSync14, mkdtempSync, rmSync as rmSync5, readFileSync as readFileSync21, readdirSync as readdirSync6, watch, realpathSync as realpathSync3, constants as fsConstants } from "node:fs";
69104
69263
  import { tmpdir as tmpdir3 } from "node:os";
69105
- import { basename as basename5, dirname as dirname12, join as join31, relative as relative4, resolve as resolve6, sep as sep5, isAbsolute as isAbsolute5 } from "node:path";
69264
+ import { basename as basename5, dirname as dirname12, join as join32, relative as relative4, resolve as resolve6, sep as sep5, isAbsolute as isAbsolute5 } from "node:path";
69106
69265
  import { execFile as execFile3 } from "node:child_process";
69107
69266
  import { promisify as promisify2 } from "node:util";
69108
69267
 
69109
69268
  // ../../packages/host-core/dist/fs-stats.js
69110
69269
  init_dist();
69111
69270
  import { readdir as readdir2, lstat, readFile as readFile8 } from "node:fs/promises";
69112
- import { join as join30 } from "node:path";
69271
+ import { join as join31 } from "node:path";
69113
69272
  var TEXT_CAP = 5 * 1024 * 1024;
69114
69273
  async function dirStats(root, opts) {
69115
69274
  const start3 = Date.now();
@@ -69140,11 +69299,11 @@ async function dirStats(root, opts) {
69140
69299
  if (opts?.excludeHeavy && STATS_HEAVY_DIRS.has(e.name))
69141
69300
  continue;
69142
69301
  dirCount++;
69143
- stack.push(join30(dir2, e.name));
69302
+ stack.push(join31(dir2, e.name));
69144
69303
  } else if (e.isFile()) {
69145
69304
  fileCount++;
69146
69305
  try {
69147
- totalBytes += (await lstat(join30(dir2, e.name))).size;
69306
+ totalBytes += (await lstat(join31(dir2, e.name))).size;
69148
69307
  } catch {
69149
69308
  }
69150
69309
  }
@@ -69179,7 +69338,7 @@ async function codeStats(root) {
69179
69338
  continue;
69180
69339
  if (e.isDirectory()) {
69181
69340
  if (!STATS_HEAVY_DIRS.has(e.name))
69182
- stack.push(join30(dir2, e.name));
69341
+ stack.push(join31(dir2, e.name));
69183
69342
  continue;
69184
69343
  }
69185
69344
  if (!e.isFile())
@@ -69187,7 +69346,7 @@ async function codeStats(root) {
69187
69346
  const lang = langForFilename(e.name);
69188
69347
  if (!lang)
69189
69348
  continue;
69190
- const full = join30(dir2, e.name);
69349
+ const full = join31(dir2, e.name);
69191
69350
  let size = 0;
69192
69351
  try {
69193
69352
  size = (await lstat(full)).size;
@@ -69280,7 +69439,7 @@ async function listAllFilesUnder(dir2) {
69280
69439
  return;
69281
69440
  if (ent.name === ".DS_Store")
69282
69441
  continue;
69283
- const full = join31(d, ent.name);
69442
+ const full = join32(d, ent.name);
69284
69443
  if (ent.isDirectory()) {
69285
69444
  if (!LISTALL_SKIP_DIRS.has(ent.name))
69286
69445
  await walk(full);
@@ -69397,18 +69556,18 @@ function resolveParent(raw, root) {
69397
69556
  if (raw === "" || raw === "~")
69398
69557
  return root;
69399
69558
  if (raw.startsWith("~/"))
69400
- return join31(root, raw.slice(2));
69401
- return isAbsolute5(raw) ? raw : join31(root, raw);
69559
+ return join32(root, raw.slice(2));
69560
+ return isAbsolute5(raw) ? raw : join32(root, raw);
69402
69561
  }
69403
69562
  function resolveUniqueName(dir2, name) {
69404
- const direct = join31(dir2, name);
69563
+ const direct = join32(dir2, name);
69405
69564
  if (!existsSync14(direct))
69406
69565
  return direct;
69407
69566
  const dot = name.lastIndexOf(".");
69408
69567
  const base = dot > 0 ? name.slice(0, dot) : name;
69409
69568
  const ext = dot > 0 ? name.slice(dot) : "";
69410
69569
  for (let i = 2; i < 1e4; i++) {
69411
- const candidate = join31(dir2, `${base} ${i}${ext}`);
69570
+ const candidate = join32(dir2, `${base} ${i}${ext}`);
69412
69571
  if (!existsSync14(candidate))
69413
69572
  return candidate;
69414
69573
  }
@@ -69481,12 +69640,12 @@ async function renderThumbnailImpl(real, maxPx) {
69481
69640
  return { error: "Thumbnails are only available on macOS hosts." };
69482
69641
  let dir2 = null;
69483
69642
  try {
69484
- dir2 = mkdtempSync(join31(tmpdir3(), "fp-thumb-"));
69643
+ dir2 = mkdtempSync(join32(tmpdir3(), "fp-thumb-"));
69485
69644
  await execFileAsync("qlmanage", ["-t", "-s", String(maxPx), "-o", dir2, real], { timeout: 1e4, killSignal: "SIGKILL" }).catch(() => void 0);
69486
69645
  const produced = readdirSync6(dir2).find((f) => f.toLowerCase().endsWith(".png"));
69487
69646
  if (!produced)
69488
69647
  return { error: "No thumbnail available." };
69489
- return { png: readFileSync21(join31(dir2, produced)).toString("base64") };
69648
+ return { png: readFileSync21(join32(dir2, produced)).toString("base64") };
69490
69649
  } catch (err2) {
69491
69650
  return { error: String(err2.message) };
69492
69651
  } finally {
@@ -69526,7 +69685,7 @@ var MAX_STAT_PATHS = 4096;
69526
69685
  var SAFE_SEG = /^[A-Za-z0-9._-]+$/;
69527
69686
  var TRASH_RETENTION_MS = 14 * 24 * 60 * 60 * 1e3;
69528
69687
  function pruneTrash(root) {
69529
- const trashDir = join31(root, TRASH_DIR);
69688
+ const trashDir = join32(root, TRASH_DIR);
69530
69689
  if (!existsSync14(trashDir))
69531
69690
  return;
69532
69691
  const cutoff = Date.now() - TRASH_RETENTION_MS;
@@ -69535,9 +69694,9 @@ function pruneTrash(root) {
69535
69694
  if (!e.isDirectory())
69536
69695
  continue;
69537
69696
  try {
69538
- const meta4 = JSON.parse(readFileSync21(join31(trashDir, e.name, ".fp-meta.json"), "utf-8"));
69697
+ const meta4 = JSON.parse(readFileSync21(join32(trashDir, e.name, ".fp-meta.json"), "utf-8"));
69539
69698
  if ((meta4.deletedAt ?? 0) < cutoff)
69540
- rmSync5(join31(trashDir, e.name), { recursive: true, force: true });
69699
+ rmSync5(join32(trashDir, e.name), { recursive: true, force: true });
69541
69700
  } catch {
69542
69701
  }
69543
69702
  }
@@ -69563,12 +69722,12 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69563
69722
  return entries.map((e) => {
69564
69723
  let id = null;
69565
69724
  try {
69566
- id = statId(join31(real, e.name));
69725
+ id = statId(join32(real, e.name));
69567
69726
  } catch {
69568
69727
  }
69569
69728
  return {
69570
69729
  name: e.name,
69571
- path: join31(dir2, e.name),
69730
+ path: join32(dir2, e.name),
69572
69731
  isDir: e.isDirectory(),
69573
69732
  ignored: ignored.has(e.name),
69574
69733
  size: id?.size,
@@ -69735,7 +69894,7 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69735
69894
  const name = String(a[1] ?? "").trim();
69736
69895
  if (!validName(name))
69737
69896
  return { ok: false, error: "Invalid project name." };
69738
- const target = join31(parent, name);
69897
+ const target = join32(parent, name);
69739
69898
  if (!insideOk(target))
69740
69899
  return { ok: false, error: "Refusing to create a project outside the shared root." };
69741
69900
  if (existsSync14(target))
@@ -69753,7 +69912,7 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69753
69912
  const name = String(a[1] ?? "").trim();
69754
69913
  if (!validName(name))
69755
69914
  return { ok: false, error: "Invalid name." };
69756
- const target = join31(parent, name);
69915
+ const target = join32(parent, name);
69757
69916
  if (!insideOk(target))
69758
69917
  return OUTSIDE("create");
69759
69918
  if (existsSync14(target))
@@ -69775,7 +69934,7 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69775
69934
  return { ok: false, error: "Invalid name." };
69776
69935
  if (!insideOk(oldPath))
69777
69936
  return OUTSIDE("rename");
69778
- const newPath = join31(dirname12(oldPath), newName);
69937
+ const newPath = join32(dirname12(oldPath), newName);
69779
69938
  if (existsSync14(newPath))
69780
69939
  return { ok: false, error: `A file named "${newName}" already exists.` };
69781
69940
  try {
@@ -69809,19 +69968,19 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69809
69968
  try {
69810
69969
  pruneTrash(root);
69811
69970
  const id = `${Date.now().toString(36)}-${(trashSeq++).toString(36)}`;
69812
- const itemDir = join31(root, TRASH_DIR, id);
69971
+ const itemDir = join32(root, TRASH_DIR, id);
69813
69972
  await mkdir7(itemDir, { recursive: true });
69814
69973
  const name = basename5(p);
69815
- const trashedPath = join31(itemDir, name);
69974
+ const trashedPath = join32(itemDir, name);
69816
69975
  await moveItem(p, trashedPath);
69817
- await writeFile8(join31(itemDir, ".fp-meta.json"), JSON.stringify({ originalPath: resolve6(p), name, deletedAt: Date.now() }));
69976
+ await writeFile8(join32(itemDir, ".fp-meta.json"), JSON.stringify({ originalPath: resolve6(p), name, deletedAt: Date.now() }));
69818
69977
  return { ok: true, trashId: id, path: trashedPath };
69819
69978
  } catch (err2) {
69820
69979
  return { ok: false, error: String(err2.message) };
69821
69980
  }
69822
69981
  }
69823
69982
  case "listTrash": {
69824
- const trashDir = join31(root, TRASH_DIR);
69983
+ const trashDir = join32(root, TRASH_DIR);
69825
69984
  if (!existsSync14(trashDir))
69826
69985
  return [];
69827
69986
  try {
@@ -69830,8 +69989,8 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69830
69989
  if (!e.isDirectory())
69831
69990
  continue;
69832
69991
  try {
69833
- const meta4 = JSON.parse(readFileSync21(join31(trashDir, e.name, ".fp-meta.json"), "utf-8"));
69834
- out.push({ trashId: e.name, name: meta4.name, path: join31(trashDir, e.name, meta4.name), originalPath: meta4.originalPath, deletedAt: meta4.deletedAt });
69992
+ const meta4 = JSON.parse(readFileSync21(join32(trashDir, e.name, ".fp-meta.json"), "utf-8"));
69993
+ out.push({ trashId: e.name, name: meta4.name, path: join32(trashDir, e.name, meta4.name), originalPath: meta4.originalPath, deletedAt: meta4.deletedAt });
69835
69994
  } catch {
69836
69995
  }
69837
69996
  }
@@ -69844,14 +70003,14 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69844
70003
  const id = String(a[0] ?? "");
69845
70004
  if (!SAFE_SEG.test(id))
69846
70005
  return { ok: false, error: "Bad trash id." };
69847
- const itemDir = join31(root, TRASH_DIR, id);
70006
+ const itemDir = join32(root, TRASH_DIR, id);
69848
70007
  if (!existsSync14(itemDir))
69849
70008
  return { ok: false, error: "Not in trash." };
69850
70009
  try {
69851
- const meta4 = JSON.parse(readFileSync21(join31(itemDir, ".fp-meta.json"), "utf-8"));
70010
+ const meta4 = JSON.parse(readFileSync21(join32(itemDir, ".fp-meta.json"), "utf-8"));
69852
70011
  if (!validName(meta4.name))
69853
70012
  return { ok: false, error: "Bad trashed name." };
69854
- const src = join31(itemDir, meta4.name);
70013
+ const src = join32(itemDir, meta4.name);
69855
70014
  let dest = meta4.originalPath;
69856
70015
  if (!insideOk(dest))
69857
70016
  return OUTSIDE("restore");
@@ -69945,7 +70104,7 @@ function watchRemoteRoot(root, key2, onChange) {
69945
70104
  const rel = String(filename);
69946
70105
  if (rel.split(sep5).some((seg) => IGNORED.has(seg)))
69947
70106
  return;
69948
- pending2.add(join31(resolved, rel));
70107
+ pending2.add(join32(resolved, rel));
69949
70108
  if (timer)
69950
70109
  clearTimeout(timer);
69951
70110
  timer = setTimeout(() => {
@@ -70160,11 +70319,11 @@ function releaseDcClient(clientId) {
70160
70319
  // ../../packages/host-core/dist/fs-transfer.js
70161
70320
  import { readdir as readdir4, lstat as lstat2, mkdir as mkdir8, chmod, rm as rm2, readlink, symlink, stat as stat3 } from "node:fs/promises";
70162
70321
  import { openSync as openSync5, readSync as readSync4, closeSync as closeSync5, existsSync as existsSync15, createWriteStream } from "node:fs";
70163
- import { dirname as dirname13, join as join33, relative as relative6, sep as sep7 } from "node:path";
70322
+ import { basename as basename6, dirname as dirname13, join as join34, relative as relative6, sep as sep7 } from "node:path";
70164
70323
 
70165
70324
  // ../../packages/host-core/dist/active-link/paths.js
70166
70325
  init_base_paths();
70167
- import { join as join32, relative as relative5, sep as sep6 } from "node:path";
70326
+ import { join as join33, relative as relative5, sep as sep6 } from "node:path";
70168
70327
  function activeLinkEnabled() {
70169
70328
  return process.env.CCB_ACTIVE_LINK !== "0";
70170
70329
  }
@@ -70190,7 +70349,7 @@ function toPosixRel(root, abs) {
70190
70349
  return relative5(root, abs).split(sep6).join("/");
70191
70350
  }
70192
70351
  function fromPosixRel(root, rel) {
70193
- return join32(root, rel.split("/").join(sep6));
70352
+ return join33(root, rel.split("/").join(sep6));
70194
70353
  }
70195
70354
  function isExcludedAbs(abs, root) {
70196
70355
  const rel = toPosixRel(root, abs);
@@ -70202,7 +70361,7 @@ function activeLinkRoot() {
70202
70361
  return process.env.CCB_ACTIVE_LINK_DIR ?? tessPath("active-link");
70203
70362
  }
70204
70363
  function linkStateDir(linkId) {
70205
- return join32(activeLinkRoot(), linkId);
70364
+ return join33(activeLinkRoot(), linkId);
70206
70365
  }
70207
70366
 
70208
70367
  // ../../packages/host-core/dist/fs-transfer.js
@@ -70215,11 +70374,43 @@ function clearTimer(t) {
70215
70374
  return null;
70216
70375
  }
70217
70376
  function uniqueDir(parent, name) {
70218
- let candidate = join33(parent, name);
70377
+ let candidate = join34(parent, name);
70378
+ for (let i = 2; existsSync15(candidate) && i < 1e4; i++)
70379
+ candidate = join34(parent, `${name} ${i}`);
70380
+ return candidate;
70381
+ }
70382
+ function uniqueFile(parent, name) {
70383
+ const dot = name.lastIndexOf(".");
70384
+ const stem = dot > 0 ? name.slice(0, dot) : name;
70385
+ const ext = dot > 0 ? name.slice(dot) : "";
70386
+ let candidate = join34(parent, name);
70219
70387
  for (let i = 2; existsSync15(candidate) && i < 1e4; i++)
70220
- candidate = join33(parent, `${name} ${i}`);
70388
+ candidate = join34(parent, `${stem} ${i}${ext}`);
70221
70389
  return candidate;
70222
70390
  }
70391
+ var inboundObserver = null;
70392
+ function setInboundTransferObserver(cb) {
70393
+ inboundObserver = cb;
70394
+ }
70395
+ function notifyInbound(s, phase, transferId, extra) {
70396
+ if (!inboundObserver)
70397
+ return;
70398
+ try {
70399
+ inboundObserver({
70400
+ transferId,
70401
+ phase,
70402
+ fromHost: s.fromHost,
70403
+ destPath: s.singleFilePath ?? s.destRoot,
70404
+ singleFile: !!s.singleFilePath,
70405
+ totalFiles: s.totalFiles,
70406
+ totalBytes: s.totalBytes,
70407
+ filesDone: s.filesDone,
70408
+ bytesDone: s.bytesDone,
70409
+ ...extra
70410
+ });
70411
+ } catch {
70412
+ }
70413
+ }
70223
70414
  var producers = /* @__PURE__ */ new Map();
70224
70415
  function failProducer(transferId, message) {
70225
70416
  const s = producers.get(transferId);
@@ -70260,7 +70451,7 @@ async function buildManifest(root, followSymlinks, excludeHeavy) {
70260
70451
  continue;
70261
70452
  if (e.isDirectory() && excludeHeavy && HEAVY_DIRS.has(e.name))
70262
70453
  continue;
70263
- const abs = join33(dir2, e.name);
70454
+ const abs = join34(dir2, e.name);
70264
70455
  const rel = relative6(root, abs).split(sep7).join("/");
70265
70456
  const st = await lstat2(abs).catch(() => null);
70266
70457
  if (!st)
@@ -70285,15 +70476,30 @@ async function buildManifest(root, followSymlinks, excludeHeavy) {
70285
70476
  }
70286
70477
  }
70287
70478
  }
70479
+ const rootSt = await lstat2(root);
70480
+ if (!rootSt.isDirectory()) {
70481
+ const name = basename6(root);
70482
+ if (rootSt.isSymbolicLink()) {
70483
+ if (!followSymlinks)
70484
+ return { dirs, files: [{ rel: name, mode: rootSt.mode, kind: "symlink", size: 0 }], totalBytes: 0, singleFile: true };
70485
+ const real = await stat3(root);
70486
+ if (real.isDirectory()) {
70487
+ await walk(root);
70488
+ return { dirs, files, totalBytes, singleFile: false };
70489
+ }
70490
+ return { dirs, files: [{ rel: name, mode: real.mode, kind: "file", size: real.size }], totalBytes: real.size, singleFile: true };
70491
+ }
70492
+ return { dirs, files: [{ rel: name, mode: rootSt.mode, kind: "file", size: rootSt.size }], totalBytes: rootSt.size, singleFile: true };
70493
+ }
70288
70494
  await walk(root);
70289
- return { dirs, files, totalBytes };
70495
+ return { dirs, files, totalBytes, singleFile: false };
70290
70496
  }
70291
70497
  async function startOutboundTransfer(msg, controllerClientId, ownHostId2, hostRoot, send3) {
70292
70498
  const { transferId, toHost, srcPath } = msg;
70293
70499
  const state2 = { toHost, controllerClientId, send: send3, sentBytes: 0, ackedBytes: 0, done: false, cancelled: false, wake: null, idle: null };
70294
70500
  producers.set(transferId, state2);
70295
70501
  if (!isInsideRoot(srcPath, hostRoot)) {
70296
- failProducer(transferId, "Source folder is outside the shared root.");
70502
+ failProducer(transferId, "Source is outside the shared root.");
70297
70503
  return;
70298
70504
  }
70299
70505
  armProducerWatchdog(transferId);
@@ -70301,22 +70507,24 @@ async function startOutboundTransfer(msg, controllerClientId, ownHostId2, hostRo
70301
70507
  try {
70302
70508
  manifest = await buildManifest(srcPath, msg.followSymlinks, msg.excludeHeavy);
70303
70509
  } catch (e) {
70304
- failProducer(transferId, `Couldn't read the source folder: ${e.message}`);
70510
+ failProducer(transferId, `Couldn't read the source: ${e.message}`);
70305
70511
  return;
70306
70512
  }
70307
70513
  if (state2.cancelled) {
70308
70514
  producers.delete(transferId);
70309
70515
  return;
70310
70516
  }
70517
+ const srcBase = manifest.singleFile ? dirname13(srcPath) : srcPath;
70311
70518
  send3({
70312
70519
  type: "fs:transferManifest",
70313
70520
  transferId,
70314
70521
  toHost,
70315
70522
  fromHost: ownHostId2,
70316
70523
  controllerClientId,
70317
- destName: msg.destName,
70524
+ destName: manifest.singleFile ? msg.destName || basename6(srcPath) : msg.destName,
70318
70525
  destParent: msg.destParent,
70319
70526
  onCollision: msg.onCollision,
70527
+ singleFile: manifest.singleFile,
70320
70528
  totalFiles: manifest.files.length,
70321
70529
  totalBytes: manifest.totalBytes,
70322
70530
  dirs: manifest.dirs
@@ -70327,11 +70535,11 @@ async function startOutboundTransfer(msg, controllerClientId, ownHostId2, hostRo
70327
70535
  for (let fileIx = 0; fileIx < manifest.files.length && !state2.cancelled; fileIx++) {
70328
70536
  const f = manifest.files[fileIx];
70329
70537
  if (f.kind === "symlink") {
70330
- const target = await readlink(join33(srcPath, f.rel.split("/").join(sep7))).catch(() => "");
70538
+ const target = await readlink(join34(srcBase, f.rel.split("/").join(sep7))).catch(() => "");
70331
70539
  send3({ type: "fs:transferChunk", transferId, toHost, fileIx, rel: f.rel, mode: f.mode, kind: "symlink", offset: 0, base64: Buffer.from(target, "utf-8").toString("base64"), eof: true }, { hostId: toHost });
70332
70540
  continue;
70333
70541
  }
70334
- const abs = join33(srcPath, f.rel.split("/").join(sep7));
70542
+ const abs = join34(srcBase, f.rel.split("/").join(sep7));
70335
70543
  let fd = null;
70336
70544
  try {
70337
70545
  fd = openSync5(abs, "r");
@@ -70388,6 +70596,7 @@ function emitProgress(transferId, send3, currentFile) {
70388
70596
  const s = consumers.get(transferId);
70389
70597
  if (!s)
70390
70598
  return;
70599
+ notifyInbound(s, "progress", transferId, { currentFile });
70391
70600
  send3({ type: "fs:transferProgress", transferId, filesDone: s.filesDone, totalFiles: s.totalFiles, bytesDone: s.bytesDone, totalBytes: s.totalBytes, currentFile }, { clientId: s.controllerClientId });
70392
70601
  }
70393
70602
  function finishConsumer(transferId, send3) {
@@ -70395,7 +70604,8 @@ function finishConsumer(transferId, send3) {
70395
70604
  if (!s)
70396
70605
  return;
70397
70606
  s.idle = clearTimer(s.idle);
70398
- send3({ type: "fs:transferDone", transferId, destRoot: s.destRoot, filesWritten: s.filesDone, bytesWritten: s.bytesDone }, { clientId: s.controllerClientId });
70607
+ notifyInbound(s, "done", transferId);
70608
+ send3({ type: "fs:transferDone", transferId, destRoot: s.singleFilePath ?? s.destRoot, filesWritten: s.filesDone, bytesWritten: s.bytesDone }, { clientId: s.controllerClientId });
70399
70609
  consumers.delete(transferId);
70400
70610
  }
70401
70611
  async function abortConsumer(transferId) {
@@ -70416,13 +70626,20 @@ async function abortConsumer(transferId) {
70416
70626
  await rm2(s.destRoot, { recursive: true, force: true });
70417
70627
  } catch {
70418
70628
  }
70629
+ } else if (s.singleFilePath && s.createdFile) {
70630
+ try {
70631
+ await rm2(s.singleFilePath, { force: true });
70632
+ } catch {
70633
+ }
70419
70634
  }
70420
70635
  consumers.delete(transferId);
70421
70636
  }
70422
70637
  function failConsumer(transferId, message, send3) {
70423
70638
  const s = consumers.get(transferId);
70424
- if (s)
70639
+ if (s) {
70640
+ notifyInbound(s, "error", transferId, { message });
70425
70641
  send3({ type: "fs:transferError", transferId, where: "dest", message }, { clientId: s.controllerClientId });
70642
+ }
70426
70643
  void abortConsumer(transferId);
70427
70644
  }
70428
70645
  function armConsumerWatchdog(transferId, send3) {
@@ -70438,12 +70655,56 @@ async function beginInboundTransfer(msg, hostRoot, send3) {
70438
70655
  send3({ type: "fs:transferError", transferId, where: "dest", message }, { clientId: controllerClientId });
70439
70656
  send3({ type: "fs:transferCancel", transferId, toHost: fromHost }, { hostId: fromHost });
70440
70657
  };
70441
- const parent = msg.destParent && isInsideRoot(msg.destParent, hostRoot) ? msg.destParent : hostRoot;
70658
+ const parent = canonicalForGuard(msg.destParent && isInsideRoot(msg.destParent, hostRoot) ? msg.destParent : hostRoot);
70442
70659
  if (!msg.destName || msg.destName.includes("/") || msg.destName.includes("\\") || msg.destName.includes("..")) {
70443
70660
  failNoState("Invalid destination name.");
70444
70661
  return;
70445
70662
  }
70446
- let destRoot = join33(parent, msg.destName);
70663
+ const register = (state2) => {
70664
+ consumers.set(transferId, {
70665
+ ...state2,
70666
+ totalFiles: msg.totalFiles,
70667
+ totalBytes: msg.totalBytes,
70668
+ filesDone: 0,
70669
+ bytesDone: 0,
70670
+ curIx: -1,
70671
+ curStream: null,
70672
+ cancelled: false,
70673
+ lastProgress: 0,
70674
+ idle: null
70675
+ });
70676
+ armConsumerWatchdog(transferId, send3);
70677
+ const s = consumers.get(transferId);
70678
+ notifyInbound(s, "begin", transferId);
70679
+ emitProgress(transferId, send3, "");
70680
+ };
70681
+ if (msg.singleFile) {
70682
+ let destFile = join34(parent, msg.destName);
70683
+ let createdFile = true;
70684
+ if (existsSync15(destFile)) {
70685
+ if (msg.onCollision === "abort") {
70686
+ failNoState(`"${msg.destName}" already exists on the destination.`);
70687
+ return;
70688
+ }
70689
+ if (msg.onCollision === "suffix")
70690
+ destFile = uniqueFile(parent, msg.destName);
70691
+ else
70692
+ createdFile = false;
70693
+ }
70694
+ if (!isInsideRoot(destFile, hostRoot)) {
70695
+ failNoState("Destination is outside the shared root.");
70696
+ return;
70697
+ }
70698
+ register({ fromHost, controllerClientId, destRoot: parent, createdRoot: false, singleFilePath: destFile, createdFile });
70699
+ if (msg.totalFiles === 0) {
70700
+ finishConsumer(transferId, send3);
70701
+ return;
70702
+ }
70703
+ await mkdir8(parent, { recursive: true }).catch(() => {
70704
+ });
70705
+ return;
70706
+ }
70707
+ let destRoot = join34(parent, msg.destName);
70447
70708
  let createdRoot = false;
70448
70709
  if (existsSync15(destRoot)) {
70449
70710
  if (msg.onCollision === "abort") {
@@ -70461,23 +70722,7 @@ async function beginInboundTransfer(msg, hostRoot, send3) {
70461
70722
  failNoState("Destination is outside the shared root.");
70462
70723
  return;
70463
70724
  }
70464
- consumers.set(transferId, {
70465
- fromHost,
70466
- controllerClientId,
70467
- destRoot,
70468
- createdRoot,
70469
- totalFiles: msg.totalFiles,
70470
- totalBytes: msg.totalBytes,
70471
- filesDone: 0,
70472
- bytesDone: 0,
70473
- curIx: -1,
70474
- curStream: null,
70475
- cancelled: false,
70476
- lastProgress: 0,
70477
- idle: null
70478
- });
70479
- armConsumerWatchdog(transferId, send3);
70480
- emitProgress(transferId, send3, "");
70725
+ register({ fromHost, controllerClientId, destRoot, createdRoot, singleFilePath: null, createdFile: false });
70481
70726
  if (msg.totalFiles === 0) {
70482
70727
  finishConsumer(transferId, send3);
70483
70728
  return;
@@ -70485,7 +70730,7 @@ async function beginInboundTransfer(msg, hostRoot, send3) {
70485
70730
  try {
70486
70731
  await mkdir8(destRoot, { recursive: true });
70487
70732
  for (const d of msg.dirs) {
70488
- const abs = join33(destRoot, d.rel.split("/").join(sep7));
70733
+ const abs = join34(destRoot, d.rel.split("/").join(sep7));
70489
70734
  if (isInsideRoot(abs, destRoot))
70490
70735
  await mkdir8(abs, { recursive: true }).catch(() => {
70491
70736
  });
@@ -70499,15 +70744,15 @@ async function writeInboundChunk(msg, send3) {
70499
70744
  if (!s || s.cancelled)
70500
70745
  return;
70501
70746
  armConsumerWatchdog(msg.transferId, send3);
70502
- const abs = join33(s.destRoot, msg.rel.split("/").join(sep7));
70503
- if (msg.rel.includes("..") || !isInsideRoot(abs, s.destRoot)) {
70747
+ const abs = s.singleFilePath ?? join34(s.destRoot, msg.rel.split("/").join(sep7));
70748
+ if (!s.singleFilePath && (msg.rel.includes("..") || !isInsideRoot(abs, s.destRoot))) {
70504
70749
  failConsumer(msg.transferId, `Refusing a path outside the destination: ${msg.rel}`, send3);
70505
70750
  return;
70506
70751
  }
70507
70752
  try {
70508
70753
  if (msg.kind === "symlink") {
70509
70754
  const target = Buffer.from(msg.base64, "base64").toString("utf-8");
70510
- const resolved = join33(dirname13(abs), target);
70755
+ const resolved = join34(dirname13(abs), target);
70511
70756
  if (isInsideRoot(resolved, s.destRoot)) {
70512
70757
  await mkdir8(dirname13(abs), { recursive: true }).catch(() => {
70513
70758
  });
@@ -70594,7 +70839,7 @@ function cancelTransfersForClient(clientId, send3) {
70594
70839
 
70595
70840
  // ../../packages/host-core/dist/peer-link/index.js
70596
70841
  init_base_paths();
70597
- import { join as join34 } from "node:path";
70842
+ import { join as join35 } from "node:path";
70598
70843
 
70599
70844
  // ../../packages/host-core/dist/peer-link/manager.js
70600
70845
  init_tls_direct();
@@ -70778,7 +71023,7 @@ var PeerLinkManager = class {
70778
71023
  }
70779
71024
  const socket2 = await q.makeQuicSocket(this.udpPort);
70780
71025
  this.quicSocket = socket2;
70781
- await q.startQuicServerOnSocket({ socket: socket2, identity, onStream: (bs3) => this.acceptInbound(bs3, "punch") });
71026
+ await q.startQuicServerOnSocket({ socket: socket2, identity, onStream: (bs4) => this.acceptInbound(bs4, "punch") });
70782
71027
  const refresh = async () => {
70783
71028
  const found = [];
70784
71029
  for (const { host, port } of resolveStunServers()) {
@@ -71097,7 +71342,7 @@ var PeerLinkManager = class {
71097
71342
  var P2P_ENABLED = process.env.CCB_P2P !== "0";
71098
71343
  function identityPaths() {
71099
71344
  const dir2 = process.env.CCB_P2P_DIR ?? tesseractHome();
71100
- return { certPath: join34(dir2, "peer-cert.pem"), keyPath: join34(dir2, "peer-key.pem") };
71345
+ return { certPath: join35(dir2, "peer-cert.pem"), keyPath: join35(dir2, "peer-key.pem") };
71101
71346
  }
71102
71347
  var manager = null;
71103
71348
  function peerLinkManager() {
@@ -71114,7 +71359,7 @@ async function openPeerLink(peerHostId, ctx2) {
71114
71359
  // ../../packages/host-core/dist/active-link/engine.js
71115
71360
  import { lstat as lstat7, readlink as readlink4, rm as rm3, mkdir as mkdir10, symlink as symlink2, rename as rename2, utimes as utimes2, writeFile as writeFile9 } from "node:fs/promises";
71116
71361
  import { existsSync as existsSync18 } from "node:fs";
71117
- import { dirname as dirname18, join as join39, resolve as resolve9 } from "node:path";
71362
+ import { dirname as dirname18, join as join40, resolve as resolve9 } from "node:path";
71118
71363
 
71119
71364
  // ../../packages/host-core/dist/active-link/blocks.js
71120
71365
  import { createHash as createHash4 } from "node:crypto";
@@ -71379,7 +71624,7 @@ var BlockAssembler = class {
71379
71624
 
71380
71625
  // ../../packages/host-core/dist/active-link/manifest.js
71381
71626
  import { readFileSync as readFileSync23, writeFileSync as writeFileSync15, mkdirSync as mkdirSync16, existsSync as existsSync17, renameSync as renameSync6 } from "node:fs";
71382
- import { join as join35 } from "node:path";
71627
+ import { join as join36 } from "node:path";
71383
71628
 
71384
71629
  // ../../packages/host-core/dist/active-link/reconcile.js
71385
71630
  var ABSENT = { kind: "absent", t: -Infinity, hash: "" };
@@ -71415,7 +71660,7 @@ var LinkManifest = class {
71415
71660
  this.dir = dir2;
71416
71661
  }
71417
71662
  file() {
71418
- return join35(this.dir, "manifest.json");
71663
+ return join36(this.dir, "manifest.json");
71419
71664
  }
71420
71665
  load() {
71421
71666
  try {
@@ -71517,7 +71762,7 @@ var LinkManifest = class {
71517
71762
 
71518
71763
  // ../../packages/host-core/dist/active-link/scanner.js
71519
71764
  import { readdir as readdir5, lstat as lstat3, readlink as readlink2 } from "node:fs/promises";
71520
- import { join as join36, sep as sep8 } from "node:path";
71765
+ import { join as join37, sep as sep8 } from "node:path";
71521
71766
  async function walkRels(root, extraIgnore) {
71522
71767
  const out = [];
71523
71768
  async function rec(dir2) {
@@ -71532,7 +71777,7 @@ async function walkRels(root, extraIgnore) {
71532
71777
  continue;
71533
71778
  if (e.isDirectory() && HEAVY_DIRS.has(e.name))
71534
71779
  continue;
71535
- const abs = join36(dir2, e.name);
71780
+ const abs = join37(dir2, e.name);
71536
71781
  if (extraIgnore?.(abs))
71537
71782
  continue;
71538
71783
  if (e.isDirectory())
@@ -71571,7 +71816,7 @@ async function scanTree(root, prior, allow, extraIgnore) {
71571
71816
  return { records };
71572
71817
  }
71573
71818
  function absOf(root, rel) {
71574
- return join36(root, rel.split("/").join(sep8));
71819
+ return join37(root, rel.split("/").join(sep8));
71575
71820
  }
71576
71821
 
71577
71822
  // ../../packages/host-core/dist/active-link/gitignore.js
@@ -71941,10 +72186,10 @@ var ReaddirpStream = class extends Readable {
71941
72186
  }
71942
72187
  async _formatEntry(dirent, path6) {
71943
72188
  let entry;
71944
- const basename9 = this._isDirent ? dirent.name : dirent;
72189
+ const basename12 = this._isDirent ? dirent.name : dirent;
71945
72190
  try {
71946
- const fullPath = presolve(pjoin(path6, basename9));
71947
- entry = { path: prelative(this._root, fullPath), fullPath, basename: basename9 };
72191
+ const fullPath = presolve(pjoin(path6, basename12));
72192
+ entry = { path: prelative(this._root, fullPath), fullPath, basename: basename12 };
71948
72193
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
71949
72194
  } catch (err2) {
71950
72195
  this._onError(err2);
@@ -72483,9 +72728,9 @@ var NodeFsHandler = class {
72483
72728
  _watchWithNodeFs(path6, listener) {
72484
72729
  const opts = this.fsw.options;
72485
72730
  const directory = sysPath.dirname(path6);
72486
- const basename9 = sysPath.basename(path6);
72731
+ const basename12 = sysPath.basename(path6);
72487
72732
  const parent = this.fsw._getWatchedDir(directory);
72488
- parent.add(basename9);
72733
+ parent.add(basename12);
72489
72734
  const absolutePath = sysPath.resolve(path6);
72490
72735
  const options = {
72491
72736
  persistent: opts.persistent
@@ -72495,7 +72740,7 @@ var NodeFsHandler = class {
72495
72740
  let closer;
72496
72741
  if (opts.usePolling) {
72497
72742
  const enableBin = opts.interval !== opts.binaryInterval;
72498
- options.interval = enableBin && isBinaryPath(basename9) ? opts.binaryInterval : opts.interval;
72743
+ options.interval = enableBin && isBinaryPath(basename12) ? opts.binaryInterval : opts.interval;
72499
72744
  closer = setFsWatchFileListener(path6, absolutePath, options, {
72500
72745
  listener,
72501
72746
  rawEmitter: this.fsw._emitRaw
@@ -72517,11 +72762,11 @@ var NodeFsHandler = class {
72517
72762
  if (this.fsw.closed) {
72518
72763
  return;
72519
72764
  }
72520
- const dirname26 = sysPath.dirname(file2);
72521
- const basename9 = sysPath.basename(file2);
72522
- const parent = this.fsw._getWatchedDir(dirname26);
72765
+ const dirname28 = sysPath.dirname(file2);
72766
+ const basename12 = sysPath.basename(file2);
72767
+ const parent = this.fsw._getWatchedDir(dirname28);
72523
72768
  let prevStats = stats;
72524
- if (parent.has(basename9))
72769
+ if (parent.has(basename12))
72525
72770
  return;
72526
72771
  const listener = async (path6, newStats) => {
72527
72772
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file2, 5))
@@ -72546,9 +72791,9 @@ var NodeFsHandler = class {
72546
72791
  prevStats = newStats2;
72547
72792
  }
72548
72793
  } catch (error51) {
72549
- this.fsw._remove(dirname26, basename9);
72794
+ this.fsw._remove(dirname28, basename12);
72550
72795
  }
72551
- } else if (parent.has(basename9)) {
72796
+ } else if (parent.has(basename12)) {
72552
72797
  const at = newStats.atimeMs;
72553
72798
  const mt = newStats.mtimeMs;
72554
72799
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -73596,7 +73841,7 @@ var ActiveLinkEngine = class {
73596
73841
  }
73597
73842
  async start() {
73598
73843
  this.emit({ type: "status", linkId: this.opts.linkId, state: "scanning" });
73599
- await rm3(join39(this.stateDir, ".staging"), { recursive: true, force: true }).catch(() => {
73844
+ await rm3(join40(this.stateDir, ".staging"), { recursive: true, force: true }).catch(() => {
73600
73845
  });
73601
73846
  this.manifest.load();
73602
73847
  await this.reconcileScan();
@@ -74224,7 +74469,7 @@ var ActiveLinkEngine = class {
74224
74469
  this.inboundWatchdog.clear();
74225
74470
  }
74226
74471
  stagingPath(rel, tag) {
74227
- return join39(this.stateDir, ".staging", `${hashString(rel)}.${tag}`);
74472
+ return join40(this.stateDir, ".staging", `${hashString(rel)}.${tag}`);
74228
74473
  }
74229
74474
  pruneSuppress() {
74230
74475
  const now2 = this.now();
@@ -74250,9 +74495,9 @@ var ActiveLinkEngine = class {
74250
74495
 
74251
74496
  // ../../packages/host-core/dist/active-link/config.js
74252
74497
  import { readFileSync as readFileSync24, writeFileSync as writeFileSync16, mkdirSync as mkdirSync17, existsSync as existsSync19, renameSync as renameSync7, watch as watch3 } from "node:fs";
74253
- import { join as join40 } from "node:path";
74498
+ import { join as join41 } from "node:path";
74254
74499
  function configPath2() {
74255
- return join40(activeLinkRoot(), "links.json");
74500
+ return join41(activeLinkRoot(), "links.json");
74256
74501
  }
74257
74502
  function loadLinkConfigs() {
74258
74503
  try {
@@ -74289,7 +74534,7 @@ function saveLinkConfig(cfg2) {
74289
74534
  writeAll(links);
74290
74535
  }
74291
74536
  function pendingPath() {
74292
- return join40(activeLinkRoot(), "pending-deprovisions.json");
74537
+ return join41(activeLinkRoot(), "pending-deprovisions.json");
74293
74538
  }
74294
74539
  function loadPendingDeprovisions() {
74295
74540
  try {
@@ -74911,6 +75156,12 @@ function connectedFsHosts() {
74911
75156
  const mine = ownHostId();
74912
75157
  return serverHosts.filter((h) => h.online && h.hostId !== mine && !h.hostId.endsWith(`:${deviceId}`)).filter((h) => h.kind === "electron" || h.kind === "cloud").filter((h) => !h.caps || h.caps.includes("fs")).map((h) => ({ hostId: h.hostId, label: h.name || (h.kind === "cloud" ? "Cloud agent" : "Remote machine") }));
74913
75158
  }
75159
+ function listHosts() {
75160
+ return presentedHosts();
75161
+ }
75162
+ function ownHost() {
75163
+ return ownHostId();
75164
+ }
74914
75165
  function assertServerSelection() {
74915
75166
  if (!socket || socket.readyState !== 1)
74916
75167
  return;
@@ -75088,7 +75339,7 @@ function sendActiveLinkProvision(cfg2) {
75088
75339
  type: "provision",
75089
75340
  linkId: cfg2.linkId,
75090
75341
  fromHost: ownHostId(),
75091
- projectName: cfg2.remoteSubfolder ?? basename8(cfg2.localRoot),
75342
+ projectName: cfg2.remoteSubfolder ?? basename9(cfg2.localRoot),
75092
75343
  ...cfg2.remoteParent ? { remoteParent: cfg2.remoteParent } : {},
75093
75344
  ...cfg2.remoteSubfolder ? { remoteSubfolder: cfg2.remoteSubfolder } : {},
75094
75345
  ...cfg2.workspaceLinkId ? { workspaceLinkId: cfg2.workspaceLinkId } : {}
@@ -75115,9 +75366,9 @@ function provisionActiveLinkMirror(payload) {
75115
75366
  const remoteSubfolder = typeof payload.remoteSubfolder === "string" ? payload.remoteSubfolder : void 0;
75116
75367
  let localRoot;
75117
75368
  if (remoteParent && remoteSubfolder) {
75118
- localRoot = join41(remoteParent, safeFolderName(remoteSubfolder));
75369
+ localRoot = join42(remoteParent, safeFolderName(remoteSubfolder));
75119
75370
  } else {
75120
- localRoot = join41(HOST_ROOT, "Tesseract Mirrors", safeFolderName(projectName));
75371
+ localRoot = join42(HOST_ROOT, "Tesseract Mirrors", safeFolderName(projectName));
75121
75372
  }
75122
75373
  if (loadLinkConfigs().some((c) => c.localRoot === localRoot && c.linkId !== linkId)) {
75123
75374
  localRoot = `${localRoot}-${linkId.slice(0, 6)}`;
@@ -75630,6 +75881,7 @@ function registerAsHost() {
75630
75881
  const name = kind === "cloud" ? "Cloud agent" : hostname3();
75631
75882
  const caps = hostSessions ? ["agent", "fs", "term", "e2e"] : ["agent", "fs", "term"];
75632
75883
  caps.push("rdesk");
75884
+ caps.push("xfer");
75633
75885
  const upAdvert = upscaleAdvert();
75634
75886
  if (upAdvert.gpu)
75635
75887
  caps.push("gpu");
@@ -75703,6 +75955,10 @@ function connect() {
75703
75955
  hooks?.onChat?.(env.payload);
75704
75956
  return;
75705
75957
  }
75958
+ if (channel === "drop") {
75959
+ hooks?.onDrop?.(env.payload);
75960
+ return;
75961
+ }
75706
75962
  if (channel === "p2p") {
75707
75963
  const m = env.payload;
75708
75964
  for (const cb of [...p2pSubscribers])
@@ -76233,6 +76489,14 @@ var RdeskSessionManager = class {
76233
76489
  this.audit({ event: "mute", sessionId, detail: `mic=${!!mute.mic} sys=${!!mute.systemAudio}` });
76234
76490
  this.deps.sendControl(sessionId, s.viewer.clientId, { type: "rdesk:muted", mic: !!mute.mic, systemAudio: !!mute.systemAudio });
76235
76491
  }
76492
+ /** Host operator → viewer: send an in-session chat line. No-op if the session is gone. */
76493
+ sendChat(sessionId, text8) {
76494
+ const s = this.sessions.get(sessionId);
76495
+ if (!s)
76496
+ return;
76497
+ this.deps.sendControl(sessionId, s.viewer.clientId, { type: "rdesk:chat", text: text8, from: "host", at: Date.now() });
76498
+ this.audit({ event: "chat", sessionId, detail: `host: ${text8.slice(0, 80)}` });
76499
+ }
76236
76500
  /** Switch the captured display. */
76237
76501
  async setMonitor(sessionId, monitorId) {
76238
76502
  const s = this.sessions.get(sessionId);
@@ -76261,6 +76525,19 @@ var RdeskSessionManager = class {
76261
76525
  return this.setSource(sessionId, msg.sourceId);
76262
76526
  if (msg.type === "rdesk:send-keys")
76263
76527
  return this.sendKeys(sessionId, msg.combo);
76528
+ if (msg.type === "rdesk:chat") {
76529
+ const s = this.sessions.get(sessionId);
76530
+ const text8 = msg.text.slice(0, 4e3);
76531
+ this.deps.onChat?.({ sessionId, viewerName: s?.viewer.name, text: text8 });
76532
+ this.audit({ event: "chat", sessionId, detail: text8.slice(0, 80) });
76533
+ return;
76534
+ }
76535
+ if (msg.type === "rdesk:av") {
76536
+ const s = this.sessions.get(sessionId);
76537
+ this.deps.onCall?.({ sessionId, on: msg.on, viewerName: s?.viewer.name });
76538
+ this.audit({ event: "call", sessionId, detail: msg.on ? "viewer joined call" : "viewer left call" });
76539
+ return;
76540
+ }
76264
76541
  if (msg.type === "rdesk:set-av") {
76265
76542
  if (typeof msg.systemAudio === "boolean")
76266
76543
  this.sessions.get(sessionId)?.engine.setMute({ systemAudio: msg.systemAudio });
@@ -76290,7 +76567,7 @@ var RdeskSessionManager = class {
76290
76567
  };
76291
76568
 
76292
76569
  // ../../packages/host-core/dist/rdesk/publisher-window.js
76293
- import { join as join42 } from "node:path";
76570
+ import { join as join43 } from "node:path";
76294
76571
  var windows = /* @__PURE__ */ new Map();
76295
76572
  var ipcInstalled = false;
76296
76573
  function ensureIpc() {
@@ -76404,7 +76681,7 @@ var PublisherWindow = class {
76404
76681
  throw new Error("Publisher requires the daemon GUI shell (no BrowserWindow).");
76405
76682
  ensureIpc();
76406
76683
  const sourceId = opts.sourceId ?? await sourceIdForMonitor(opts.monitorId ?? await this.primaryMonitorId());
76407
- const win2 = new el2.BrowserWindow({
76684
+ const win3 = new el2.BrowserWindow({
76408
76685
  width: 320,
76409
76686
  height: 200,
76410
76687
  show: false,
@@ -76416,13 +76693,13 @@ var PublisherWindow = class {
76416
76693
  y: -32e3,
76417
76694
  webPreferences: { preload: daemonPreloadPath(), contextIsolation: true, sandbox: false, backgroundThrottling: false }
76418
76695
  });
76419
- this.win = win2;
76696
+ this.win = win3;
76420
76697
  windows.set(this.sessionId, this);
76421
76698
  try {
76422
- win2.setContentProtection(true);
76699
+ win3.setContentProtection(true);
76423
76700
  } catch {
76424
76701
  }
76425
- await win2.loadFile(join42(daemonUiDir(), "publisher.html"));
76702
+ await win3.loadFile(join43(daemonUiDir(), "publisher.html"));
76426
76703
  this.send("rdesk:pub:start", {
76427
76704
  sessionId: this.sessionId,
76428
76705
  sourceId,
@@ -76447,7 +76724,7 @@ var PublisherWindow = class {
76447
76724
  if (!el2?.BrowserWindow)
76448
76725
  throw new Error("Publisher requires the daemon GUI shell (no BrowserWindow).");
76449
76726
  ensureIpc();
76450
- const win2 = new el2.BrowserWindow({
76727
+ const win3 = new el2.BrowserWindow({
76451
76728
  width: 320,
76452
76729
  height: 200,
76453
76730
  show: false,
@@ -76458,13 +76735,13 @@ var PublisherWindow = class {
76458
76735
  y: -32e3,
76459
76736
  webPreferences: { preload: daemonPreloadPath(), contextIsolation: true, sandbox: false, backgroundThrottling: false }
76460
76737
  });
76461
- this.win = win2;
76738
+ this.win = win3;
76462
76739
  windows.set(this.sessionId, this);
76463
76740
  try {
76464
- win2.setContentProtection(true);
76741
+ win3.setContentProtection(true);
76465
76742
  } catch {
76466
76743
  }
76467
- await win2.loadFile(join42(daemonUiDir(), "publisher.html"));
76744
+ await win3.loadFile(join43(daemonUiDir(), "publisher.html"));
76468
76745
  this.send("rdesk:pub:start", {
76469
76746
  sessionId: this.sessionId,
76470
76747
  mode: "canvas",
@@ -78299,7 +78576,17 @@ var XAAD = utf8("tesseract-vault-export-v1");
78299
78576
  var XJSON_AAD = utf8("tesseract-vault-encrypted-json-v1");
78300
78577
 
78301
78578
  // ../../packages/host-core/dist/vault/store.js
78302
- import { randomUUID as randomUUID15 } from "node:crypto";
78579
+ import { randomUUID as randomUUID16 } from "node:crypto";
78580
+ var b64 = (u) => Buffer.from(u).toString("base64");
78581
+ var u82 = (s) => new Uint8Array(Buffer.from(s, "base64"));
78582
+ var isServerRecoverableMeta = (m) => !m.wrappedVaultKey;
78583
+ var metaFromWire = (w) => ({
78584
+ format: "TVK1",
78585
+ kdf: w.kdf,
78586
+ wrappedVaultKey: w.wrappedVaultKey,
78587
+ recoveryWrappedVaultKey: w.recoveryWrappedVaultKey ?? void 0,
78588
+ clientUpdatedAt: w.updatedAt
78589
+ });
78303
78590
  var TOMBSTONE_TTL_MS = 180 * 24 * 60 * 60 * 1e3;
78304
78591
  var VaultStore = class {
78305
78592
  persistence;
@@ -78311,6 +78598,15 @@ var VaultStore = class {
78311
78598
  // decrypted, only while unlocked
78312
78599
  dirty = /* @__PURE__ */ new Set();
78313
78600
  // item ids changed locally since last successful push
78601
+ // Mode 1: a no-password local vault whose random key is cached in the 0600 daemon dir (zero
78602
+ // friction; a fresh install starts with an empty usable vault). Upgraded via setMasterPassword,
78603
+ // or merged into the account + deleted on sign-in.
78604
+ deviceVault = false;
78605
+ // Mode 3: signed-in with NO master password. The key is cached locally (non-ZK by design → offline
78606
+ // auto-unlock) AND server-recoverable so any signed-in device auto-unlocks.
78607
+ serverRecoverable = false;
78608
+ // One-shot: on the mode-3 → mode-4 upgrade, tell the server to drop its recoverable key copy.
78609
+ pendingClearServerKey = false;
78314
78610
  constructor(persistence, now2 = () => Date.now()) {
78315
78611
  this.persistence = persistence;
78316
78612
  this.now = now2;
@@ -78324,7 +78620,163 @@ var VaultStore = class {
78324
78620
  for (const it of this.enc.values())
78325
78621
  if (!it.deleted)
78326
78622
  live++;
78327
- return { exists: this.meta !== null, unlocked: this.vaultKey !== null, itemCount: live };
78623
+ return {
78624
+ exists: this.meta !== null || this.deviceVault,
78625
+ unlocked: this.vaultKey !== null,
78626
+ itemCount: live,
78627
+ deviceVault: this.deviceVault,
78628
+ serverRecoverable: this.serverRecoverable,
78629
+ hasMasterPassword: !!(this.meta && this.meta.wrappedVaultKey && !this.serverRecoverable)
78630
+ };
78631
+ }
78632
+ isDeviceVault() {
78633
+ return this.deviceVault;
78634
+ }
78635
+ isServerRecoverable() {
78636
+ return this.serverRecoverable;
78637
+ }
78638
+ /** For sync push: the raw key to hand the server to seal (mode 3), so other devices auto-unlock. */
78639
+ serverRecoverableKeyForPush() {
78640
+ return this.serverRecoverable && this.vaultKey ? b64(this.vaultKey) : null;
78641
+ }
78642
+ /** For sync push: consume the one-shot "clear the server's recoverable key" flag (mode-4 upgrade). */
78643
+ /** Peek the one-shot "clear the server's recoverable key" flag (mode-4 upgrade) WITHOUT consuming it. */
78644
+ hasPendingClearServerKey() {
78645
+ return this.pendingClearServerKey;
78646
+ }
78647
+ /** Consume the flag — call ONLY after the meta PUT carrying `serverRecoverableKey: null` succeeds. */
78648
+ clearPendingServerKeyFlag() {
78649
+ this.pendingClearServerKey = false;
78650
+ }
78651
+ /** On sign-out, drop an ACCOUNT vault's local state so this process-wide singleton can't leak one
78652
+ * user's vault into the next user's sync (the daemon isn't restarted on an in-process account
78653
+ * switch). The account's ciphertext + meta are safe on the server (re-pulled on the next sign-in),
78654
+ * and the mode-3 device key IS the account key, so it's cleared too. A machine-local NO-PASSWORD
78655
+ * device vault (mode 1) is NOT account-bound — it's left intact. The next ensureVault re-establishes
78656
+ * a fresh device vault from the now-empty state. */
78657
+ resetForSignOut() {
78658
+ if (this.deviceVault)
78659
+ return;
78660
+ this.meta = null;
78661
+ this.enc.clear();
78662
+ this.vaultKey = null;
78663
+ this.items = null;
78664
+ this.dirty.clear();
78665
+ this.serverRecoverable = false;
78666
+ this.pendingClearServerKey = false;
78667
+ this.persistence.clearDeviceKey();
78668
+ this.persist();
78669
+ }
78670
+ // ── zero-friction lifecycle (device vault / server-recoverable / merge) ────────────
78671
+ /** Call once on load: if no vault exists, AUTO-CREATE an empty no-password device vault (mode 1,
78672
+ * no prompt); if a mode-3 (server-recoverable) meta + cached key exist, auto-unlock. No-op when a
78673
+ * master-password vault exists (it stays locked for the unlock screen). Mirrors the extension. */
78674
+ async ensureVault() {
78675
+ const stored = this.persistence.loadDeviceKey();
78676
+ if (this.meta) {
78677
+ if (isServerRecoverableMeta(this.meta) && stored) {
78678
+ this.vaultKey = u82(stored);
78679
+ this.serverRecoverable = true;
78680
+ await this.decryptAll();
78681
+ }
78682
+ return;
78683
+ }
78684
+ if (stored) {
78685
+ this.vaultKey = u82(stored);
78686
+ } else {
78687
+ this.vaultKey = newVaultKey();
78688
+ this.persistence.saveDeviceKey(b64(this.vaultKey));
78689
+ this.items = /* @__PURE__ */ new Map();
78690
+ }
78691
+ this.deviceVault = true;
78692
+ if (this.enc.size)
78693
+ await this.decryptAll();
78694
+ else
78695
+ this.items = this.items ?? /* @__PURE__ */ new Map();
78696
+ }
78697
+ /** On sign-in, reconcile the local vault against the account snapshot:
78698
+ * - account has a master-password vault → `needsMasterPassword` (caller collects it → mergeIntoAccount);
78699
+ * - else establish mode 3 from the device/server key + fold local items in (auto-unlock, no prompt). */
78700
+ async reconcileSignIn(snap) {
78701
+ if (snap.meta && !snap.meta.serverKey && snap.meta.wrappedVaultKey) {
78702
+ return { status: "needsMasterPassword" };
78703
+ }
78704
+ const localItems = this.items ? [...this.items.values()] : [];
78705
+ const cached3 = this.persistence.loadDeviceKey();
78706
+ let key2;
78707
+ if (snap.meta?.serverKey)
78708
+ key2 = u82(snap.meta.serverKey);
78709
+ else if (this.vaultKey)
78710
+ key2 = this.vaultKey;
78711
+ else if (cached3)
78712
+ key2 = u82(cached3);
78713
+ else
78714
+ key2 = newVaultKey();
78715
+ this.vaultKey = key2;
78716
+ this.persistence.saveDeviceKey(b64(key2));
78717
+ this.serverRecoverable = true;
78718
+ this.deviceVault = false;
78719
+ this.meta = { format: "TVK1", kdf: defaultKdfParams(), wrappedVaultKey: "", recoveryWrappedVaultKey: void 0, clientUpdatedAt: this.now() };
78720
+ this.enc = new Map((snap.items ?? []).map((i) => [i.id, i]));
78721
+ for (const it of localItems) {
78722
+ this.enc.set(it.id, await encryptItem(key2, it));
78723
+ this.dirty.add(it.id);
78724
+ }
78725
+ await this.decryptAll();
78726
+ this.persist();
78727
+ return { status: "unlocked" };
78728
+ }
78729
+ /** Merge the local vault's items INTO the account vault: unlock it with the master password (or
78730
+ * create it if the account has none), re-encrypt local items under the account key, and drop the
78731
+ * device vault. Returns a recovery key only when a NEW account vault was created. */
78732
+ async reconcileMergeIntoAccount(password, snap) {
78733
+ const localItems = this.items ? [...this.items.values()] : [];
78734
+ let accountKey;
78735
+ let recoveryKey;
78736
+ if (snap.meta) {
78737
+ const accountMeta = metaFromWire(snap.meta);
78738
+ accountKey = await unlockVault(password, accountMeta);
78739
+ this.meta = accountMeta;
78740
+ } else {
78741
+ const created = await createVault(password);
78742
+ const rec = await createRecoveryKey(created.vaultKey);
78743
+ this.meta = { ...created.meta, recoveryWrappedVaultKey: rec.recoveryWrappedVaultKey, clientUpdatedAt: this.now() };
78744
+ accountKey = created.vaultKey;
78745
+ recoveryKey = rec.recoveryKey;
78746
+ }
78747
+ this.enc = new Map((snap.items ?? []).map((i) => [i.id, i]));
78748
+ for (const it of localItems) {
78749
+ this.enc.set(it.id, await encryptItem(accountKey, it));
78750
+ this.dirty.add(it.id);
78751
+ }
78752
+ this.vaultKey = accountKey;
78753
+ this.deviceVault = false;
78754
+ this.serverRecoverable = false;
78755
+ this.persistence.clearDeviceKey();
78756
+ await this.decryptAll();
78757
+ this.persist();
78758
+ return { recoveryKey };
78759
+ }
78760
+ /** Set a master password on a no-password vault (mode 1 → 2, or mode 3 → 4). Same key, re-wrapped;
78761
+ * items unchanged. Drops the local key cache (now zero-knowledge) and — if it was server-recoverable
78762
+ * — schedules clearing the server's recoverable key copy on the next sync. Returns a recovery key. */
78763
+ async setMasterPassword(password) {
78764
+ if (!this.vaultKey)
78765
+ throw new Error("Vault is locked.");
78766
+ if (this.meta && this.meta.wrappedVaultKey && !this.serverRecoverable)
78767
+ throw new Error("A master password is already set.");
78768
+ const kdf = defaultKdfParams();
78769
+ const wrappedVaultKey = await wrapVaultKey(deriveMasterKey(password, kdf), this.vaultKey);
78770
+ const rec = await createRecoveryKey(this.vaultKey);
78771
+ const wasServerRecoverable = this.serverRecoverable;
78772
+ this.meta = { format: "TVK1", kdf, wrappedVaultKey, recoveryWrappedVaultKey: rec.recoveryWrappedVaultKey, clientUpdatedAt: this.now() };
78773
+ this.deviceVault = false;
78774
+ this.serverRecoverable = false;
78775
+ this.persistence.clearDeviceKey();
78776
+ if (wasServerRecoverable)
78777
+ this.pendingClearServerKey = true;
78778
+ this.persist();
78779
+ return { recoveryKey: rec.recoveryKey };
78328
78780
  }
78329
78781
  isUnlocked() {
78330
78782
  return this.vaultKey !== null;
@@ -78342,6 +78794,12 @@ var VaultStore = class {
78342
78794
  throw new Error("No vault to unlock");
78343
78795
  this.vaultKey = new Uint8Array(Buffer.from(base64Key, "base64"));
78344
78796
  await this.decryptAll();
78797
+ const live = [...this.enc.values()].filter((e) => !e.deleted).length;
78798
+ if (live > 0 && (this.items?.size ?? 0) === 0) {
78799
+ this.vaultKey = null;
78800
+ this.items = null;
78801
+ throw new Error("Key did not match this vault");
78802
+ }
78345
78803
  }
78346
78804
  persist() {
78347
78805
  this.persistence.save({ meta: this.meta, items: [...this.enc.values()] });
@@ -78386,7 +78844,8 @@ var VaultStore = class {
78386
78844
  if (enc.deleted)
78387
78845
  continue;
78388
78846
  try {
78389
- map2.set(enc.id, await decryptItem(key2, enc));
78847
+ const it = await decryptItem(key2, enc);
78848
+ map2.set(it.id, it);
78390
78849
  } catch {
78391
78850
  }
78392
78851
  }
@@ -78419,7 +78878,7 @@ var VaultStore = class {
78419
78878
  const existing = input.id ? items.get(input.id) : void 0;
78420
78879
  const { id: _drop, name, folderId, favorite, reprompt, notes, tags, ...content } = input;
78421
78880
  const item = {
78422
- id: input.id ?? randomUUID15(),
78881
+ id: input.id ?? randomUUID16(),
78423
78882
  name,
78424
78883
  folderId,
78425
78884
  favorite,
@@ -78448,7 +78907,7 @@ var VaultStore = class {
78448
78907
  const now2 = this.now();
78449
78908
  for (const d of drafts) {
78450
78909
  const { name, notes, favorite, tags, ...content } = d;
78451
- const item = { id: randomUUID15(), name, notes, favorite, tags, createdAt: now2, updatedAt: now2, ...content };
78910
+ const item = { id: randomUUID16(), name, notes, favorite, tags, createdAt: now2, updatedAt: now2, ...content };
78452
78911
  items.set(item.id, item);
78453
78912
  this.enc.set(item.id, await encryptItem(key2, item));
78454
78913
  this.dirty.add(item.id);
@@ -78474,6 +78933,8 @@ var VaultStore = class {
78474
78933
  }
78475
78934
  /** Adopt a newer remote meta (e.g. a master-password change on another device). */
78476
78935
  applyRemoteMeta(meta4) {
78936
+ if (this.deviceVault)
78937
+ return;
78477
78938
  if (meta4 && (!this.meta || (meta4.clientUpdatedAt ?? 0) > (this.meta.clientUpdatedAt ?? 0))) {
78478
78939
  this.meta = meta4;
78479
78940
  this.persist();
@@ -78531,9 +78992,18 @@ var VaultSync = class {
78531
78992
  this.getToken = getToken2;
78532
78993
  this.debounceMs = debounceMs;
78533
78994
  }
78995
+ /** A local NO-PASSWORD device vault (mode 1) has no account to sync with — its items are encrypted
78996
+ * under the on-disk device key, so pushing them would poison the account with undecryptable
78997
+ * ciphertext AND pulling the account meta would overwrite the device vault's null meta → orphan
78998
+ * the local items on the next lock/restart (the exact data-loss reconcileSignIn guards against).
78999
+ * Sign-in (reconcileSignIn/mergeIntoAccount) is what turns a device vault INTO an account vault;
79000
+ * until then, sync must stay OFF. Mirrors the extension/WebVaultClient `if (deviceVault) return`. */
79001
+ syncable() {
79002
+ return !!this.getToken() && !this.store.isDeviceVault();
79003
+ }
78534
79004
  /** Call after any local mutation (upsert/remove/import/meta change). Debounced. */
78535
79005
  markLocalChange() {
78536
- if (!this.getToken())
79006
+ if (!this.syncable())
78537
79007
  return;
78538
79008
  if (this.timer)
78539
79009
  clearTimeout(this.timer);
@@ -78543,13 +79013,19 @@ var VaultSync = class {
78543
79013
  /** Once per signed-in session (deduped by token): pull + reconcile, then push. */
78544
79014
  async ensureSynced() {
78545
79015
  const token3 = this.getToken();
78546
- if (!token3 || token3 === this.lastSyncedToken)
79016
+ if (!this.syncable() || token3 === this.lastSyncedToken)
78547
79017
  return;
78548
79018
  this.lastSyncedToken = token3;
78549
79019
  await this.pullAndReconcile().catch(() => {
78550
79020
  this.lastSyncedToken = null;
78551
79021
  });
78552
79022
  }
79023
+ /** Push the current cache (dirty items + meta, incl. mode-3 serverRecoverableKey) NOW — used right
79024
+ * after a sign-in reconcile / merge so the account is updated immediately, not on the debounce. */
79025
+ async pushNow() {
79026
+ await this.push().catch(() => {
79027
+ });
79028
+ }
78553
79029
  onSignedOut() {
78554
79030
  this.lastSyncedToken = null;
78555
79031
  this.lastMetaPushed = -1;
@@ -78573,7 +79049,7 @@ var VaultSync = class {
78573
79049
  }
78574
79050
  }
78575
79051
  async push() {
78576
- if (!this.getToken())
79052
+ if (!this.syncable())
78577
79053
  return;
78578
79054
  const dirty = this.store.takeDirty();
78579
79055
  if (dirty.length) {
@@ -78583,8 +79059,17 @@ var VaultSync = class {
78583
79059
  const { meta: meta4 } = this.store.metaForPush();
78584
79060
  const clock = meta4?.clientUpdatedAt ?? -1;
78585
79061
  if (meta4 && clock > this.lastMetaPushed) {
78586
- await this.api.putMeta(toWire(meta4));
79062
+ const wire = toWire(meta4);
79063
+ const srk = this.store.serverRecoverableKeyForPush();
79064
+ const clearing = !srk && this.store.hasPendingClearServerKey();
79065
+ if (srk)
79066
+ wire.serverRecoverableKey = srk;
79067
+ else if (clearing)
79068
+ wire.serverRecoverableKey = null;
79069
+ await this.api.putMeta(wire);
78587
79070
  this.lastMetaPushed = clock;
79071
+ if (clearing)
79072
+ this.store.clearPendingServerKeyFlag();
78588
79073
  }
78589
79074
  }
78590
79075
  };
@@ -78608,12 +79093,15 @@ function makeDaemonVaultApi(getOrigin, getToken2) {
78608
79093
 
78609
79094
  // ../../packages/host-core/dist/vault/persistence.js
78610
79095
  init_base_paths();
78611
- import { existsSync as existsSync21, mkdirSync as mkdirSync19, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "node:fs";
79096
+ import { existsSync as existsSync21, mkdirSync as mkdirSync19, readFileSync as readFileSync25, writeFileSync as writeFileSync17, unlinkSync as unlinkSync2 } from "node:fs";
78612
79097
  import { dirname as dirname19 } from "node:path";
78613
79098
  function vaultStoreFile() {
78614
79099
  return homePath(".tesseract", "vault", "vault.json");
78615
79100
  }
78616
- function makeVaultPersistence(file2 = vaultStoreFile) {
79101
+ function vaultDeviceKeyFile() {
79102
+ return homePath(".tesseract", "vault", "device-key");
79103
+ }
79104
+ function makeVaultPersistence(file2 = vaultStoreFile, deviceKeyFile = vaultDeviceKeyFile) {
78617
79105
  return {
78618
79106
  load() {
78619
79107
  try {
@@ -78633,6 +79121,27 @@ function makeVaultPersistence(file2 = vaultStoreFile) {
78633
79121
  writeFileSync17(file2(), JSON.stringify(state2), { mode: 384 });
78634
79122
  } catch {
78635
79123
  }
79124
+ },
79125
+ loadDeviceKey() {
79126
+ try {
79127
+ return existsSync21(deviceKeyFile()) ? readFileSync25(deviceKeyFile(), "utf8").trim() || null : null;
79128
+ } catch {
79129
+ return null;
79130
+ }
79131
+ },
79132
+ saveDeviceKey(base64Key) {
79133
+ try {
79134
+ mkdirSync19(dirname19(deviceKeyFile()), { recursive: true, mode: 448 });
79135
+ writeFileSync17(deviceKeyFile(), base64Key, { mode: 384 });
79136
+ } catch {
79137
+ }
79138
+ },
79139
+ clearDeviceKey() {
79140
+ try {
79141
+ if (existsSync21(deviceKeyFile()))
79142
+ unlinkSync2(deviceKeyFile());
79143
+ } catch {
79144
+ }
78636
79145
  }
78637
79146
  };
78638
79147
  }
@@ -78640,7 +79149,7 @@ function makeVaultPersistence(file2 = vaultStoreFile) {
78640
79149
  // ../../packages/host-core/dist/vault/biometric.js
78641
79150
  init_base_paths();
78642
79151
  import { existsSync as existsSync22, mkdirSync as mkdirSync20, readFileSync as readFileSync26, rmSync as rmSync6, writeFileSync as writeFileSync18 } from "node:fs";
78643
- import { join as join43 } from "node:path";
79152
+ import { join as join44 } from "node:path";
78644
79153
  async function electronBio() {
78645
79154
  if (!electronModule())
78646
79155
  return null;
@@ -78661,7 +79170,7 @@ function bioDir() {
78661
79170
  return d;
78662
79171
  }
78663
79172
  function bioFile(id) {
78664
- return join43(bioDir(), `${String(id).replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64)}.enc`);
79173
+ return join44(bioDir(), `${String(id).replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64)}.enc`);
78665
79174
  }
78666
79175
  async function biometricAvailable() {
78667
79176
  if (process.platform !== "darwin")
@@ -78709,13 +79218,15 @@ function settingsFile() {
78709
79218
  var VaultManager = class {
78710
79219
  store;
78711
79220
  sync;
79221
+ api;
78712
79222
  autoLockMinutes = 0;
78713
79223
  idleTimer = null;
78714
79224
  constructor() {
78715
79225
  this.store = new VaultStore(makeVaultPersistence());
78716
- this.sync = new VaultSync(this.store, makeDaemonVaultApi(() => readCredentials()?.apiOrigin ?? "", () => readCredentials()?.accessToken ?? null), () => readCredentials()?.accessToken ?? null);
79226
+ this.api = makeDaemonVaultApi(() => readCredentials()?.apiOrigin ?? "", () => readCredentials()?.accessToken ?? null);
79227
+ this.sync = new VaultSync(this.store, this.api, () => readCredentials()?.accessToken ?? null);
78717
79228
  this.autoLockMinutes = this.loadAutoLock();
78718
- void this.sync.ensureSynced().catch(() => {
79229
+ void this.ensureVault().then(() => this.sync.ensureSynced()).catch(() => {
78719
79230
  });
78720
79231
  }
78721
79232
  // ── auto-lock ────────────────────────────────────────────────────────────────────
@@ -78764,12 +79275,49 @@ var VaultManager = class {
78764
79275
  exists: s.exists,
78765
79276
  unlocked: s.unlocked,
78766
79277
  itemCount: s.itemCount,
78767
- hasMasterPassword: s.exists,
79278
+ hasMasterPassword: !!s.hasMasterPassword,
79279
+ deviceVault: !!s.deviceVault,
79280
+ serverRecoverable: !!s.serverRecoverable,
78768
79281
  biometricAvailable: await biometricAvailable(),
78769
79282
  biometricEnrolled: id ? biometricEnrolled(id) : false,
78770
79283
  autoLockMinutes: this.autoLockMinutes
78771
79284
  };
78772
79285
  }
79286
+ // ── zero-friction lifecycle (device vault / sign-in reconcile / merge) ─────────────
79287
+ /** Auto-create/auto-unlock the no-password device vault (mode 1) or auto-unlock mode 3. Idempotent;
79288
+ * called on construction + on the app's vault-tab mount. Mirrors the extension's ensureLoaded. */
79289
+ async ensureVault() {
79290
+ await this.store.ensureVault();
79291
+ if (this.store.isServerRecoverable())
79292
+ void this.sync.ensureSynced().catch(() => {
79293
+ });
79294
+ this.touch();
79295
+ }
79296
+ /** On sign-in: reconcile local ↔ account. `needsMasterPassword` → the app collects it + calls
79297
+ * mergeIntoAccount; `unlocked` → mode 3 established from the device key + local items folded in. */
79298
+ async signIn() {
79299
+ const snap = await this.api.getSnapshot();
79300
+ const r = await this.store.reconcileSignIn(snap);
79301
+ if (r.status === "unlocked")
79302
+ await this.sync.pushNow();
79303
+ this.touch();
79304
+ return r;
79305
+ }
79306
+ /** Merge the local vault into the account (unlock/create it with `masterPassword`), then sync. */
79307
+ async mergeIntoAccount(masterPassword) {
79308
+ const snap = await this.api.getSnapshot();
79309
+ const r = await this.store.reconcileMergeIntoAccount(masterPassword, snap);
79310
+ await this.sync.pushNow();
79311
+ this.touch();
79312
+ return r;
79313
+ }
79314
+ /** Optional upgrade: set a master password on a no-password vault (mode 1→2 or 3→4). */
79315
+ async setMasterPassword(masterPassword) {
79316
+ const r = await this.store.setMasterPassword(masterPassword);
79317
+ await this.sync.pushNow();
79318
+ this.touch();
79319
+ return r;
79320
+ }
78773
79321
  // ── lifecycle ────────────────────────────────────────────────────────────────────
78774
79322
  async create(masterPassword) {
78775
79323
  const res = await this.store.create(masterPassword);
@@ -78820,6 +79368,20 @@ var VaultManager = class {
78820
79368
  });
78821
79369
  this.touch();
78822
79370
  }
79371
+ // ── cross-device unlock (phone approve → desktop unlock) ───────────────────────────
79372
+ /** The (non-secret) vault crypto header for the QR-unlock payload — kdf + wrapped keys, NO raw key.
79373
+ * Returned to the renderer so it can render the QR; the raw key never leaves the daemon. */
79374
+ exportMetaWire() {
79375
+ const m = this.store.getMeta();
79376
+ return m ? { format: m.format, kdf: m.kdf, wrappedVaultKey: m.wrappedVaultKey, recoveryWrappedVaultKey: m.recoveryWrappedVaultKey ?? null, updatedAt: m.clientUpdatedAt ?? 0 } : null;
79377
+ }
79378
+ /** Adopt a raw vault key recovered cross-device (the phone approved + sealed it back over the relay). */
79379
+ async adoptKey(base64Key) {
79380
+ await this.store.adoptVaultKeyBase64(base64Key);
79381
+ void this.sync.ensureSynced().catch(() => {
79382
+ });
79383
+ this.touch();
79384
+ }
78823
79385
  disableBiometric() {
78824
79386
  const id = this.accountId();
78825
79387
  if (id)
@@ -78966,6 +79528,9 @@ var VaultManager = class {
78966
79528
  onSignedOut() {
78967
79529
  this.sync.onSignedOut();
78968
79530
  this.lock();
79531
+ this.store.resetForSignOut();
79532
+ void this.store.ensureVault().catch(() => {
79533
+ });
78969
79534
  }
78970
79535
  };
78971
79536
  var singleton = null;
@@ -78996,7 +79561,7 @@ function originAllowed(origin) {
78996
79561
  }
78997
79562
  }
78998
79563
  async function startLocalServer(opts) {
78999
- const log5 = opts.log ?? (() => {
79564
+ const log9 = opts.log ?? (() => {
79000
79565
  });
79001
79566
  const wss3 = new import_websocket_server.default({ host: "127.0.0.1", port: 0 });
79002
79567
  await new Promise((resolve12, reject) => {
@@ -79005,7 +79570,7 @@ async function startLocalServer(opts) {
79005
79570
  });
79006
79571
  const addr = wss3.address();
79007
79572
  const port = typeof addr === "object" && addr ? addr.port : 0;
79008
- log5(`local listener on 127.0.0.1:${port}`);
79573
+ log9(`local listener on 127.0.0.1:${port}`);
79009
79574
  wss3.on("connection", (ws, req) => {
79010
79575
  const url2 = new URL(req.url ?? "/", "http://127.0.0.1");
79011
79576
  const origin = req.headers.origin ?? void 0;
@@ -79365,6 +79930,19 @@ async function startLocalServer(opts) {
79365
79930
  case "status":
79366
79931
  result = await vm.status();
79367
79932
  break;
79933
+ case "ensureVault":
79934
+ await vm.ensureVault();
79935
+ result = { ok: true };
79936
+ break;
79937
+ case "signIn":
79938
+ result = await vm.signIn();
79939
+ break;
79940
+ case "mergeIntoAccount":
79941
+ result = await vm.mergeIntoAccount(String(a[0]));
79942
+ break;
79943
+ case "setMasterPassword":
79944
+ result = await vm.setMasterPassword(String(a[0]));
79945
+ break;
79368
79946
  case "create":
79369
79947
  result = await vm.create(String(a[0]));
79370
79948
  break;
@@ -79380,6 +79958,13 @@ async function startLocalServer(opts) {
79380
79958
  await vm.unlockBiometric();
79381
79959
  result = { ok: true };
79382
79960
  break;
79961
+ case "exportMeta":
79962
+ result = vm.exportMetaWire();
79963
+ break;
79964
+ case "adoptKey":
79965
+ await vm.adoptKey(String(a[0]));
79966
+ result = { ok: true };
79967
+ break;
79383
79968
  case "lock":
79384
79969
  vm.lock();
79385
79970
  result = { ok: true };
@@ -79482,18 +80067,18 @@ async function startLocalServer(opts) {
79482
80067
 
79483
80068
  // ../../packages/host-core/dist/daemon-runtime.js
79484
80069
  init_base_paths();
79485
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync25, existsSync as existsSync28, mkdirSync as mkdirSync26, unlinkSync as unlinkSync3, chmodSync as chmodSync5 } from "node:fs";
80070
+ import { readFileSync as readFileSync30, writeFileSync as writeFileSync25, existsSync as existsSync28, mkdirSync as mkdirSync26, unlinkSync as unlinkSync4, chmodSync as chmodSync5 } from "node:fs";
79486
80071
  import { execFileSync as execFileSync4 } from "node:child_process";
79487
- import { join as join50 } from "node:path";
80072
+ import { join as join54 } from "node:path";
79488
80073
  import { randomBytes as randomBytes8 } from "node:crypto";
79489
80074
  import { hostname as hostname4 } from "node:os";
79490
80075
 
79491
80076
  // ../../packages/host-core/dist/device-identity.js
79492
- import { join as join44 } from "node:path";
80077
+ import { join as join45 } from "node:path";
79493
80078
  import { readFileSync as readFileSync28, writeFileSync as writeFileSync20, existsSync as existsSync23, mkdirSync as mkdirSync22, renameSync as renameSync8, chmodSync as chmodSync3 } from "node:fs";
79494
80079
  var cached2 = null;
79495
80080
  function identityPath() {
79496
- return join44(daemonDir2(), "identity.json");
80081
+ return join45(daemonDir2(), "identity.json");
79497
80082
  }
79498
80083
  async function loadOrCreateDeviceIdentity() {
79499
80084
  if (cached2)
@@ -79521,6 +80106,40 @@ async function loadOrCreateDeviceIdentity() {
79521
80106
  cached2 = kp;
79522
80107
  return kp;
79523
80108
  }
80109
+ var sealingCached = null;
80110
+ function sealingPath() {
80111
+ return join45(daemonDir2(), "sealing.json");
80112
+ }
80113
+ var EC_DH = { name: "ECDH", namedCurve: "P-256" };
80114
+ async function loadOrCreateSealingKey() {
80115
+ if (sealingCached)
80116
+ return sealingCached;
80117
+ const { subtle: subtle4 } = globalThis.crypto;
80118
+ try {
80119
+ const stored = JSON.parse(readFileSync28(sealingPath(), "utf-8"));
80120
+ if (stored?.privateJwk && stored.publicRaw) {
80121
+ const privateKey = await subtle4.importKey("jwk", stored.privateJwk, EC_DH, true, ["deriveBits"]);
80122
+ sealingCached = { privateKey, publicRaw: unb64u(stored.publicRaw) };
80123
+ return sealingCached;
80124
+ }
80125
+ } catch {
80126
+ }
80127
+ const kp = await subtle4.generateKey(EC_DH, true, ["deriveBits"]);
80128
+ const publicRaw = new Uint8Array(await subtle4.exportKey("raw", kp.publicKey));
80129
+ const privateJwk = await subtle4.exportKey("jwk", kp.privateKey);
80130
+ const dir2 = daemonDir2();
80131
+ if (!existsSync23(dir2))
80132
+ mkdirSync22(dir2, { recursive: true, mode: 448 });
80133
+ const tmp = `${sealingPath()}.tmp`;
80134
+ writeFileSync20(tmp, JSON.stringify({ privateJwk, publicRaw: b64u(publicRaw), alg: "P256-ECDH" }, null, 2), { mode: 384 });
80135
+ renameSync8(tmp, sealingPath());
80136
+ try {
80137
+ chmodSync3(sealingPath(), 384);
80138
+ } catch {
80139
+ }
80140
+ sealingCached = { privateKey: kp.privateKey, publicRaw };
80141
+ return sealingCached;
80142
+ }
79524
80143
  var DEVICES_BASE = (apiOrigin) => `${apiOrigin.replace(/\/$/, "")}/api/core/tesseract/devices`;
79525
80144
  async function registerDeviceKey(opts) {
79526
80145
  try {
@@ -79532,7 +80151,8 @@ async function registerDeviceKey(opts) {
79532
80151
  identityPub: b64u(opts.publicRaw),
79533
80152
  alg: "P256-ECDSA",
79534
80153
  tlsFingerprint256: opts.tlsFingerprint256,
79535
- label: opts.label
80154
+ label: opts.label,
80155
+ ...opts.sealingPub ? { sealingPub: opts.sealingPub, sealingPubSig: opts.sealingPubSig } : {}
79536
80156
  })
79537
80157
  });
79538
80158
  return r.ok;
@@ -79569,12 +80189,584 @@ async function getPeerIdentityPub(opts) {
79569
80189
  }
79570
80190
  }
79571
80191
 
80192
+ // ../../packages/host-core/dist/drops/receive-ui.js
80193
+ import { basename as basename10, dirname as dirname21 } from "node:path";
80194
+ var log5 = (...a) => console.log("[drops]", ...a);
80195
+ function formatBytes(n) {
80196
+ if (!Number.isFinite(n) || n <= 0)
80197
+ return "0 B";
80198
+ const units = ["B", "KB", "MB", "GB", "TB"];
80199
+ const i = Math.min(units.length - 1, Math.floor(Math.log(n) / Math.log(1024)));
80200
+ const v = n / 1024 ** i;
80201
+ return `${i === 0 ? v : v.toFixed(v < 10 ? 1 : 0)} ${units[i]}`;
80202
+ }
80203
+ function initDropReceiveUi(deps = {}) {
80204
+ const live = /* @__PURE__ */ new Map();
80205
+ const clearActivity = () => {
80206
+ if (live.size === 0)
80207
+ setTrayActivity(null);
80208
+ };
80209
+ const describe3 = (e) => {
80210
+ const from = deps.hostLabel?.(e.fromHost) ?? "another machine";
80211
+ const what = e.singleFile || e.totalFiles === 1 ? basename10(e.destPath) : `${e.totalFiles} items`;
80212
+ return `${what} from ${from}`;
80213
+ };
80214
+ setInboundTransferObserver((e) => {
80215
+ try {
80216
+ if (e.phase === "begin") {
80217
+ live.set(e.transferId, { destPath: e.destPath, singleFile: e.singleFile, totalFiles: e.totalFiles, lastTrayAt: 0 });
80218
+ setTrayActivity(`receiving ${e.totalFiles === 1 ? "1 file" : `${e.totalFiles} files`}`);
80219
+ return;
80220
+ }
80221
+ const l = live.get(e.transferId);
80222
+ if (e.phase === "progress") {
80223
+ if (!l)
80224
+ return;
80225
+ const now2 = Date.now();
80226
+ if (now2 - l.lastTrayAt < 1e3)
80227
+ return;
80228
+ l.lastTrayAt = now2;
80229
+ const pct = e.totalBytes > 0 ? Math.min(99, Math.floor(e.bytesDone / e.totalBytes * 100)) : 0;
80230
+ setTrayActivity(`receiving ${formatBytes(e.totalBytes)} (${pct}%)`);
80231
+ return;
80232
+ }
80233
+ live.delete(e.transferId);
80234
+ clearActivity();
80235
+ if (e.phase === "error") {
80236
+ log5(`transfer ${e.transferId} failed: ${e.message ?? "unknown error"}`);
80237
+ if (deps.appIsUp?.())
80238
+ return;
80239
+ daemonNotify({ title: "Couldn't receive files", body: e.message ?? "The transfer failed.", silent: true });
80240
+ return;
80241
+ }
80242
+ const destPath = e.destPath || l?.destPath || "";
80243
+ if (!destPath)
80244
+ return;
80245
+ log5(`received ${e.totalFiles} item(s), ${formatBytes(e.bytesDone)} \u2192 ${destPath}`);
80246
+ if (deps.appIsUp?.())
80247
+ return;
80248
+ const reveal = () => {
80249
+ const el2 = electronModule();
80250
+ if (!el2)
80251
+ return;
80252
+ try {
80253
+ el2.shell.showItemInFolder(destPath);
80254
+ } catch (err2) {
80255
+ log5("reveal failed:", err2 instanceof Error ? err2.message : String(err2));
80256
+ }
80257
+ };
80258
+ const open5 = () => {
80259
+ const el2 = electronModule();
80260
+ if (!el2)
80261
+ return;
80262
+ void el2.shell.openPath(destPath).catch(() => {
80263
+ });
80264
+ };
80265
+ daemonNotify({
80266
+ title: "Files received",
80267
+ body: `${describe3(e)} \xB7 ${formatBytes(e.bytesDone)}`,
80268
+ actions: [{ id: "reveal", label: "Show in Finder" }, { id: "open", label: "Open" }],
80269
+ onAction: (id) => id === "open" ? open5() : reveal(),
80270
+ // Clicking the banner body is the only reliable action on macOS (buttons need
80271
+ // an expanded banner and are suppressed under Focus), so make it do the useful
80272
+ // thing rather than nothing.
80273
+ onClick: reveal
80274
+ });
80275
+ } catch (err2) {
80276
+ log5("receive-ui error:", err2 instanceof Error ? err2.message : String(err2));
80277
+ }
80278
+ });
80279
+ }
80280
+
80281
+ // ../../packages/host-core/dist/drops/handler.js
80282
+ import { basename as basename11 } from "node:path";
80283
+
80284
+ // ../../packages/host-core/dist/drops/paths.js
80285
+ init_base_paths();
80286
+ import { mkdir as mkdir11 } from "node:fs/promises";
80287
+ import { join as join46 } from "node:path";
80288
+
80289
+ // ../../packages/host-core/dist/drops/names.js
80290
+ var WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
80291
+ var CONTROL = /[\u0000-\u001f\u007f-\u009f]/g;
80292
+ var BIDI = /[\u200e\u200f\u202a-\u202e\u2066-\u2069]/g;
80293
+ var MAX_NAME_BYTES = 200;
80294
+ var MAX_EXT_BYTES = 32;
80295
+ var MAX_DEPTH = 32;
80296
+ var utf82 = new TextEncoder();
80297
+ var byteLen = (s) => utf82.encode(s).length;
80298
+ function capBytes(stem, ext, max) {
80299
+ if (byteLen(stem + ext) <= max)
80300
+ return stem + ext;
80301
+ const budget = Math.max(1, max - byteLen(ext));
80302
+ let out = "";
80303
+ let used = 0;
80304
+ for (const ch of stem) {
80305
+ const n = byteLen(ch);
80306
+ if (used + n > budget)
80307
+ break;
80308
+ out += ch;
80309
+ used += n;
80310
+ }
80311
+ return out + ext;
80312
+ }
80313
+ function sanitizeDropName(raw, fallback = "file") {
80314
+ let s = String(raw ?? "").split(/[/\\]/).pop() ?? "";
80315
+ s = s.replace(CONTROL, "").replace(BIDI, "").normalize("NFC");
80316
+ s = s.replace(/[ .]+$/g, "");
80317
+ s = s.replace(/^\.+/, "");
80318
+ s = s.trim();
80319
+ if (!s)
80320
+ return fallback;
80321
+ const dot = s.lastIndexOf(".");
80322
+ let stem = dot > 0 ? s.slice(0, dot) : s;
80323
+ let ext = dot > 0 ? s.slice(dot) : "";
80324
+ if (byteLen(ext) > MAX_EXT_BYTES) {
80325
+ stem = s;
80326
+ ext = "";
80327
+ }
80328
+ if (!stem)
80329
+ return fallback;
80330
+ if (WINDOWS_RESERVED.test(stem))
80331
+ stem = `_${stem}`;
80332
+ const out = capBytes(stem, ext, MAX_NAME_BYTES);
80333
+ return out || fallback;
80334
+ }
80335
+ function sanitizeDropRel(rel) {
80336
+ const parts = String(rel ?? "").split("/").filter((p) => p !== "");
80337
+ if (!parts.length || parts.length > MAX_DEPTH)
80338
+ return null;
80339
+ const out = [];
80340
+ for (const p of parts) {
80341
+ if (p === "." || p === "..")
80342
+ return null;
80343
+ const clean = sanitizeDropName(p, "");
80344
+ if (!clean)
80345
+ return null;
80346
+ out.push(clean);
80347
+ }
80348
+ return out.join("/");
80349
+ }
80350
+ async function uniqueDropPath(abs) {
80351
+ const { access } = await import("node:fs/promises");
80352
+ const exists2 = async (p) => access(p).then(() => true, () => false);
80353
+ if (!await exists2(abs))
80354
+ return abs;
80355
+ const dir2 = abs.slice(0, abs.length - basenameOf(abs).length);
80356
+ const name = basenameOf(abs);
80357
+ const dot = name.lastIndexOf(".");
80358
+ const stem = dot > 0 ? name.slice(0, dot) : name;
80359
+ const ext = dot > 0 ? name.slice(dot) : "";
80360
+ for (let i = 2; i < 1e4; i++) {
80361
+ const candidate = `${dir2}${stem} ${i}${ext}`;
80362
+ if (!await exists2(candidate))
80363
+ return candidate;
80364
+ }
80365
+ return `${dir2}${stem} ${Date.now()}${ext}`;
80366
+ }
80367
+ function basenameOf(p) {
80368
+ const i = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"));
80369
+ return i === -1 ? p : p.slice(i + 1);
80370
+ }
80371
+
80372
+ // ../../packages/host-core/dist/drops/paths.js
80373
+ function resolveDropsRoot() {
80374
+ const root = resolveHostRoot();
80375
+ const preferred = dropsDir();
80376
+ return isInsideRoot(preferred, root) ? preferred : join46(root, "Drops");
80377
+ }
80378
+ async function ensureDropsRoot() {
80379
+ const dir2 = resolveDropsRoot();
80380
+ await mkdir11(dir2, { recursive: true });
80381
+ return dir2;
80382
+ }
80383
+ async function ensureDropDir(opts) {
80384
+ const root = await ensureDropsRoot();
80385
+ if (opts.singleItem && !opts.sender)
80386
+ return root;
80387
+ const parts = [root];
80388
+ if (opts.sender)
80389
+ parts.push(sanitizeDropName(opts.sender, "Unknown sender"));
80390
+ if (!opts.singleItem && opts.label)
80391
+ parts.push(sanitizeDropName(opts.label, "Files"));
80392
+ const dir2 = join46(...parts);
80393
+ await mkdir11(dir2, { recursive: true });
80394
+ return dir2;
80395
+ }
80396
+
80397
+ // ../../packages/host-core/dist/drops/handler.js
80398
+ var log6 = (...a) => console.log("[drops]", ...a);
80399
+ var CONSENT_TIMEOUT_MS = 12e4;
80400
+ function createDropHandler(deps) {
80401
+ const pending2 = /* @__PURE__ */ new Map();
80402
+ const seen = /* @__PURE__ */ new Set();
80403
+ async function claim(offer) {
80404
+ const hostId = deps.ownHostId();
80405
+ if (!hostId) {
80406
+ log6(`drop ${offer.dropId}: no hostId yet, leaving it to another device`);
80407
+ return;
80408
+ }
80409
+ const singleItem = offer.totalFiles === 1;
80410
+ const destDir = await ensureDropDir({
80411
+ singleItem,
80412
+ // Cross-user drops are grouped by sender so "delete everything they sent me" is
80413
+ // one action. Safe only because the name is sanitized on the way in.
80414
+ sender: offer.kind === "peer" ? offer.from.email || offer.from.name : void 0,
80415
+ label: offer.label
80416
+ });
80417
+ let res;
80418
+ try {
80419
+ res = await deps.api(`/${offer.dropId}/claim`, {
80420
+ method: "POST",
80421
+ body: {
80422
+ deviceId: deps.deviceId(),
80423
+ hostId,
80424
+ // We run the transfer engine, so a LIVE drop is ours to take.
80425
+ caps: ["fs", "xfer"],
80426
+ destParent: destDir,
80427
+ destName: sanitizeDropName(singleItem ? basename11(offer.label) : offer.label, "Files")
80428
+ }
80429
+ });
80430
+ } catch (e) {
80431
+ log6(`drop ${offer.dropId}: claim failed:`, e instanceof Error ? e.message : String(e));
80432
+ return;
80433
+ }
80434
+ if (!res.won) {
80435
+ log6(`drop ${offer.dropId}: ${res.reason ?? "not available"}`);
80436
+ return;
80437
+ }
80438
+ if (res.transport === "blob" && deps.fetchBlobDrop) {
80439
+ try {
80440
+ await deps.fetchBlobDrop({
80441
+ dropId: offer.dropId,
80442
+ destDir,
80443
+ claim: res,
80444
+ sender: offer.kind === "peer" ? offer.from.email || offer.from.name : void 0
80445
+ });
80446
+ await deps.api(`/${offer.dropId}/complete`, { method: "POST", body: { ok: true } });
80447
+ } catch (e) {
80448
+ const message = e instanceof Error ? e.message : String(e);
80449
+ log6(`drop ${offer.dropId}: download failed:`, message);
80450
+ await deps.api(`/${offer.dropId}/complete`, { method: "POST", body: { ok: false, error: message } }).catch(() => {
80451
+ });
80452
+ }
80453
+ }
80454
+ }
80455
+ async function handleOffer(offer) {
80456
+ if (seen.has(offer.dropId))
80457
+ return;
80458
+ seen.add(offer.dropId);
80459
+ if (seen.size > 500)
80460
+ seen.clear();
80461
+ if (offer.autoAccept) {
80462
+ await claim(offer);
80463
+ return;
80464
+ }
80465
+ if (!deps.canPrompt()) {
80466
+ log6(`drop ${offer.dropId}: headless, staying silent`);
80467
+ return;
80468
+ }
80469
+ const ac = new AbortController();
80470
+ pending2.set(offer.dropId, ac);
80471
+ const timer = setTimeout(() => ac.abort(), CONSENT_TIMEOUT_MS);
80472
+ let accepted = false;
80473
+ try {
80474
+ accepted = await deps.confirm({
80475
+ title: "Accept files?",
80476
+ // Sender identity is the SERVER-RESOLVED one. Rendering a sender-supplied
80477
+ // display name here would make "IT Department ✓" a one-line phishing kit.
80478
+ // No thumbnails and no raw filenames: the offer rings every device the user
80479
+ // owns, so the preview is the harassment payload.
80480
+ body: `${offer.from.email || offer.from.name} wants to send you ${offer.totalFiles === 1 ? "1 file" : `${offer.totalFiles} files`} (${formatBytes(offer.totalBytes)}).`
80481
+ }, ac.signal);
80482
+ } finally {
80483
+ clearTimeout(timer);
80484
+ pending2.delete(offer.dropId);
80485
+ }
80486
+ if (ac.signal.aborted) {
80487
+ log6(`drop ${offer.dropId}: prompt withdrawn`);
80488
+ return;
80489
+ }
80490
+ if (!accepted) {
80491
+ await deps.api(`/${offer.dropId}/decline`, { method: "POST" }).catch(() => {
80492
+ });
80493
+ return;
80494
+ }
80495
+ await claim(offer);
80496
+ }
80497
+ return {
80498
+ onFrame(msg) {
80499
+ switch (msg.type) {
80500
+ case "drop:offer":
80501
+ void handleOffer(msg).catch((e) => log6("offer failed:", e instanceof Error ? e.message : String(e)));
80502
+ return;
80503
+ case "drop:dismiss":
80504
+ pending2.get(msg.dropId)?.abort();
80505
+ pending2.delete(msg.dropId);
80506
+ seen.add(msg.dropId);
80507
+ return;
80508
+ case "drop:claimed":
80509
+ if (msg.senderHostId && msg.senderHostId === deps.ownHostId())
80510
+ deps.startLiveSend?.(msg);
80511
+ return;
80512
+ default:
80513
+ return;
80514
+ }
80515
+ },
80516
+ /** Catches offers that arrived while this device was offline. */
80517
+ async reconcile() {
80518
+ try {
80519
+ const rows = await deps.api("/inbox");
80520
+ for (const r of rows) {
80521
+ if (r.status && r.status !== "PENDING")
80522
+ continue;
80523
+ void handleOffer({ ...r, type: "drop:offer" });
80524
+ }
80525
+ } catch (e) {
80526
+ log6("inbox reconcile failed:", e instanceof Error ? e.message : String(e));
80527
+ }
80528
+ }
80529
+ };
80530
+ }
80531
+
80532
+ // ../../packages/host-core/dist/drops/blob-fetch.js
80533
+ import { createWriteStream as createWriteStream2 } from "node:fs";
80534
+ import { mkdir as mkdir12, rm as rm4, chmod as chmod3 } from "node:fs/promises";
80535
+ import { dirname as dirname22, join as join47 } from "node:path";
80536
+ import { Readable as Readable2 } from "node:stream";
80537
+ import { pipeline } from "node:stream/promises";
80538
+
80539
+ // ../../packages/host-core/dist/drops/crypto.js
80540
+ import { Transform } from "node:stream";
80541
+ var MAGIC = Buffer.from("TSD1", "ascii");
80542
+ var NONCE_BYTES = 12;
80543
+ var HEADER_BYTES = MAGIC.length + NONCE_BYTES;
80544
+ var FRAME_BYTES = 1024 * 1024;
80545
+ var TAG_BYTES = 16;
80546
+ var MAX_FRAME_CIPHERTEXT = FRAME_BYTES + TAG_BYTES + 1024;
80547
+ var subtle3 = () => globalThis.crypto.subtle;
80548
+ var bs3 = (b) => b;
80549
+ async function importKey(raw) {
80550
+ return subtle3().importKey("raw", bs3(raw), { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
80551
+ }
80552
+ function nonceFor(base, index) {
80553
+ const n = Buffer.from(base);
80554
+ n[8] ^= index >>> 24 & 255;
80555
+ n[9] ^= index >>> 16 & 255;
80556
+ n[10] ^= index >>> 8 & 255;
80557
+ n[11] ^= index & 255;
80558
+ return n;
80559
+ }
80560
+ function aadFor(index) {
80561
+ const a = Buffer.alloc(4);
80562
+ a.writeUInt32BE(index >>> 0, 0);
80563
+ return a;
80564
+ }
80565
+ var ByteQueue = class {
80566
+ chunks = [];
80567
+ length = 0;
80568
+ push(b) {
80569
+ if (b.length) {
80570
+ this.chunks.push(b);
80571
+ this.length += b.length;
80572
+ }
80573
+ }
80574
+ /** The first `n` bytes, or null when we don't have them yet. Does not consume. */
80575
+ peek(n) {
80576
+ if (this.length < n)
80577
+ return null;
80578
+ const first = this.chunks[0];
80579
+ if (first.length >= n)
80580
+ return first.subarray(0, n);
80581
+ const out = Buffer.allocUnsafe(n);
80582
+ let off = 0;
80583
+ for (const c of this.chunks) {
80584
+ const take = Math.min(c.length, n - off);
80585
+ c.copy(out, off, 0, take);
80586
+ off += take;
80587
+ if (off === n)
80588
+ break;
80589
+ }
80590
+ return out;
80591
+ }
80592
+ consume(n) {
80593
+ let left = n;
80594
+ while (left > 0) {
80595
+ const c = this.chunks[0];
80596
+ if (c.length <= left) {
80597
+ left -= c.length;
80598
+ this.length -= c.length;
80599
+ this.chunks.shift();
80600
+ } else {
80601
+ this.chunks[0] = c.subarray(left);
80602
+ this.length -= left;
80603
+ left = 0;
80604
+ }
80605
+ }
80606
+ }
80607
+ };
80608
+ function decryptStream(source, keyRaw) {
80609
+ let key2 = null;
80610
+ let index = 0;
80611
+ let base = null;
80612
+ const q = new ByteQueue();
80613
+ let sawEnd = false;
80614
+ const t = new Transform({
80615
+ async transform(chunk, _enc, cb) {
80616
+ try {
80617
+ q.push(chunk);
80618
+ if (!base) {
80619
+ const head = q.peek(HEADER_BYTES);
80620
+ if (!head) {
80621
+ cb();
80622
+ return;
80623
+ }
80624
+ if (!head.subarray(0, MAGIC.length).equals(MAGIC))
80625
+ throw new Error("not an encrypted drop");
80626
+ base = Buffer.from(head.subarray(MAGIC.length, HEADER_BYTES));
80627
+ q.consume(HEADER_BYTES);
80628
+ key2 = await importKey(keyRaw);
80629
+ }
80630
+ while (!sawEnd) {
80631
+ const lenBuf = q.peek(4);
80632
+ if (!lenBuf)
80633
+ break;
80634
+ const len = lenBuf.readUInt32BE(0);
80635
+ if (len === 0) {
80636
+ sawEnd = true;
80637
+ q.consume(4);
80638
+ break;
80639
+ }
80640
+ if (len > MAX_FRAME_CIPHERTEXT)
80641
+ throw new Error("encrypted frame is implausibly large");
80642
+ const frame = q.peek(4 + len);
80643
+ if (!frame)
80644
+ break;
80645
+ const plain = Buffer.from(await subtle3().decrypt({ name: "AES-GCM", iv: bs3(nonceFor(base, index)), additionalData: bs3(aadFor(index)) }, key2, bs3(frame.subarray(4))));
80646
+ this.push(plain);
80647
+ index += 1;
80648
+ q.consume(4 + len);
80649
+ }
80650
+ cb();
80651
+ } catch (e) {
80652
+ cb(e);
80653
+ }
80654
+ },
80655
+ flush(cb) {
80656
+ cb(sawEnd ? null : new Error("the transfer ended early (incomplete or tampered)"));
80657
+ }
80658
+ });
80659
+ source.on("error", (e) => t.destroy(e));
80660
+ source.pipe(t);
80661
+ return t;
80662
+ }
80663
+
80664
+ // ../../packages/host-core/dist/drops/quarantine.js
80665
+ import { execFile as execFile5 } from "node:child_process";
80666
+ import { writeFile as writeFile10 } from "node:fs/promises";
80667
+ import { promisify as promisify4 } from "node:util";
80668
+ var exec = promisify4(execFile5);
80669
+ async function markQuarantined(path6, sourceLabel) {
80670
+ try {
80671
+ if (process.platform === "darwin") {
80672
+ const stamp2 = Math.floor(Date.now() / 1e3).toString(16);
80673
+ const agent = sourceLabel.replace(/[^\w.@-]/g, "_").slice(0, 64) || "Tesseract";
80674
+ await exec("xattr", ["-w", "com.apple.quarantine", `0081;${stamp2};Tesseract;${agent}`, path6]);
80675
+ return;
80676
+ }
80677
+ if (process.platform === "win32") {
80678
+ await writeFile10(`${path6}:Zone.Identifier`, "[ZoneTransfer]\r\nZoneId=3\r\n", "utf-8");
80679
+ }
80680
+ } catch {
80681
+ }
80682
+ }
80683
+
80684
+ // ../../packages/host-core/dist/drops/blob-fetch.js
80685
+ var log7 = (...a) => console.log("[drops]", ...a);
80686
+ async function fetchBlobDrop(opts) {
80687
+ const fetchFn = opts.fetchImpl ?? fetch;
80688
+ const written = [];
80689
+ let contentKey;
80690
+ if (opts.sealedKey) {
80691
+ if (!opts.sealingPrivate)
80692
+ throw new Error("This device has no sealing key, so it cannot open the transfer.");
80693
+ const open5 = opts.openSeal ?? openFromPrivateKey;
80694
+ contentKey = await open5(await opts.sealingPrivate(), opts.sealedKey, new TextEncoder().encode(opts.dropId));
80695
+ }
80696
+ const totalBytes = opts.items.reduce((n, i) => n + (i.kind === "file" ? i.size : 0), 0);
80697
+ let bytesDone = 0;
80698
+ try {
80699
+ for (const item of opts.items) {
80700
+ if (item.kind !== "dir")
80701
+ continue;
80702
+ const rel = sanitizeDropRel(item.rel);
80703
+ if (!rel) {
80704
+ log7(`drop ${opts.dropId}: refusing directory "${item.rel}"`);
80705
+ continue;
80706
+ }
80707
+ const abs = join47(opts.destDir, rel);
80708
+ if (!isInsideRoot(abs, opts.destDir)) {
80709
+ log7(`drop ${opts.dropId}: refusing directory outside the destination`);
80710
+ continue;
80711
+ }
80712
+ await mkdir12(abs, { recursive: true });
80713
+ }
80714
+ for (const item of opts.items) {
80715
+ if (opts.signal?.aborted)
80716
+ throw new Error("Cancelled.");
80717
+ if (item.kind !== "file")
80718
+ continue;
80719
+ if (!item.url) {
80720
+ log7(`drop ${opts.dropId}: item ${item.itemId} has no bytes, skipping`);
80721
+ continue;
80722
+ }
80723
+ const rel = sanitizeDropRel(item.rel);
80724
+ if (!rel) {
80725
+ log7(`drop ${opts.dropId}: refusing "${item.rel}"`);
80726
+ continue;
80727
+ }
80728
+ let abs = join47(opts.destDir, rel);
80729
+ if (!isInsideRoot(abs, opts.destDir)) {
80730
+ log7(`drop ${opts.dropId}: refusing a path outside the destination`);
80731
+ continue;
80732
+ }
80733
+ await mkdir12(dirname22(abs), { recursive: true });
80734
+ abs = await uniqueDropPath(abs);
80735
+ const res = await fetchFn(new URL(item.url, opts.apiOrigin).toString(), { signal: opts.signal });
80736
+ if (!res.ok || !res.body)
80737
+ throw new Error(`Download failed (${res.status}).`);
80738
+ const body = Readable2.fromWeb(res.body);
80739
+ const plain = contentKey ? decryptStream(body, contentKey) : body;
80740
+ try {
80741
+ await pipeline(plain, createWriteStream2(abs));
80742
+ } catch (e) {
80743
+ await rm4(abs, { force: true }).catch(() => {
80744
+ });
80745
+ throw e;
80746
+ }
80747
+ await chmod3(abs, 420).catch(() => {
80748
+ });
80749
+ if (opts.sender)
80750
+ await markQuarantined(abs, opts.sender);
80751
+ written.push(abs);
80752
+ bytesDone += item.size;
80753
+ opts.onProgress?.(bytesDone, totalBytes);
80754
+ }
80755
+ } catch (e) {
80756
+ for (const p of written)
80757
+ await rm4(p, { force: true }).catch(() => {
80758
+ });
80759
+ throw e;
80760
+ }
80761
+ return { written };
80762
+ }
80763
+
79572
80764
  // ../../packages/host-core/dist/chat-agent.js
79573
- import { randomUUID as randomUUID16 } from "node:crypto";
80765
+ import { randomUUID as randomUUID17 } from "node:crypto";
79574
80766
  var TURN_TIMEOUT_MS = 4 * 6e4;
79575
80767
  var PARTIAL_INTERVAL_MS = 700;
79576
80768
  function createChatAgentRunner(deps) {
79577
- const log5 = deps.log ?? (() => {
80769
+ const log9 = deps.log ?? (() => {
79578
80770
  });
79579
80771
  const active = /* @__PURE__ */ new Set();
79580
80772
  async function api(path6, init) {
@@ -79598,7 +80790,7 @@ function createChatAgentRunner(deps) {
79598
80790
  }
79599
80791
  async function runTurn(prompt, onPartial) {
79600
80792
  const backend2 = await getSharedBackend();
79601
- const clientRequestId = `chat-agent-${randomUUID16()}`;
80793
+ const clientRequestId = `chat-agent-${randomUUID17()}`;
79602
80794
  return new Promise((resolve12, reject) => {
79603
80795
  let text8 = "";
79604
80796
  let settled = false;
@@ -79662,7 +80854,7 @@ function createChatAgentRunner(deps) {
79662
80854
  body: { deviceId: deps.deviceId }
79663
80855
  });
79664
80856
  if (!claimed) {
79665
- log5("chat-agent: job already claimed elsewhere", event.jobId);
80857
+ log9("chat-agent: job already claimed elsewhere", event.jobId);
79666
80858
  return;
79667
80859
  }
79668
80860
  const ctx2 = await api(`/api/core/tesseract/chat/agent-jobs/${event.jobId}/context`);
@@ -79686,10 +80878,10 @@ function createChatAgentRunner(deps) {
79686
80878
  await api(`/api/core/tesseract/chat/agent-jobs/${event.jobId}/reply`, { method: "POST", body: { markdown: trimmed } });
79687
80879
  }
79688
80880
  await api(`/api/core/tesseract/chat/agent-jobs/${event.jobId}/complete`, { method: "POST", body: {} });
79689
- log5("chat-agent: answered", event.jobId);
80881
+ log9("chat-agent: answered", event.jobId);
79690
80882
  } catch (err2) {
79691
80883
  const message = err2 instanceof Error ? err2.message : String(err2);
79692
- log5("chat-agent: job failed", event.jobId, message);
80884
+ log9("chat-agent: job failed", event.jobId, message);
79693
80885
  await api(`/api/core/tesseract/chat/agent-jobs/${event.jobId}/complete`, {
79694
80886
  method: "POST",
79695
80887
  body: { error: message }
@@ -79879,9 +81071,9 @@ function registerRdpEngine() {
79879
81071
 
79880
81072
  // ../../packages/host-core/dist/rdesk/audit.js
79881
81073
  import { appendFileSync as appendFileSync2 } from "node:fs";
79882
- import { join as join45 } from "node:path";
81074
+ import { join as join48 } from "node:path";
79883
81075
  function rdeskAuditPath() {
79884
- return join45(daemonDir2(), "rdesk-audit.jsonl");
81076
+ return join48(daemonDir2(), "rdesk-audit.jsonl");
79885
81077
  }
79886
81078
  function appendRdeskAudit(e) {
79887
81079
  try {
@@ -79892,10 +81084,10 @@ function appendRdeskAudit(e) {
79892
81084
 
79893
81085
  // ../../packages/host-core/dist/rdesk/password.js
79894
81086
  import { scryptSync, randomBytes as randomBytes6, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
79895
- import { readFileSync as readFileSync29, writeFileSync as writeFileSync21, existsSync as existsSync24, unlinkSync as unlinkSync2 } from "node:fs";
79896
- import { join as join46 } from "node:path";
81087
+ import { readFileSync as readFileSync29, writeFileSync as writeFileSync21, existsSync as existsSync24, unlinkSync as unlinkSync3 } from "node:fs";
81088
+ import { join as join49 } from "node:path";
79897
81089
  function pwFile() {
79898
- return join46(daemonDir2(), "rdesk-password.json");
81090
+ return join49(daemonDir2(), "rdesk-password.json");
79899
81091
  }
79900
81092
  function verifyRdeskPassword(password) {
79901
81093
  if (!password)
@@ -79992,26 +81184,26 @@ function broadcastFileProviderHostsChanged() {
79992
81184
  }
79993
81185
  var BRIDGE_PORTS = [47821, 47822, 47823, 47824, 47825, 47826, 47827, 47828];
79994
81186
  var BRIDGE_TOKEN = "b7c1f9a4e25d4c3a8f06e1d72b9a3c5e";
79995
- function startFileProviderBridge(log5 = () => {
81187
+ function startFileProviderBridge(log9 = () => {
79996
81188
  }) {
79997
81189
  if (process.platform !== "darwin" || wss)
79998
81190
  return;
79999
81191
  stopping = false;
80000
81192
  token = BRIDGE_TOKEN;
80001
- bindBridge(0, log5);
81193
+ bindBridge(0, log9);
80002
81194
  }
80003
- function bindBridge(i, log5) {
81195
+ function bindBridge(i, log9) {
80004
81196
  if (stopping || wss)
80005
81197
  return;
80006
81198
  if (i >= BRIDGE_PORTS.length) {
80007
- log5("fileprovider: no free bridge port in the fixed set");
81199
+ log9("fileprovider: no free bridge port in the fixed set");
80008
81200
  return;
80009
81201
  }
80010
81202
  const port = BRIDGE_PORTS[i];
80011
81203
  const server = new import_websocket_server.default({ host: "127.0.0.1", port });
80012
81204
  server.on("listening", () => {
80013
81205
  wss = server;
80014
- log5(`fileprovider bridge listening on 127.0.0.1:${port}`);
81206
+ log9(`fileprovider bridge listening on 127.0.0.1:${port}`);
80015
81207
  });
80016
81208
  server.on("connection", (ws, req) => {
80017
81209
  let ok = false;
@@ -80037,9 +81229,9 @@ function bindBridge(i, log5) {
80037
81229
  server.close();
80038
81230
  } catch {
80039
81231
  }
80040
- bindBridge(i + 1, log5);
81232
+ bindBridge(i + 1, log9);
80041
81233
  } else
80042
- log5("fileprovider bridge error", e?.message);
81234
+ log9("fileprovider bridge error", e?.message);
80043
81235
  });
80044
81236
  }
80045
81237
 
@@ -80047,12 +81239,12 @@ function bindBridge(i, log5) {
80047
81239
  import { randomBytes as randomBytes7 } from "node:crypto";
80048
81240
  import { existsSync as existsSync25, mkdirSync as mkdirSync23, writeFileSync as writeFileSync22, rmSync as rmSync7 } from "node:fs";
80049
81241
  import { homedir as homedir6 } from "node:os";
80050
- import { join as join47 } from "node:path";
81242
+ import { join as join50 } from "node:path";
80051
81243
 
80052
81244
  // ../../packages/host-core/dist/vault/peer-verify.js
80053
- import { execFile as execFile5 } from "node:child_process";
80054
- import { promisify as promisify4 } from "node:util";
80055
- var run2 = promisify4(execFile5);
81245
+ import { execFile as execFile6 } from "node:child_process";
81246
+ import { promisify as promisify5 } from "node:util";
81247
+ var run2 = promisify5(execFile6);
80056
81248
  var APPEX_IDENTIFIER = "dev.tesseract.AutoFill";
80057
81249
  async function teamIdOf(path6) {
80058
81250
  try {
@@ -80109,13 +81301,13 @@ async function isTrustedAutofillPeer(remotePort) {
80109
81301
  var APPEX_BUNDLE_ID = "dev.tesseract.AutoFill";
80110
81302
  var BRIDGE_PORTS2 = [17872, 17873, 17874, 17875];
80111
81303
  function appexContainerData() {
80112
- return join47(homedir6(), "Library", "Containers", APPEX_BUNDLE_ID, "Data");
81304
+ return join50(homedir6(), "Library", "Containers", APPEX_BUNDLE_ID, "Data");
80113
81305
  }
80114
81306
  function bridgeInfoPath() {
80115
- return join47(appexContainerData(), "Library", "Application Support", "tesseract-autofill-bridge.json");
81307
+ return join50(appexContainerData(), "Library", "Application Support", "tesseract-autofill-bridge.json");
80116
81308
  }
80117
81309
  var infoLoggedOnce = false;
80118
- function writeBridgeInfo(port, token3, log5) {
81310
+ function writeBridgeInfo(port, token3, log9) {
80119
81311
  if (process.platform !== "darwin")
80120
81312
  return;
80121
81313
  const containerData = appexContainerData();
@@ -80123,21 +81315,21 @@ function writeBridgeInfo(port, token3, log5) {
80123
81315
  if (!existsSync25(containerData)) {
80124
81316
  if (!infoLoggedOnce) {
80125
81317
  infoLoggedOnce = true;
80126
- log5("autofill bridge-info SKIP: no appex container yet at", containerData);
81318
+ log9("autofill bridge-info SKIP: no appex container yet at", containerData);
80127
81319
  }
80128
81320
  return;
80129
81321
  }
80130
- const dir2 = join47(containerData, "Library", "Application Support");
81322
+ const dir2 = join50(containerData, "Library", "Application Support");
80131
81323
  mkdirSync23(dir2, { recursive: true });
80132
81324
  writeFileSync22(bridgeInfoPath(), JSON.stringify({ port, token: token3, pid: process.pid, ts: Date.now() }), { mode: 384 });
80133
81325
  if (!infoLoggedOnce) {
80134
81326
  infoLoggedOnce = true;
80135
- log5("autofill bridge-info WROTE", port, "\u2192", dir2);
81327
+ log9("autofill bridge-info WROTE", port, "\u2192", dir2);
80136
81328
  }
80137
81329
  } catch (e) {
80138
81330
  if (!infoLoggedOnce) {
80139
81331
  infoLoggedOnce = true;
80140
- log5("autofill bridge-info FAILED:", e?.message);
81332
+ log9("autofill bridge-info FAILED:", e?.message);
80141
81333
  }
80142
81334
  }
80143
81335
  }
@@ -80229,20 +81421,20 @@ var token2 = "";
80229
81421
  var infoTimer = null;
80230
81422
  var stopping2 = false;
80231
81423
  var sockets2 = /* @__PURE__ */ new Set();
80232
- function bind(i, log5) {
81424
+ function bind(i, log9) {
80233
81425
  if (stopping2 || wss2)
80234
81426
  return;
80235
81427
  if (i >= BRIDGE_PORTS2.length) {
80236
- log5("autofill: no free bridge port in the fixed set");
81428
+ log9("autofill: no free bridge port in the fixed set");
80237
81429
  return;
80238
81430
  }
80239
81431
  const port = BRIDGE_PORTS2[i];
80240
81432
  const server = new import_websocket_server.default({ host: "127.0.0.1", port });
80241
81433
  server.on("listening", () => {
80242
81434
  wss2 = server;
80243
- log5(`autofill bridge listening on 127.0.0.1:${port}`);
80244
- writeBridgeInfo(port, token2, log5);
80245
- infoTimer = setInterval(() => writeBridgeInfo(port, token2, log5), 15e3);
81435
+ log9(`autofill bridge listening on 127.0.0.1:${port}`);
81436
+ writeBridgeInfo(port, token2, log9);
81437
+ infoTimer = setInterval(() => writeBridgeInfo(port, token2, log9), 15e3);
80246
81438
  infoTimer.unref?.();
80247
81439
  });
80248
81440
  server.on("connection", (ws, req) => {
@@ -80270,18 +81462,18 @@ function bind(i, log5) {
80270
81462
  server.close();
80271
81463
  } catch {
80272
81464
  }
80273
- bind(i + 1, log5);
81465
+ bind(i + 1, log9);
80274
81466
  } else
80275
- log5("autofill bridge error", e?.message);
81467
+ log9("autofill bridge error", e?.message);
80276
81468
  });
80277
81469
  }
80278
- function startAutofillBridge(log5 = () => {
81470
+ function startAutofillBridge(log9 = () => {
80279
81471
  }) {
80280
81472
  if (process.platform !== "darwin" || wss2)
80281
81473
  return;
80282
81474
  stopping2 = false;
80283
81475
  token2 = randomBytes7(24).toString("base64url");
80284
- bind(0, log5);
81476
+ bind(0, log9);
80285
81477
  }
80286
81478
 
80287
81479
  // ../../packages/host-core/dist/power-node.js
@@ -80345,7 +81537,7 @@ function createNodePowerController() {
80345
81537
  import { execFileSync as execFileSync3 } from "node:child_process";
80346
81538
  import { chmodSync as chmodSync4, existsSync as existsSync26, mkdirSync as mkdirSync24, writeFileSync as writeFileSync23 } from "node:fs";
80347
81539
  import { homedir as homedir7, platform as platform2 } from "node:os";
80348
- import { dirname as dirname21, join as join48, resolve as resolve10 } from "node:path";
81540
+ import { dirname as dirname23, join as join51, resolve as resolve10 } from "node:path";
80349
81541
  import { fileURLToPath as fileURLToPath3 } from "node:url";
80350
81542
  var HOST_NAME = "com.tesseract.companion";
80351
81543
  var EXTENSION_IDS = ["icpnghhpcmaonjckkpilpajblfehpplo", "giolfpookdhjfafcchaflhmigghaeffn"];
@@ -80354,21 +81546,21 @@ function browserProfileDirs() {
80354
81546
  const home = homedir7();
80355
81547
  const p = platform2();
80356
81548
  if (p === "darwin") {
80357
- const base = join48(home, "Library/Application Support");
80358
- return ["Google/Chrome", "Google/Chrome Beta", "Google/Chrome Dev", "Google/Chrome Canary", "Chromium", "Microsoft Edge", "BraveSoftware/Brave-Browser", "Vivaldi", "Arc"].map((b) => join48(base, b));
81549
+ const base = join51(home, "Library/Application Support");
81550
+ return ["Google/Chrome", "Google/Chrome Beta", "Google/Chrome Dev", "Google/Chrome Canary", "Chromium", "Microsoft Edge", "BraveSoftware/Brave-Browser", "Vivaldi", "Arc"].map((b) => join51(base, b));
80359
81551
  }
80360
81552
  if (p === "win32")
80361
81553
  return [];
80362
- const cfg2 = join48(home, ".config");
80363
- return ["google-chrome", "google-chrome-beta", "chromium", "microsoft-edge", "BraveSoftware/Brave-Browser", "vivaldi"].map((b) => join48(cfg2, b));
81554
+ const cfg2 = join51(home, ".config");
81555
+ return ["google-chrome", "google-chrome-beta", "chromium", "microsoft-edge", "BraveSoftware/Brave-Browser", "vivaldi"].map((b) => join51(cfg2, b));
80364
81556
  }
80365
- function resolveBridge(log5) {
81557
+ function resolveBridge(log9) {
80366
81558
  const candidates = [
80367
81559
  process.env.CCB_COMPANION_HOST_JS,
80368
- process.resourcesPath ? join48(process.resourcesPath, "companion-host", BRIDGE_BASENAME) : void 0
81560
+ process.resourcesPath ? join51(process.resourcesPath, "companion-host", BRIDGE_BASENAME) : void 0
80369
81561
  ];
80370
81562
  try {
80371
- const here = dirname21(fileURLToPath3(import.meta.url));
81563
+ const here = dirname23(fileURLToPath3(import.meta.url));
80372
81564
  candidates.push(resolve10(here, "../../../apps/companion-host/dist", BRIDGE_BASENAME));
80373
81565
  candidates.push(resolve10(here, "../../apps/companion-host/dist", BRIDGE_BASENAME));
80374
81566
  } catch {
@@ -80376,12 +81568,12 @@ function resolveBridge(log5) {
80376
81568
  for (const c of candidates)
80377
81569
  if (c && existsSync26(c))
80378
81570
  return c;
80379
- log5("companion native-host: bridge not found; skipping registration (looked in", candidates.filter(Boolean).join(" , "), ")");
81571
+ log9("companion native-host: bridge not found; skipping registration (looked in", candidates.filter(Boolean).join(" , "), ")");
80380
81572
  return null;
80381
81573
  }
80382
81574
  function writeLauncher(daemonDir3, bridge, runtime) {
80383
81575
  const isWin = platform2() === "win32";
80384
- const launcher = join48(daemonDir3, isWin ? "companion-host.cmd" : "companion-host.sh");
81576
+ const launcher = join51(daemonDir3, isWin ? "companion-host.cmd" : "companion-host.sh");
80385
81577
  if (isWin) {
80386
81578
  writeFileSync23(launcher, `@echo off\r
80387
81579
  set ELECTRON_RUN_AS_NODE=1\r
@@ -80397,9 +81589,9 @@ ELECTRON_RUN_AS_NODE=1 exec "${runtime}" "${bridge}" "$@"
80397
81589
  return launcher;
80398
81590
  }
80399
81591
  function registerNativeHost(opts) {
80400
- const log5 = opts.log ?? (() => void 0);
81592
+ const log9 = opts.log ?? (() => void 0);
80401
81593
  try {
80402
- const bridge = resolveBridge(log5);
81594
+ const bridge = resolveBridge(log9);
80403
81595
  if (!bridge)
80404
81596
  return;
80405
81597
  const runtime = opts.runtime ?? process.execPath;
@@ -80413,27 +81605,27 @@ function registerNativeHost(opts) {
80413
81605
  allowed_origins: EXTENSION_IDS.map((id) => `chrome-extension://${id}/`)
80414
81606
  }, null, 2);
80415
81607
  if (platform2() === "win32")
80416
- return registerWindows(opts.daemonDir, manifestJson, log5);
81608
+ return registerWindows(opts.daemonDir, manifestJson, log9);
80417
81609
  let wrote = 0;
80418
81610
  for (const profile of browserProfileDirs()) {
80419
81611
  if (!existsSync26(profile))
80420
81612
  continue;
80421
81613
  try {
80422
- const dir2 = join48(profile, "NativeMessagingHosts");
81614
+ const dir2 = join51(profile, "NativeMessagingHosts");
80423
81615
  mkdirSync24(dir2, { recursive: true });
80424
- writeFileSync23(join48(dir2, `${HOST_NAME}.json`), manifestJson);
81616
+ writeFileSync23(join51(dir2, `${HOST_NAME}.json`), manifestJson);
80425
81617
  wrote++;
80426
81618
  } catch {
80427
81619
  }
80428
81620
  }
80429
- log5(`companion native-host: registered for ${wrote} browser(s) \u2192 ${launcher}`);
81621
+ log9(`companion native-host: registered for ${wrote} browser(s) \u2192 ${launcher}`);
80430
81622
  } catch (e) {
80431
- log5("companion native-host: registration failed", e.message);
81623
+ log9("companion native-host: registration failed", e.message);
80432
81624
  }
80433
81625
  }
80434
- function registerWindows(daemonDir3, manifestJson, log5) {
81626
+ function registerWindows(daemonDir3, manifestJson, log9) {
80435
81627
  try {
80436
- const manifestPath = join48(daemonDir3, `${HOST_NAME}.json`);
81628
+ const manifestPath = join51(daemonDir3, `${HOST_NAME}.json`);
80437
81629
  writeFileSync23(manifestPath, manifestJson);
80438
81630
  for (const root of ["Software\\Google\\Chrome", "Software\\Microsoft\\Edge", "Software\\BraveSoftware\\Brave-Browser"]) {
80439
81631
  try {
@@ -80441,17 +81633,17 @@ function registerWindows(daemonDir3, manifestJson, log5) {
80441
81633
  } catch {
80442
81634
  }
80443
81635
  }
80444
- log5("companion native-host: registered (Windows registry)");
81636
+ log9("companion native-host: registered (Windows registry)");
80445
81637
  } catch (e) {
80446
- log5("companion native-host: Windows registration failed", e.message);
81638
+ log9("companion native-host: Windows registration failed", e.message);
80447
81639
  }
80448
81640
  }
80449
81641
 
80450
81642
  // ../../packages/host-core/dist/activity/activity-tracker.js
80451
81643
  import { promises as fsp, writeFileSync as writeFileSync24, mkdirSync as mkdirSync25, existsSync as existsSync27 } from "node:fs";
80452
- import { join as join49 } from "node:path";
81644
+ import { join as join52 } from "node:path";
80453
81645
  function activityProfilePath() {
80454
- return join49(daemonDir2(), "activity-profile.json");
81646
+ return join52(daemonDir2(), "activity-profile.json");
80455
81647
  }
80456
81648
  var MAX_APPS = 300;
80457
81649
  var MAX_AGE_DAYS = 120;
@@ -80494,7 +81686,7 @@ function loadMacAx2() {
80494
81686
  function createActivityTracker(opts = {}) {
80495
81687
  const intervalMs = opts.intervalMs ?? 5e3;
80496
81688
  const flushEvery = opts.flushEvery ?? 12;
80497
- const log5 = opts.log ?? (() => {
81689
+ const log9 = opts.log ?? (() => {
80498
81690
  });
80499
81691
  let timer = null;
80500
81692
  let profile = null;
@@ -80512,10 +81704,10 @@ function createActivityTracker(opts = {}) {
80512
81704
  try {
80513
81705
  await fsp.mkdir(daemonDir2(), { recursive: true, mode: 448 });
80514
81706
  await fsp.writeFile(activityProfilePath(), JSON.stringify(profile), { mode: 384 });
80515
- log5(`activity flushed (${Object.keys(profile.apps).length} apps tracked)`);
81707
+ log9(`activity flushed (${Object.keys(profile.apps).length} apps tracked)`);
80516
81708
  dirty = 0;
80517
81709
  } catch (e) {
80518
- log5("activity flush failed", e instanceof Error ? e.message : String(e));
81710
+ log9("activity flush failed", e instanceof Error ? e.message : String(e));
80519
81711
  }
80520
81712
  }
80521
81713
  async function sample() {
@@ -80527,7 +81719,7 @@ function createActivityTracker(opts = {}) {
80527
81719
  const key2 = front.bundleId || front.name;
80528
81720
  await ensureLoaded();
80529
81721
  if (profile && Object.keys(profile.apps).length && !existsSync27(activityProfilePath())) {
80530
- log5("activity profile cleared externally \u2014 resetting in-memory");
81722
+ log9("activity profile cleared externally \u2014 resetting in-memory");
80531
81723
  profile = { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), apps: {} };
80532
81724
  }
80533
81725
  const p = profile;
@@ -80541,14 +81733,14 @@ function createActivityTracker(opts = {}) {
80541
81733
  if (++dirty >= flushEvery)
80542
81734
  await flush2();
80543
81735
  } catch (e) {
80544
- log5("activity sample failed", e instanceof Error ? e.message : String(e));
81736
+ log9("activity sample failed", e instanceof Error ? e.message : String(e));
80545
81737
  }
80546
81738
  }
80547
81739
  return {
80548
81740
  start() {
80549
81741
  if (timer)
80550
81742
  return;
80551
- log5("activity tracking ON (local-only)");
81743
+ log9("activity tracking ON (local-only)");
80552
81744
  timer = setInterval(() => {
80553
81745
  void sample();
80554
81746
  }, intervalMs);
@@ -80567,9 +81759,9 @@ function createActivityTracker(opts = {}) {
80567
81759
  dirty = 0;
80568
81760
  }
80569
81761
  } catch (e) {
80570
- log5("activity final flush failed", e instanceof Error ? e.message : String(e));
81762
+ log9("activity final flush failed", e instanceof Error ? e.message : String(e));
80571
81763
  }
80572
- log5("activity tracking OFF");
81764
+ log9("activity tracking OFF");
80573
81765
  },
80574
81766
  running() {
80575
81767
  return timer !== null;
@@ -80596,16 +81788,68 @@ function getRdeskClientWebContents() {
80596
81788
  } };
80597
81789
  }
80598
81790
 
81791
+ // ../../packages/host-core/dist/gui/rdesk-call-window.js
81792
+ import { join as join53 } from "node:path";
81793
+ var win2 = null;
81794
+ function showRdeskCallWindow(opts) {
81795
+ const el2 = electronModule();
81796
+ if (!el2?.BrowserWindow)
81797
+ return;
81798
+ if (win2 && !win2.isDestroyed()) {
81799
+ try {
81800
+ win2.focus();
81801
+ } catch {
81802
+ }
81803
+ return;
81804
+ }
81805
+ const w = new el2.BrowserWindow({
81806
+ width: 380,
81807
+ height: 320,
81808
+ resizable: true,
81809
+ minimizable: true,
81810
+ fullscreenable: false,
81811
+ alwaysOnTop: true,
81812
+ skipTaskbar: false,
81813
+ title: "Remote desktop \u2014 call",
81814
+ backgroundColor: "#15151a",
81815
+ webPreferences: { contextIsolation: true, sandbox: false }
81816
+ });
81817
+ try {
81818
+ w.setAlwaysOnTop(true, "floating");
81819
+ } catch {
81820
+ }
81821
+ w.on("closed", () => {
81822
+ win2 = null;
81823
+ try {
81824
+ opts.onClosed?.();
81825
+ } catch {
81826
+ }
81827
+ });
81828
+ const loadFile = w.loadFile;
81829
+ void loadFile(join53(daemonUiDir(), "rdesk-call.html"), { query: { url: opts.url, token: opts.token, room: opts.room } }).catch((e) => console.error("[rdesk-call] loadFile FAILED", e?.message ?? e));
81830
+ win2 = w;
81831
+ }
81832
+ function closeRdeskCallWindow() {
81833
+ const w = win2;
81834
+ win2 = null;
81835
+ if (w && !w.isDestroyed()) {
81836
+ try {
81837
+ w.close();
81838
+ } catch {
81839
+ }
81840
+ }
81841
+ }
81842
+
80599
81843
  // ../../packages/host-core/dist/daemon-runtime.js
80600
81844
  var daemonClipboardSync = null;
80601
81845
  var REFRESH_INTERVAL_MS = 12 * 60 * 60 * 1e3;
80602
81846
  async function runDaemon(opts = {}) {
80603
81847
  const VERSION = opts.version ?? process.env.CCB_DAEMON_VERSION ?? "dev";
80604
81848
  const TAG = opts.tag ?? "host-daemon";
80605
- const log5 = (...args) => console.log(`[${TAG} ${(/* @__PURE__ */ new Date()).toISOString()}]`, ...args);
80606
- log5("paths", JSON.stringify(sealBasePaths()));
80607
- const pidfile = () => join50(daemonDir2(), "daemon.lock");
80608
- const statusFile = () => join50(daemonDir2(), "status.json");
81849
+ const log9 = (...args) => console.log(`[${TAG} ${(/* @__PURE__ */ new Date()).toISOString()}]`, ...args);
81850
+ log9("paths", JSON.stringify(sealBasePaths()));
81851
+ const pidfile = () => join54(daemonDir2(), "daemon.lock");
81852
+ const statusFile = () => join54(daemonDir2(), "status.json");
80609
81853
  const isAlive2 = (pid) => {
80610
81854
  try {
80611
81855
  process.kill(pid, 0);
@@ -80632,7 +81876,7 @@ async function runDaemon(opts = {}) {
80632
81876
  return false;
80633
81877
  if (prev !== process.pid && isAlive2(prev))
80634
81878
  return false;
80635
- unlinkSync3(f);
81879
+ unlinkSync4(f);
80636
81880
  } catch {
80637
81881
  return false;
80638
81882
  }
@@ -80644,7 +81888,7 @@ async function runDaemon(opts = {}) {
80644
81888
  try {
80645
81889
  const f = pidfile();
80646
81890
  if (existsSync28(f) && Number(readFileSync30(f, "utf-8").trim()) === process.pid)
80647
- unlinkSync3(f);
81891
+ unlinkSync4(f);
80648
81892
  } catch {
80649
81893
  }
80650
81894
  }
@@ -80704,7 +81948,7 @@ async function runDaemon(opts = {}) {
80704
81948
  const live = [...rivals].filter(isAlive2);
80705
81949
  if (live.length) {
80706
81950
  for (const pid of live) {
80707
- log5(`takeover: killing rival daemon pid=${pid} (latest-wins single-instance)`);
81951
+ log9(`takeover: killing rival daemon pid=${pid} (latest-wins single-instance)`);
80708
81952
  try {
80709
81953
  process.kill(pid, "SIGTERM");
80710
81954
  } catch {
@@ -80714,7 +81958,7 @@ async function runDaemon(opts = {}) {
80714
81958
  await new Promise((r) => setTimeout(r, 100));
80715
81959
  for (const pid of live)
80716
81960
  if (isAlive2(pid)) {
80717
- log5(`takeover: SIGKILL rival pid=${pid} (ignored SIGTERM)`);
81961
+ log9(`takeover: SIGKILL rival pid=${pid} (ignored SIGTERM)`);
80718
81962
  try {
80719
81963
  process.kill(pid, "SIGKILL");
80720
81964
  } catch {
@@ -80723,7 +81967,7 @@ async function runDaemon(opts = {}) {
80723
81967
  }
80724
81968
  try {
80725
81969
  if (existsSync28(pidfile()))
80726
- unlinkSync3(pidfile());
81970
+ unlinkSync4(pidfile());
80727
81971
  } catch {
80728
81972
  }
80729
81973
  }
@@ -80738,7 +81982,7 @@ async function runDaemon(opts = {}) {
80738
81982
  await takeoverMachine();
80739
81983
  if (!acquireLock2()) {
80740
81984
  try {
80741
- unlinkSync3(pidfile());
81985
+ unlinkSync4(pidfile());
80742
81986
  } catch {
80743
81987
  }
80744
81988
  if (!acquireLock2()) {
@@ -80749,26 +81993,30 @@ async function runDaemon(opts = {}) {
80749
81993
  }
80750
81994
  }
80751
81995
  }
81996
+ try {
81997
+ unlinkSync4(join54(daemonDir2(), "daemon.booting"));
81998
+ } catch {
81999
+ }
80752
82000
  let timer = null;
80753
82001
  let localServer = null;
80754
82002
  let localInfo = null;
80755
82003
  let rdeskManager = null;
80756
82004
  const loopbackRdeskSend = /* @__PURE__ */ new Map();
80757
- const activityTracker = createActivityTracker({ log: log5 });
82005
+ const activityTracker = createActivityTracker({ log: log9 });
80758
82006
  let activityTimer = null;
80759
82007
  const creds3 = readCredentials();
80760
82008
  const deviceId2 = creds3?.deviceId;
80761
82009
  const daemonStartedAt = Date.now();
80762
82010
  process.on("exit", (code) => {
80763
82011
  try {
80764
- log5(`PROCESS EXIT code=${code} uptimeMs=${Date.now() - daemonStartedAt}`);
82012
+ log9(`PROCESS EXIT code=${code} uptimeMs=${Date.now() - daemonStartedAt}`);
80765
82013
  } catch {
80766
82014
  }
80767
82015
  });
80768
82016
  function shutdown(code, reason = "unknown") {
80769
82017
  try {
80770
- log5(`SHUTDOWN reason=${reason} code=${code} uptimeMs=${Date.now() - daemonStartedAt} ppid=${process.ppid} pid=${process.pid}`);
80771
- log5(`SHUTDOWN caller: ${(new Error("shutdown").stack ?? "").split("\n").slice(2, 7).map((l) => l.trim()).join(" <- ")}`);
82018
+ log9(`SHUTDOWN reason=${reason} code=${code} uptimeMs=${Date.now() - daemonStartedAt} ppid=${process.ppid} pid=${process.pid}`);
82019
+ log9(`SHUTDOWN caller: ${(new Error("shutdown").stack ?? "").split("\n").slice(2, 7).map((l) => l.trim()).join(" <- ")}`);
80772
82020
  } catch {
80773
82021
  }
80774
82022
  if (timer) {
@@ -80799,18 +82047,60 @@ async function runDaemon(opts = {}) {
80799
82047
  }
80800
82048
  process.on("SIGTERM", () => shutdown(0, "signal:SIGTERM"));
80801
82049
  process.on("SIGINT", () => shutdown(0, "signal:SIGINT"));
82050
+ process.on("uncaughtException", (e) => {
82051
+ try {
82052
+ log9(`UNCAUGHT (kept alive): ${e instanceof Error ? e.stack ?? e.message : String(e)}`);
82053
+ } catch {
82054
+ }
82055
+ });
82056
+ process.on("unhandledRejection", (r) => {
82057
+ try {
82058
+ log9(`UNHANDLED REJECTION (kept alive): ${r instanceof Error ? r.stack ?? r.message : String(r)}`);
82059
+ } catch {
82060
+ }
82061
+ });
82062
+ setInterval(() => {
82063
+ }, 1 << 30);
82064
+ try {
82065
+ const eapp = electronModule()?.app;
82066
+ if (eapp) {
82067
+ eapp.on("before-quit", (e) => {
82068
+ try {
82069
+ log9("APP before-quit \u2014 PREVENTED (daemon never self-quits via Electron)");
82070
+ } catch {
82071
+ }
82072
+ try {
82073
+ e?.preventDefault?.();
82074
+ } catch {
82075
+ }
82076
+ });
82077
+ eapp.on("render-process-gone", (_e, d) => {
82078
+ try {
82079
+ log9(`render-process-gone (kept alive): ${JSON.stringify(d)}`);
82080
+ } catch {
82081
+ }
82082
+ });
82083
+ eapp.on("child-process-gone", (_e, d) => {
82084
+ try {
82085
+ log9(`child-process-gone (kept alive): ${JSON.stringify(d)}`);
82086
+ } catch {
82087
+ }
82088
+ });
82089
+ }
82090
+ } catch {
82091
+ }
80802
82092
  if (process.env.CCB_RDESK_ROLE !== "client")
80803
82093
  process.env.CCB_HOST_DAEMON = "1";
80804
82094
  const localToken = randomBytes8(24).toString("hex");
80805
82095
  try {
80806
- localServer = await startLocalServer({ hostRoot: resolveHostRoot(), token: localToken, log: log5, getRdesk: () => rdeskManager, loopbackRdeskSend });
82096
+ localServer = await startLocalServer({ hostRoot: resolveHostRoot(), token: localToken, log: log9, getRdesk: () => rdeskManager, loopbackRdeskSend });
80807
82097
  localInfo = { localPort: localServer.port, localToken };
80808
82098
  } catch (e) {
80809
- log5("local listener failed to start", e.message);
82099
+ log9("local listener failed to start", e.message);
80810
82100
  }
80811
82101
  writeStatus({ deviceId: deviceId2, connected: false });
80812
82102
  try {
80813
- registerNativeHost({ daemonDir: daemonDir2(), log: log5 });
82103
+ registerNativeHost({ daemonDir: daemonDir2(), log: log9 });
80814
82104
  } catch {
80815
82105
  }
80816
82106
  const syncActivity = () => {
@@ -80829,12 +82119,12 @@ async function runDaemon(opts = {}) {
80829
82119
  try {
80830
82120
  await opts.onOwned?.();
80831
82121
  } catch (e) {
80832
- log5("onOwned hook failed", e.message);
82122
+ log9("onOwned hook failed", e.message);
80833
82123
  }
80834
82124
  if (!creds3) {
80835
82125
  timer = setInterval(() => writeStatus({ deviceId: deviceId2, connected: false, localOnly: true }), 6e4);
80836
82126
  writeStatus({ deviceId: deviceId2, connected: false, localOnly: true });
80837
- log5(`started LOCAL-ONLY (not signed in) \u2014 local :${localInfo?.localPort ?? "off"}, version ${VERSION}`);
82127
+ log9(`started LOCAL-ONLY (not signed in) \u2014 local :${localInfo?.localPort ?? "off"}, version ${VERSION}`);
80838
82128
  return;
80839
82129
  }
80840
82130
  const c = creds3;
@@ -80843,10 +82133,20 @@ async function runDaemon(opts = {}) {
80843
82133
  void (async () => {
80844
82134
  try {
80845
82135
  const id = await loadOrCreateDeviceIdentity();
80846
- const ok = await registerDeviceKey({ apiOrigin: c.apiOrigin, accessToken, deviceId: c.deviceId, publicRaw: id.publicRaw, label: hostname4() });
80847
- log5(ok ? "e2e identity registered" : "e2e identity registration not accepted (will retry on restart)");
82136
+ const sealing = await loadOrCreateSealingKey();
82137
+ const sealingPubSig = await attestSealingKey(id.privateKey, sealing.publicRaw);
82138
+ const ok = await registerDeviceKey({
82139
+ apiOrigin: c.apiOrigin,
82140
+ accessToken,
82141
+ deviceId: c.deviceId,
82142
+ publicRaw: id.publicRaw,
82143
+ label: hostname4(),
82144
+ sealingPub: b64u(sealing.publicRaw),
82145
+ sealingPubSig
82146
+ });
82147
+ log9(ok ? "e2e identity + sealing key registered" : "e2e identity registration not accepted (will retry on restart)");
80848
82148
  } catch (e) {
80849
- log5("e2e identity registration failed (non-fatal)", e.message);
82149
+ log9("e2e identity registration failed (non-fatal)", e.message);
80850
82150
  }
80851
82151
  })();
80852
82152
  async function refresh() {
@@ -80857,11 +82157,11 @@ async function runDaemon(opts = {}) {
80857
82157
  body: JSON.stringify({ dtid: c.dtid, refreshSecret: c.refreshSecret })
80858
82158
  });
80859
82159
  if (!r.ok) {
80860
- log5("token refresh rejected", r.status);
82160
+ log9("token refresh rejected", r.status);
80861
82161
  if (r.status === 401) {
80862
- log5("device token no longer valid \u2014 dropping to local-only on restart");
82162
+ log9("device token no longer valid \u2014 dropping to local-only on restart");
80863
82163
  try {
80864
- unlinkSync3(join50(daemonDir2(), "credentials.json"));
82164
+ unlinkSync4(join54(daemonDir2(), "credentials.json"));
80865
82165
  } catch {
80866
82166
  }
80867
82167
  shutdown(0, "token-revoked-401");
@@ -80873,15 +82173,53 @@ async function runDaemon(opts = {}) {
80873
82173
  accessToken = data.accessToken;
80874
82174
  writeCredentials({ ...c, accessToken: data.accessToken, expiresAt: data.expiresAt ?? c.expiresAt, publicId: data.publicId ?? c.publicId });
80875
82175
  refreshControlLink();
80876
- log5("token refreshed");
82176
+ log9("token refreshed");
80877
82177
  }
80878
82178
  } catch (e) {
80879
- log5("token refresh failed (will retry)", e.message);
82179
+ log9("token refresh failed (will retry)", e.message);
80880
82180
  }
80881
82181
  }
80882
82182
  registerTesseractEngine({ getIceServers: () => fetchIceServers() });
80883
82183
  registerVncEngine();
80884
82184
  registerRdpEngine();
82185
+ initDropReceiveUi({ hostLabel: (hostId) => listHosts().find((h) => h.hostId === hostId)?.name });
82186
+ const dropsBase = `${c.apiOrigin.replace(/\/$/, "")}/api/core/tesseract/drops`;
82187
+ const drops = createDropHandler({
82188
+ api: async (path6, init) => {
82189
+ const method = init?.method ?? "GET";
82190
+ const hasBody = method !== "GET" && method !== "HEAD";
82191
+ const r = await fetch(`${dropsBase}${path6}`, {
82192
+ method,
82193
+ headers: { authorization: `Bearer ${accessToken}`, ...hasBody ? { "content-type": "application/json" } : {} },
82194
+ ...hasBody ? { body: JSON.stringify(init?.body ?? {}) } : {},
82195
+ signal: AbortSignal.timeout(1e4)
82196
+ });
82197
+ if (!r.ok)
82198
+ throw new Error(`drops ${path6} \u2192 ${r.status}`);
82199
+ return await r.json().catch(() => ({}));
82200
+ },
82201
+ deviceId: () => c.deviceId,
82202
+ ownHostId: () => ownHost() || null,
82203
+ canPrompt: () => !!electronModule(),
82204
+ confirm: async (spec, signal) => (await confirmAction(spec, { signal })).confirmed,
82205
+ // Store-and-forward: we won the claim, so pull the bytes and (for cross-user
82206
+ // drops) open the one seal in the fan-out that was made for THIS device.
82207
+ fetchBlobDrop: async ({ dropId, destDir, claim, sender }) => {
82208
+ await fetchBlobDrop({
82209
+ dropId,
82210
+ destDir,
82211
+ apiOrigin: c.apiOrigin,
82212
+ items: claim.items ?? [],
82213
+ sealedKey: claim.sealedKey,
82214
+ sealingPrivate: async () => (await loadOrCreateSealingKey()).privateKey,
82215
+ // Present ⇒ cross-user ⇒ quarantine, so the OS treats the file as foreign.
82216
+ sender
82217
+ });
82218
+ }
82219
+ // No startLiveSend here: SENDING is driven by the app (a client tells the source
82220
+ // host to begin, exactly like the existing copy-to-machine flow). The daemon is
82221
+ // the receiving half, and the source host it addresses may well be this daemon.
82222
+ });
80885
82223
  let rdeskRef = null;
80886
82224
  const rdesk = new RdeskSessionManager({
80887
82225
  createEngine: createRemoteDesktopEngine,
@@ -80912,8 +82250,64 @@ async function runDaemon(opts = {}) {
80912
82250
  onSessionsChanged: (sessions3) => {
80913
82251
  writeStatus({ deviceId: c.deviceId, connected: true, rdeskSessions: sessions3.length });
80914
82252
  setRdeskTraySessions(sessions3.length, () => void rdeskRef?.disconnectAll("host"));
82253
+ if (sessions3.length === 0)
82254
+ closeRdeskCallWindow();
82255
+ },
82256
+ // A viewer joined/left the in-session voice/video call → open/close the operator call window,
82257
+ // joined to the SAME LiveKit room (= the rdesk sessionId) the viewer is in, so the person at the
82258
+ // host can talk. Reuses the team-chat/huddle conferencing; the daemon mints its own join token
82259
+ // with the device token (identity `${sub}#host`, distinct from the viewer's).
82260
+ onCall: ({ sessionId, on }) => {
82261
+ if (!on) {
82262
+ closeRdeskCallWindow();
82263
+ return;
82264
+ }
82265
+ void (async () => {
82266
+ try {
82267
+ const r = await fetch(`${c.apiOrigin.replace(/\/$/, "")}/api/core/tesseract/conferencing/token`, {
82268
+ method: "POST",
82269
+ headers: { "content-type": "application/json", authorization: `Bearer ${accessToken}` },
82270
+ body: JSON.stringify({ room: sessionId, identity: "host", name: hostname4() }),
82271
+ signal: AbortSignal.timeout(1e4)
82272
+ });
82273
+ if (!r.ok) {
82274
+ log9(`[rdesk call] token mint ${r.status} (conferencing configured?)`);
82275
+ return;
82276
+ }
82277
+ const j = await r.json().catch(() => ({}));
82278
+ if (!j.token || !j.url) {
82279
+ log9("[rdesk call] token response missing token/url");
82280
+ return;
82281
+ }
82282
+ showRdeskCallWindow({ url: j.url, token: j.token, room: sessionId });
82283
+ } catch (e) {
82284
+ log9("[rdesk call] token mint failed", e instanceof Error ? e.message : String(e));
82285
+ }
82286
+ })();
82287
+ },
82288
+ // In-session chat from the viewer → surface it to the operator as a native notification.
82289
+ // On macOS the notification carries an inline REPLY box (hasReply) whose text is sent
82290
+ // straight back via sendChat — a full operator chat loop with no extra window. Off-mac (no
82291
+ // hasReply) it's receive-only for now; the daemon log always records it as a fallback.
82292
+ onChat: ({ sessionId, viewerName, text: text8 }) => {
82293
+ const NotifCtor = electronModule()?.Notification;
82294
+ if (!NotifCtor) {
82295
+ log9(`[rdesk chat] ${viewerName ?? "viewer"}: ${text8}`);
82296
+ return;
82297
+ }
82298
+ try {
82299
+ const n = new NotifCtor({ title: `${viewerName ?? "Remote viewer"} \u2014 remote desktop`, body: text8, hasReply: true, replyPlaceholder: "Reply\u2026" });
82300
+ n.on("reply", (_e, reply3) => {
82301
+ const r = typeof reply3 === "string" ? reply3.trim() : "";
82302
+ if (r)
82303
+ rdeskRef?.sendChat(sessionId, r);
82304
+ });
82305
+ n.show();
82306
+ } catch (e) {
82307
+ log9("[rdesk chat] notify failed", e instanceof Error ? e.message : String(e));
82308
+ }
80915
82309
  },
80916
- log: (m) => log5(m)
82310
+ log: (m) => log9(m)
80917
82311
  });
80918
82312
  rdeskRef = rdesk;
80919
82313
  rdeskManager = rdesk;
@@ -80922,7 +82316,7 @@ async function runDaemon(opts = {}) {
80922
82316
  token: async () => accessToken,
80923
82317
  deviceId: c.deviceId,
80924
82318
  projectDir: daemonWorkspaceDir(),
80925
- log: (...args) => log5("chat-agent", ...args.map((a) => String(a)))
82319
+ log: (...args) => log9("chat-agent", ...args.map((a) => String(a)))
80926
82320
  });
80927
82321
  const hooks2 = {
80928
82322
  deliverToRenderer: () => {
@@ -80939,7 +82333,7 @@ async function runDaemon(opts = {}) {
80939
82333
  getRdeskClientWebContents()?.send("hostlink:own", own2);
80940
82334
  },
80941
82335
  notify: (n) => {
80942
- log5("notify", JSON.stringify(n));
82336
+ log9("notify", JSON.stringify(n));
80943
82337
  getRdeskClientWebContents()?.send("ccb:notify", n);
80944
82338
  },
80945
82339
  // No renderer, but DO forward remote change feeds to the macOS File Provider extension
@@ -80952,6 +82346,7 @@ async function runDaemon(opts = {}) {
80952
82346
  onHostsChanged: () => broadcastFileProviderHostsChanged(),
80953
82347
  pushTransferEvent: () => {
80954
82348
  },
82349
+ onDrop: (msg) => drops.onFrame(msg),
80955
82350
  pushTerm: () => {
80956
82351
  },
80957
82352
  // Remote-desktop host: a viewer drives an rdesk session on this machine.
@@ -81003,13 +82398,13 @@ async function runDaemon(opts = {}) {
81003
82398
  getPeerIdentityPub: (d) => getPeerIdentityPub({ apiOrigin: c.apiOrigin, accessToken, deviceId: d })
81004
82399
  };
81005
82400
  } catch (err2) {
81006
- log5("e2e responder init failed (staying plaintext)", err2.message);
82401
+ log9("e2e responder init failed (staying plaintext)", err2.message);
81007
82402
  }
81008
82403
  }
81009
82404
  startControlLink({ deviceId: c.deviceId, power: createNodePowerController(), tokenSource, version: VERSION, e2e }, hooks2);
81010
- startFileProviderBridge(log5);
81011
- startAutofillBridge(log5);
81012
- log5(`started \u2014 device ${c.deviceId}, user ${c.userId}, relay ${c.backendOrigin}, local :${localInfo?.localPort ?? "off"}, version ${VERSION}`);
82405
+ startFileProviderBridge(log9);
82406
+ startAutofillBridge(log9);
82407
+ log9(`started \u2014 device ${c.deviceId}, user ${c.userId}, relay ${c.backendOrigin}, local :${localInfo?.localPort ?? "off"}, version ${VERSION}`);
81013
82408
  await refresh();
81014
82409
  timer = setInterval(() => {
81015
82410
  void refresh();
@@ -81034,14 +82429,14 @@ var CHUNK_BYTES = 256 * 1024;
81034
82429
  import { platform as platform3 } from "node:os";
81035
82430
 
81036
82431
  // ../../packages/host-core/dist/service/launchd.js
81037
- import { writeFileSync as writeFileSync26, existsSync as existsSync29, mkdirSync as mkdirSync27, unlinkSync as unlinkSync4 } from "node:fs";
81038
- import { dirname as dirname22, join as join51 } from "node:path";
82432
+ import { writeFileSync as writeFileSync26, existsSync as existsSync29, mkdirSync as mkdirSync27, unlinkSync as unlinkSync5 } from "node:fs";
82433
+ import { dirname as dirname24, join as join55 } from "node:path";
81039
82434
  import { homedir as homedir8, userInfo } from "node:os";
81040
82435
 
81041
82436
  // ../../packages/host-core/dist/service/exec.js
81042
- import { execFile as execFile6, spawn as spawn10 } from "node:child_process";
81043
- import { promisify as promisify5 } from "node:util";
81044
- var run3 = promisify5(execFile6);
82437
+ import { execFile as execFile7, spawn as spawn10 } from "node:child_process";
82438
+ import { promisify as promisify6 } from "node:util";
82439
+ var run3 = promisify6(execFile7);
81045
82440
  function runInherit(cmd, args) {
81046
82441
  return new Promise((resolve12) => {
81047
82442
  const p = spawn10(cmd, args, { stdio: "inherit" });
@@ -81059,10 +82454,10 @@ function xml(s) {
81059
82454
  }
81060
82455
  function makeLaunchd(instanceId) {
81061
82456
  const label = SERVICE_LABEL_BASE + (instanceId ? `.${instanceId}` : "");
81062
- const plistPath = () => join51(homedir8(), "Library", "LaunchAgents", `${label}.plist`);
82457
+ const plistPath = () => join55(homedir8(), "Library", "LaunchAgents", `${label}.plist`);
81063
82458
  const domainTarget = () => `gui/${userInfo().uid}`;
81064
82459
  const serviceTarget = () => `${domainTarget()}/${label}`;
81065
- const logFile = () => join51(daemonDir2(), "daemon.log");
82460
+ const logFile = () => join55(daemonDir2(), "daemon.log");
81066
82461
  const workDirOf = (spec) => spec.workingDirectory ?? daemonDir2();
81067
82462
  function buildPlist(spec) {
81068
82463
  const args = [spec.exec, ...spec.args].map((a) => ` <string>${xml(a)}</string>`).join("\n");
@@ -81108,8 +82503,8 @@ ${env}
81108
82503
  return {
81109
82504
  async install(spec) {
81110
82505
  mkdirSync27(workDirOf(spec), { recursive: true });
81111
- mkdirSync27(dirname22(plistPath()), { recursive: true });
81112
- mkdirSync27(dirname22(spec.logPath), { recursive: true });
82506
+ mkdirSync27(dirname24(plistPath()), { recursive: true });
82507
+ mkdirSync27(dirname24(spec.logPath), { recursive: true });
81113
82508
  writeFileSync26(plistPath(), buildPlist(spec));
81114
82509
  await run3("launchctl", ["bootout", serviceTarget()]).catch(() => {
81115
82510
  });
@@ -81124,7 +82519,7 @@ ${env}
81124
82519
  });
81125
82520
  if (existsSync29(plistPath())) {
81126
82521
  try {
81127
- unlinkSync4(plistPath());
82522
+ unlinkSync5(plistPath());
81128
82523
  } catch {
81129
82524
  }
81130
82525
  }
@@ -81160,8 +82555,8 @@ ${env}
81160
82555
  }
81161
82556
 
81162
82557
  // ../../packages/host-core/dist/service/systemd.js
81163
- import { writeFileSync as writeFileSync27, existsSync as existsSync30, mkdirSync as mkdirSync28, unlinkSync as unlinkSync5 } from "node:fs";
81164
- import { dirname as dirname23, join as join52 } from "node:path";
82558
+ import { writeFileSync as writeFileSync27, existsSync as existsSync30, mkdirSync as mkdirSync28, unlinkSync as unlinkSync6 } from "node:fs";
82559
+ import { dirname as dirname25, join as join56 } from "node:path";
81165
82560
  import { homedir as homedir9, userInfo as userInfo2 } from "node:os";
81166
82561
  function unitVal(v) {
81167
82562
  return v.replace(/%/g, "%%");
@@ -81172,8 +82567,8 @@ function shq(s) {
81172
82567
  function makeSystemd(instanceId) {
81173
82568
  const unit = `tesseract-host${instanceId ? `-${instanceId}` : ""}.service`;
81174
82569
  const unitPath = () => {
81175
- const base = process.env.XDG_CONFIG_HOME || join52(homedir9(), ".config");
81176
- return join52(base, "systemd", "user", unit);
82570
+ const base = process.env.XDG_CONFIG_HOME || join56(homedir9(), ".config");
82571
+ return join56(base, "systemd", "user", unit);
81177
82572
  };
81178
82573
  const workDirOf = (spec) => spec.workingDirectory ?? daemonDir2();
81179
82574
  function buildUnit(spec) {
@@ -81201,8 +82596,8 @@ WantedBy=default.target
81201
82596
  return {
81202
82597
  async install(spec) {
81203
82598
  mkdirSync28(workDirOf(spec), { recursive: true });
81204
- mkdirSync28(dirname23(unitPath()), { recursive: true });
81205
- mkdirSync28(dirname23(spec.logPath), { recursive: true });
82599
+ mkdirSync28(dirname25(unitPath()), { recursive: true });
82600
+ mkdirSync28(dirname25(spec.logPath), { recursive: true });
81206
82601
  writeFileSync27(unitPath(), buildUnit(spec));
81207
82602
  await run3("loginctl", ["enable-linger", userInfo2().username]).catch(() => {
81208
82603
  });
@@ -81217,7 +82612,7 @@ WantedBy=default.target
81217
82612
  });
81218
82613
  if (existsSync30(unitPath())) {
81219
82614
  try {
81220
- unlinkSync5(unitPath());
82615
+ unlinkSync6(unitPath());
81221
82616
  } catch {
81222
82617
  }
81223
82618
  }
@@ -81246,12 +82641,12 @@ WantedBy=default.target
81246
82641
  }
81247
82642
 
81248
82643
  // ../../packages/host-core/dist/service/windows.js
81249
- import { writeFileSync as writeFileSync28, existsSync as existsSync31, mkdirSync as mkdirSync29, unlinkSync as unlinkSync6 } from "node:fs";
81250
- import { dirname as dirname24, join as join53 } from "node:path";
82644
+ import { writeFileSync as writeFileSync28, existsSync as existsSync31, mkdirSync as mkdirSync29, unlinkSync as unlinkSync7 } from "node:fs";
82645
+ import { dirname as dirname26, join as join57 } from "node:path";
81251
82646
  function makeWindowsTask(instanceId) {
81252
82647
  const task = `Fineguide\\TesseractHost${instanceId ? `-${instanceId}` : ""}`;
81253
- const wrapperPath = () => join53(daemonDir2(), `run-host${instanceId ? `-${instanceId}` : ""}.cmd`);
81254
- const logFile = () => join53(daemonDir2(), "daemon.log");
82648
+ const wrapperPath = () => join57(daemonDir2(), `run-host${instanceId ? `-${instanceId}` : ""}.cmd`);
82649
+ const logFile = () => join57(daemonDir2(), "daemon.log");
81255
82650
  const oneLine2 = (s) => s.replace(/[\r\n]+/g, " ");
81256
82651
  const envVal = (v) => oneLine2(v).replace(/%/g, "%%").replace(/"/g, "");
81257
82652
  const cmdArg = (a) => `"${oneLine2(a).replace(/"/g, "")}"`;
@@ -81268,8 +82663,8 @@ start "" /b ${cmd} >> "${oneLine2(spec.logPath).replace(/"/g, "")}" 2>&1\r
81268
82663
  return {
81269
82664
  async install(spec) {
81270
82665
  mkdirSync29(spec.workingDirectory ?? daemonDir2(), { recursive: true });
81271
- mkdirSync29(dirname24(spec.logPath), { recursive: true });
81272
- mkdirSync29(dirname24(wrapperPath()), { recursive: true });
82666
+ mkdirSync29(dirname26(spec.logPath), { recursive: true });
82667
+ mkdirSync29(dirname26(wrapperPath()), { recursive: true });
81273
82668
  writeFileSync28(wrapperPath(), buildWrapper(spec));
81274
82669
  await run3("schtasks", ["/Create", "/TN", task, "/SC", "ONLOGON", "/RL", "LIMITED", "/F", "/TR", `"${wrapperPath()}"`]);
81275
82670
  await run3("schtasks", ["/Run", "/TN", task]).catch(() => {
@@ -81280,7 +82675,7 @@ start "" /b ${cmd} >> "${oneLine2(spec.logPath).replace(/"/g, "")}" 2>&1\r
81280
82675
  });
81281
82676
  if (existsSync31(wrapperPath())) {
81282
82677
  try {
81283
- unlinkSync6(wrapperPath());
82678
+ unlinkSync7(wrapperPath());
81284
82679
  } catch {
81285
82680
  }
81286
82681
  }
@@ -81334,9 +82729,9 @@ function getServiceController(instanceId) {
81334
82729
 
81335
82730
  // src/auth.ts
81336
82731
  var import_qrcode_terminal = __toESM(require_main(), 1);
81337
- import { randomUUID as randomUUID17 } from "node:crypto";
82732
+ import { randomUUID as randomUUID18 } from "node:crypto";
81338
82733
  import { existsSync as existsSync32, rmSync as rmSync8 } from "node:fs";
81339
- import { join as join54 } from "node:path";
82734
+ import { join as join58 } from "node:path";
81340
82735
 
81341
82736
  // src/config.ts
81342
82737
  import { hostname as hostname5 } from "node:os";
@@ -81476,7 +82871,7 @@ async function login(opts) {
81476
82871
  process.exit(1);
81477
82872
  }
81478
82873
  }
81479
- const deviceId2 = readCredentials()?.deviceId ?? process.env.CCB_DEVICE_ID ?? randomUUID17().slice(0, 8);
82874
+ const deviceId2 = readCredentials()?.deviceId ?? process.env.CCB_DEVICE_ID ?? randomUUID18().slice(0, 8);
81480
82875
  const label = opts.name ?? defaultLabel();
81481
82876
  if (opts.token) {
81482
82877
  const dt = await fetchJson(`${api}/auth/device-token`, {
@@ -81537,7 +82932,7 @@ async function logout() {
81537
82932
  }
81538
82933
  for (const f of ["credentials.json", "status.json", "daemon.lock"]) {
81539
82934
  try {
81540
- const p = join54(daemonDir2(), f);
82935
+ const p = join58(daemonDir2(), f);
81541
82936
  if (existsSync32(p)) rmSync8(p);
81542
82937
  } catch {
81543
82938
  }
@@ -81584,7 +82979,7 @@ function isNewer(latest, current) {
81584
82979
  for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
81585
82980
  return false;
81586
82981
  }
81587
- function log4(...args) {
82982
+ function log8(...args) {
81588
82983
  console.log(`[self-update ${(/* @__PURE__ */ new Date()).toISOString()}]`, ...args);
81589
82984
  }
81590
82985
  async function fetchLatest() {
@@ -81630,25 +83025,25 @@ function verifyNpmSignature(version2, integrity, sig) {
81630
83025
  function installVersion(target) {
81631
83026
  return new Promise((resolve12) => {
81632
83027
  const npm = process.platform === "win32" ? "npm.cmd" : "npm";
81633
- log4(`installing tesseract-agent@${target} \u2026`);
83028
+ log8(`installing tesseract-agent@${target} \u2026`);
81634
83029
  let child;
81635
83030
  try {
81636
83031
  child = spawn11(npm, ["install", "-g", `tesseract-agent@${target}`], { stdio: ["ignore", "inherit", "inherit"] });
81637
83032
  } catch (e) {
81638
- log4("npm spawn threw:", e instanceof Error ? e.message : String(e));
83033
+ log8("npm spawn threw:", e instanceof Error ? e.message : String(e));
81639
83034
  resolve12(false);
81640
83035
  return;
81641
83036
  }
81642
83037
  child.on("error", (e) => {
81643
- log4("npm not runnable:", e.message);
83038
+ log8("npm not runnable:", e.message);
81644
83039
  resolve12(false);
81645
83040
  });
81646
83041
  child.on("close", (code) => {
81647
83042
  if (code === 0) {
81648
- log4(`installed ${target}`);
83043
+ log8(`installed ${target}`);
81649
83044
  resolve12(true);
81650
83045
  } else {
81651
- log4(`npm install exited ${code} \u2014 staying on the current version`);
83046
+ log8(`npm install exited ${code} \u2014 staying on the current version`);
81652
83047
  resolve12(false);
81653
83048
  }
81654
83049
  });
@@ -81661,15 +83056,15 @@ async function checkOnce(current) {
81661
83056
  try {
81662
83057
  const latest = await fetchLatest();
81663
83058
  if (!latest || !isNewer(latest, current)) return;
81664
- log4(`update available: ${current} \u2192 ${latest}`);
83059
+ log8(`update available: ${current} \u2192 ${latest}`);
81665
83060
  const dist = await fetchVersionDist(latest);
81666
83061
  if (!verifyNpmSignature(latest, dist?.integrity, dist?.sig)) {
81667
- log4(`REFUSING update to ${latest}: registry signature did not verify \u2014 staying on ${current}`);
83062
+ log8(`REFUSING update to ${latest}: registry signature did not verify \u2014 staying on ${current}`);
81668
83063
  return;
81669
83064
  }
81670
- log4(`registry signature verified for ${latest} (keyid ${dist?.keyid ?? "?"})`);
83065
+ log8(`registry signature verified for ${latest} (keyid ${dist?.keyid ?? "?"})`);
81671
83066
  if (await installVersion(latest)) {
81672
- log4("restarting on the new version (supervisor re-spawns) \u2026");
83067
+ log8("restarting on the new version (supervisor re-spawns) \u2026");
81673
83068
  process.exit(RESTART_EXIT_CODE);
81674
83069
  }
81675
83070
  } finally {
@@ -81678,11 +83073,11 @@ async function checkOnce(current) {
81678
83073
  }
81679
83074
  function startSelfUpdater(current) {
81680
83075
  if (disabled()) {
81681
- log4("disabled via TESSERACT_AUTO_UPDATE/CCB_AUTO_UPDATE");
83076
+ log8("disabled via TESSERACT_AUTO_UPDATE/CCB_AUTO_UPDATE");
81682
83077
  return;
81683
83078
  }
81684
83079
  if (!isReleaseVersion(current)) {
81685
- log4(`skipped \u2014 non-release version "${current}"`);
83080
+ log8(`skipped \u2014 non-release version "${current}"`);
81686
83081
  return;
81687
83082
  }
81688
83083
  const first = setTimeout(() => {
@@ -81696,7 +83091,7 @@ function startSelfUpdater(current) {
81696
83091
  }
81697
83092
 
81698
83093
  // src/service.ts
81699
- import { join as join55 } from "node:path";
83094
+ import { join as join59 } from "node:path";
81700
83095
  import { platform as platform4 } from "node:os";
81701
83096
  import { existsSync as existsSync33, readFileSync as readFileSync31 } from "node:fs";
81702
83097
  function controller(instanceId) {
@@ -81711,7 +83106,7 @@ function buildSpec(cliEntry) {
81711
83106
  exec: process.execPath,
81712
83107
  args: [cliEntry, "run"],
81713
83108
  env: { CCB_DAEMON_DIR: daemonDir2() },
81714
- logPath: join55(daemonDir2(), "daemon.log")
83109
+ logPath: join59(daemonDir2(), "daemon.log")
81715
83110
  };
81716
83111
  }
81717
83112
  var isAlive = (pid) => {
@@ -81723,7 +83118,7 @@ var isAlive = (pid) => {
81723
83118
  }
81724
83119
  };
81725
83120
  function runningDaemonInfo() {
81726
- const lock = join55(daemonDir2(), "daemon.lock");
83121
+ const lock = join59(daemonDir2(), "daemon.lock");
81727
83122
  try {
81728
83123
  if (!existsSync33(lock)) return null;
81729
83124
  const pid = Number(readFileSync31(lock, "utf-8").trim());
@@ -81762,7 +83157,7 @@ async function logs(instanceId, follow) {
81762
83157
  // src/cli.ts
81763
83158
  function pkgVersion() {
81764
83159
  try {
81765
- const p = join56(dirname25(fileURLToPath4(import.meta.url)), "..", "package.json");
83160
+ const p = join60(dirname27(fileURLToPath4(import.meta.url)), "..", "package.json");
81766
83161
  return JSON.parse(readFileSync32(p, "utf8")).version ?? "0.0.0";
81767
83162
  } catch {
81768
83163
  return "0.0.0";
@@ -81955,7 +83350,7 @@ async function linkCommand(rest, flags) {
81955
83350
  process.exit(1);
81956
83351
  }
81957
83352
  async function captureClaudeSubscription(flags) {
81958
- const home = mkdtempSync2(join56(tmpdir4(), "tesseract-claude-login-"));
83353
+ const home = mkdtempSync2(join60(tmpdir4(), "tesseract-claude-login-"));
81959
83354
  const binary = typeof flags["claude-path"] === "string" ? flags["claude-path"] : process.env.CCB_ENGINE_PATH_CLAUDE_CODE || "claude";
81960
83355
  const args = ["auth", "login", "--claudeai"];
81961
83356
  if (typeof flags.email === "string") args.push("--email", flags.email);
@@ -81972,7 +83367,7 @@ async function captureClaudeSubscription(flags) {
81972
83367
  child.once("close", (exitCode, signal) => resolveCode(signal ? 1 : exitCode ?? 1));
81973
83368
  });
81974
83369
  if (code !== 0) throw new Error(`Claude sign-in did not complete (exit ${code}).`);
81975
- const file2 = join56(home, ".credentials.json");
83370
+ const file2 = join60(home, ".credentials.json");
81976
83371
  if (existsSync34(file2)) return readFileSync32(file2, "utf8");
81977
83372
  if (process.platform === "darwin") {
81978
83373
  const value = execFileSync5("security", [