tesseract-agent 0.9.5 → 0.9.7

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 +1917 -431
  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 });
@@ -22751,13 +22752,13 @@ Add it to an EDL as an audio track (duckUnderSpeech:true under speech).`);
22751
22752
  return text6(`Speech generated: ${res.file.path}
22752
22753
  Overlay it in render_edl as an audio track (mute the original speaker's segments to replace their voice).`);
22753
22754
  }),
22754
- tool7("list_avatars", "List the HeyGen avatars available on the user's account (for virtual-presenter videos), with their avatar ids. Key-gated: needs a HeyGen API key. Pair an avatar_id with a HeyGen voice for generate_avatar_video.", {}, async () => {
22755
+ tool7("list_avatars", "List the HeyGen avatars available for virtual-presenter videos, with their avatar ids. Works on the free bundled tier when signed in, or on the user's own HeyGen key if they set one. Pair an avatar_id with a HeyGen voice for generate_avatar_video.", {}, async () => {
22755
22756
  const res = await deps.listAvatars();
22756
22757
  if (!res.ok)
22757
22758
  return text6(`Could not list avatars: ${res.error ?? "unknown error"}`, true);
22758
22759
  return jsonText(res.avatars ?? []);
22759
22760
  }),
22760
- tool7("generate_avatar_video", "Generate a talking-presenter (avatar) video from a script (HeyGen \u2014 paid, needs a HeyGen key). Draft the script yourself; pick an avatar via list_avatars and a HeyGen voice_id. Takes 1\u20135 minutes; the finished video is displayed in the chat. Use for news-desk reads, explainers, spokesperson clips. ONLY use avatars/likenesses you are authorized to use.", {
22761
+ tool7("generate_avatar_video", "Generate a talking-presenter (avatar) video from a script (HeyGen). Runs on the free bundled tier when signed in, or on the user's own HeyGen key if they set one \u2014 they never need to be asked for a key up front. Draft the script yourself; pick an avatar via list_avatars and a HeyGen voice_id. Takes 1\u20135 minutes; the finished video is displayed in the chat. Use for news-desk reads, explainers, spokesperson clips. ONLY use avatars/likenesses you are authorized to use.", {
22761
22762
  script: external_exports.string().describe("What the presenter says."),
22762
22763
  avatar_id: external_exports.string().optional().describe("HeyGen avatar id (from list_avatars). Provide this OR talking_photo_id."),
22763
22764
  talking_photo_id: external_exports.string().optional().describe("HeyGen talking-photo id (alternative to avatar_id)."),
@@ -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;
@@ -24265,6 +24266,13 @@ ${JSON.stringify(r, null, 2)}`
24265
24266
  // ── image (image-generation/edit providers) ──
24266
24267
  { id: "image.list_providers", domain: "image", title: "List image providers", description: "Image providers with configured/default/model status.", input: external_exports.object({}), mutation: "read", availability: "host", binding: { kind: "ipc", channel: "imageEdit", verb: "list" } },
24267
24268
  { id: "image.set_provider_key", domain: "image", title: "Set image provider key", description: "Set (blank clears) an image provider's API key.", input: external_exports.object({ provider_id: IMAGE_PROVIDER, api_key: external_exports.string().describe("API key (secret); blank clears.") }), mutation: "write", availability: "host", binding: { kind: "ipc", channel: "imageEdit", verb: "setKey" } },
24269
+ // Avatar videos are the one media provider whose key lives outside the image pool
24270
+ // (HeyGen, its own vendor) — but it is set in the SAME Settings panel, so it belongs
24271
+ // to the same domain rather than earning a domain of its own. `has_` returns a
24272
+ // boolean, never the key: writing a secret in is a capability, reading one out is
24273
+ // not (the same line `auth:token` sits on the wrong side of).
24274
+ { id: "image.set_avatar_key", domain: "image", title: "Set avatar-video (HeyGen) key", description: "Set (blank clears) the HeyGen API key used for avatar/presenter videos. Without one, avatar videos fall back to the free Tesseract proxy tier for signed-in users.", input: external_exports.object({ api_key: external_exports.string().describe("HeyGen API key (secret); blank clears.") }), mutation: "write", availability: "host", binding: { kind: "ipc", channel: "heygen", verb: "setKey" } },
24275
+ { id: "image.has_avatar_key", domain: "image", title: "Avatar-video key configured?", description: "Whether a personal HeyGen key is set. Never returns the key itself.", input: external_exports.object({}), mutation: "read", availability: "host", binding: { kind: "ipc", channel: "heygen", verb: "hasKey" } },
24268
24276
  { id: "image.remove_provider_key", domain: "image", title: "Remove image provider key", description: "Remove all credentials for an image provider.", input: external_exports.object({ provider_id: IMAGE_PROVIDER }), mutation: "write", availability: "host", binding: { kind: "ipc", channel: "imageEdit", verb: "removeKey" } },
24269
24277
  { id: "image.set_default_provider", domain: "image", title: "Set default image provider", description: "Set the default image provider.", input: external_exports.object({ provider_id: IMAGE_PROVIDER }), mutation: "write", availability: "host", binding: { kind: "ipc", channel: "imageEdit", verb: "setDefault" } },
24270
24278
  { id: "image.set_provider_model", domain: "image", title: "Set image provider model", description: "Set the model for an image provider.", input: external_exports.object({ provider_id: IMAGE_PROVIDER, model: external_exports.string() }), mutation: "write", availability: "host", binding: { kind: "ipc", channel: "imageEdit", verb: "setModel" } },
@@ -26463,9 +26471,9 @@ function sourcesEqual(a, b) {
26463
26471
  if (a.length !== b.length)
26464
26472
  return false;
26465
26473
  const as = [...a].sort();
26466
- const bs3 = [...b].sort();
26474
+ const bs4 = [...b].sort();
26467
26475
  for (let i = 0; i < as.length; i++)
26468
- if (as[i] !== bs3[i])
26476
+ if (as[i] !== bs4[i])
26469
26477
  return false;
26470
26478
  return true;
26471
26479
  }
@@ -36728,9 +36736,9 @@ var require_encoder = __commonJS({
36728
36736
  RGB_YUV_TABLE[i + 1792 >> 0] = -5329 * i;
36729
36737
  }
36730
36738
  }
36731
- function writeBits(bs3) {
36732
- var value = bs3[0];
36733
- var posval = bs3[1] - 1;
36739
+ function writeBits(bs4) {
36740
+ var value = bs4[0];
36741
+ var posval = bs4[1] - 1;
36734
36742
  while (posval >= 0) {
36735
36743
  if (value & 1 << posval) {
36736
36744
  bytenew |= 1 << bytepos;
@@ -58598,12 +58606,12 @@ import { createHash as createHash8 } from "node:crypto";
58598
58606
  import { execFileSync as execFileSync5, spawn as spawn12 } from "node:child_process";
58599
58607
  import { fileURLToPath as fileURLToPath4 } from "node:url";
58600
58608
  import { tmpdir as tmpdir4 } from "node:os";
58601
- import { dirname as dirname25, join as join56, resolve as resolve11 } from "node:path";
58609
+ import { dirname as dirname27, join as join60, resolve as resolve11 } from "node:path";
58602
58610
  import { createInterface } from "node:readline/promises";
58603
58611
 
58604
58612
  // ../../packages/host-core/dist/control-link.js
58605
58613
  import { hostname as hostname3 } from "node:os";
58606
- import { join as join41, basename as basename8 } from "node:path";
58614
+ import { join as join42, basename as basename9 } from "node:path";
58607
58615
  import { mkdirSync as mkdirSync18 } from "node:fs";
58608
58616
 
58609
58617
  // ../../node_modules/ws/wrapper.mjs
@@ -59030,13 +59038,27 @@ var E2ESessions = class {
59030
59038
 
59031
59039
  // ../../packages/e2e/dist/seal.js
59032
59040
  var SEAL_INFO = utf8("tesseract-seal-v1");
59041
+ async function openFromPrivateKey(recipientPrivate, sealed, aad = new Uint8Array()) {
59042
+ const shared = await ecdh(recipientPrivate, unb64u(sealed.epk));
59043
+ const key2 = await importAesKey(await hkdf(shared, new Uint8Array(), SEAL_INFO, 32));
59044
+ return aesOpen(key2, unb64u(sealed.nonce), unb64u(sealed.ct), aad);
59045
+ }
59033
59046
 
59034
59047
  // ../../packages/e2e/dist/attest.js
59035
59048
  var SEALING_ATTEST_CTX = utf8("tesseract-sealing-attest-v1");
59049
+ function attestMessage(sealingPubRaw) {
59050
+ const out = new Uint8Array(SEALING_ATTEST_CTX.length + sealingPubRaw.length);
59051
+ out.set(SEALING_ATTEST_CTX, 0);
59052
+ out.set(sealingPubRaw, SEALING_ATTEST_CTX.length);
59053
+ return out;
59054
+ }
59055
+ async function attestSealingKey(identityPrivate, sealingPubRaw) {
59056
+ return b64u(await sign(identityPrivate, attestMessage(sealingPubRaw)));
59057
+ }
59036
59058
 
59037
59059
  // ../../packages/host-core/dist/daemon-agent.js
59038
59060
  init_base_paths();
59039
- import { join as join29 } from "node:path";
59061
+ import { join as join30 } from "node:path";
59040
59062
 
59041
59063
  // ../../packages/host-core/dist/surface-hub.js
59042
59064
  init_dist();
@@ -60712,7 +60734,7 @@ function convertTool(t) {
60712
60734
  if (!t || typeof t.name !== "string" || typeof t.execute !== "function")
60713
60735
  return null;
60714
60736
  const shape = t.parameters?.shape ?? t.inputSchema?.shape ?? {};
60715
- const exec = t.execute;
60737
+ const exec2 = t.execute;
60716
60738
  return {
60717
60739
  name: t.name,
60718
60740
  description: t.description ?? t.name,
@@ -60720,12 +60742,12 @@ function convertTool(t) {
60720
60742
  // Fallback handler — used when no surface host is wired (off-daemon / no session): the
60721
60743
  // OpenClaw tool still runs with a DEGRADED ctx whose ui methods no-op (open → an
60722
60744
  // already-closed handle), so `ctx.ui.notify(...)` degrades instead of throwing on `{}`.
60723
- handler: async (args) => toCallToolResult(await exec(args, NOOP_UI_CTX)),
60745
+ handler: async (args) => toCallToolResult(await exec2(args, NOOP_UI_CTX)),
60724
60746
  // Ctx-aware path (Pillar 1 P4): agent-manager's wrapCtxTool injects a per-session ctx
60725
60747
  // ({ ui, sessionId, toolId }) here, so the OpenClaw tool's execute(args, ctx) can open
60726
60748
  // and drive surfaces via ctx.ui. Unknown ctx members (channel/secret/log) read as
60727
60749
  // undefined — graceful, matching the Proxy no-op contract.
60728
- __ctxHandler: async (args, ctx2) => toCallToolResult(await exec(args, ctx2))
60750
+ __ctxHandler: async (args, ctx2) => toCallToolResult(await exec2(args, ctx2))
60729
60751
  // Our narrow CallToolResult is a structural subset of the SDK's (which carries an
60730
60752
  // index signature + a richer content union); cast through unknown intentionally.
60731
60753
  };
@@ -62192,12 +62214,17 @@ async function closeEnginePage(browserId) {
62192
62214
  }
62193
62215
  }
62194
62216
 
62217
+ // ../../packages/host-core/dist/gui/windows.js
62218
+ import { randomUUID as randomUUID8 } from "node:crypto";
62219
+ import { join as join20 } from "node:path";
62220
+
62195
62221
  // ../../packages/host-core/dist/gui/daemon-gui.js
62196
62222
  import { join as join19, dirname as dirname8 } from "node:path";
62197
62223
  import { fileURLToPath as fileURLToPath2 } from "node:url";
62198
62224
  import { existsSync as existsSync9 } from "node:fs";
62199
62225
  var el = null;
62200
62226
  var guiReady = false;
62227
+ var log2 = (...a) => console.log("[daemon-gui]", ...a);
62201
62228
  var tray = null;
62202
62229
  function formatAccelerator(accel) {
62203
62230
  const sym = {
@@ -62256,6 +62283,68 @@ function buildTrayMenu(deps) {
62256
62283
  ]);
62257
62284
  }
62258
62285
  var rdeskPowerBlockerId = null;
62286
+ var trayActivity = null;
62287
+ function trayTooltip() {
62288
+ if (process.env.CCB_RDESK_ROLE === "server")
62289
+ return "Tesseract Remote Desktop \u2014 this machine";
62290
+ if (process.env.CCB_RDESK_ROLE === "client")
62291
+ return "Tesseract Remote Desktop";
62292
+ const bits = [];
62293
+ if (rdeskActive > 0)
62294
+ bits.push(`sharing screen (${rdeskActive})`);
62295
+ if (trayActivity)
62296
+ bits.push(trayActivity);
62297
+ return bits.length ? `Tesseract \u2014 ${bits.join(" \xB7 ")}` : "Tesseract";
62298
+ }
62299
+ function setTrayActivity(label) {
62300
+ trayActivity = label;
62301
+ if (!tray)
62302
+ return;
62303
+ try {
62304
+ tray.setToolTip(trayTooltip());
62305
+ } catch {
62306
+ }
62307
+ }
62308
+ function daemonNotify(opts) {
62309
+ if (!guiReady || !el)
62310
+ return false;
62311
+ try {
62312
+ if (!el.Notification.isSupported())
62313
+ return false;
62314
+ const actions = (opts.actions ?? []).slice(0, 3);
62315
+ const n = new el.Notification({
62316
+ title: opts.title,
62317
+ body: opts.body,
62318
+ silent: opts.silent,
62319
+ ...actions.length ? { actions: actions.map((a) => ({ type: "button", text: a.label })) } : {}
62320
+ });
62321
+ if (actions.length && opts.onAction) {
62322
+ n.on("action", (...a) => {
62323
+ const ix = typeof a[1] === "number" ? a[1] : -1;
62324
+ const chosen = actions[ix];
62325
+ if (chosen) {
62326
+ try {
62327
+ opts.onAction(chosen.id);
62328
+ } catch (e) {
62329
+ log2("notify action failed:", e instanceof Error ? e.message : String(e));
62330
+ }
62331
+ }
62332
+ });
62333
+ }
62334
+ if (opts.onClick)
62335
+ n.on("click", () => {
62336
+ try {
62337
+ opts.onClick();
62338
+ } catch {
62339
+ }
62340
+ });
62341
+ n.show();
62342
+ return true;
62343
+ } catch (e) {
62344
+ log2("notify failed:", e instanceof Error ? e.message : String(e));
62345
+ return false;
62346
+ }
62347
+ }
62259
62348
  function setRdeskTraySessions(count, onStopAll) {
62260
62349
  rdeskActive = count;
62261
62350
  rdeskStopAll = onStopAll;
@@ -62277,7 +62366,7 @@ function setRdeskTraySessions(count, onStopAll) {
62277
62366
  if (!tray || !trayDeps)
62278
62367
  return;
62279
62368
  try {
62280
- tray.setToolTip(count > 0 ? `Tesseract \u2014 sharing screen (${count})` : "Tesseract");
62369
+ tray.setToolTip(trayTooltip());
62281
62370
  tray.setContextMenu(buildTrayMenu(trayDeps));
62282
62371
  } catch {
62283
62372
  }
@@ -62352,7 +62441,7 @@ function daemonPreloadPath() {
62352
62441
  // ../../packages/host-core/dist/os-control-runner.js
62353
62442
  import { spawn as spawn6 } from "node:child_process";
62354
62443
  var IS_MAC = process.platform === "darwin";
62355
- var log2 = (...a) => console.log("[os-control]", ...a);
62444
+ var log3 = (...a) => console.log("[os-control]", ...a);
62356
62445
  function capture(command, args, opts = {}) {
62357
62446
  return new Promise((resolve12) => {
62358
62447
  const child = spawn6(command, args, { cwd: opts.cwd, windowsHide: true });
@@ -62499,7 +62588,7 @@ function createDaemonOsControlRunner() {
62499
62588
  if (o.at)
62500
62589
  n.moveMouse(Math.round(o.at.x), Math.round(o.at.y));
62501
62590
  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)}` : ""}`);
62591
+ log3(`${o.button ?? "left"}${o.double ? "-double" : ""}-click${o.at ? ` @ ${Math.round(o.at.x)},${Math.round(o.at.y)}` : ""}`);
62503
62592
  },
62504
62593
  async mouseDrag(from, to, o) {
62505
62594
  const n = await nutOrThrow();
@@ -62561,7 +62650,7 @@ function createDaemonOsControlRunner() {
62561
62650
  async typeText(t) {
62562
62651
  const n = await nutOrThrow();
62563
62652
  n.typeString(t);
62564
- log2(`type "${t.length > 50 ? `${t.slice(0, 50)}\u2026` : t}"`);
62653
+ log3(`type "${t.length > 50 ? `${t.slice(0, 50)}\u2026` : t}"`);
62565
62654
  },
62566
62655
  async pressKeys(combo) {
62567
62656
  const n = await nutOrThrow();
@@ -62569,7 +62658,7 @@ function createDaemonOsControlRunner() {
62569
62658
  if (!key2)
62570
62659
  throw new Error(`Empty key combo: "${combo}"`);
62571
62660
  n.keyTap(key2, modifiers);
62572
- log2(`key ${combo}`);
62661
+ log3(`key ${combo}`);
62573
62662
  },
62574
62663
  // App launch (the one non-input capability the voice path needs — shells `open`)
62575
62664
  async appLaunch(app) {
@@ -62581,6 +62670,83 @@ function createDaemonOsControlRunner() {
62581
62670
  }
62582
62671
 
62583
62672
  // ../../packages/host-core/dist/gui/windows.js
62673
+ function uiFile(name) {
62674
+ return join20(daemonUiDir(), name);
62675
+ }
62676
+ function notReady(what) {
62677
+ 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.`);
62678
+ }
62679
+ var ipcWired = false;
62680
+ var pendingInput = /* @__PURE__ */ new Map();
62681
+ function wireIpcOnce() {
62682
+ const el2 = electronModule();
62683
+ if (!el2 || ipcWired)
62684
+ return;
62685
+ ipcWired = true;
62686
+ el2.ipcMain.on("input:submit", (_e, payload) => {
62687
+ const p = payload;
62688
+ const resolve12 = pendingInput.get(p.reqId);
62689
+ if (resolve12) {
62690
+ pendingInput.delete(p.reqId);
62691
+ resolve12({ submitted: !!p.submitted, values: p.values ?? {} });
62692
+ }
62693
+ });
62694
+ }
62695
+ function showFormWindow(kind, payload, signal) {
62696
+ const el2 = electronModule();
62697
+ if (!el2)
62698
+ notReady("Asking for input");
62699
+ wireIpcOnce();
62700
+ const reqId = randomUUID8();
62701
+ const win3 = new el2.BrowserWindow({
62702
+ width: 460,
62703
+ height: 320,
62704
+ transparent: true,
62705
+ frame: false,
62706
+ resizable: false,
62707
+ alwaysOnTop: true,
62708
+ skipTaskbar: true,
62709
+ fullscreenable: false,
62710
+ show: false,
62711
+ webPreferences: { preload: daemonPreloadPath(), contextIsolation: true, sandbox: false }
62712
+ });
62713
+ win3.setAlwaysOnTop(true, "screen-saver");
62714
+ win3.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
62715
+ void win3.loadFile(uiFile("input.html"));
62716
+ win3.webContents.on("did-finish-load", () => {
62717
+ win3.webContents.send(`${kind}:show`, { reqId, ...payload });
62718
+ win3.center();
62719
+ win3.show();
62720
+ win3.focus();
62721
+ });
62722
+ return new Promise((resolve12) => {
62723
+ let done = false;
62724
+ const onAbort = () => finish({ submitted: false, values: {} });
62725
+ const finish = (r) => {
62726
+ if (done)
62727
+ return;
62728
+ done = true;
62729
+ signal?.removeEventListener("abort", onAbort);
62730
+ pendingInput.delete(reqId);
62731
+ try {
62732
+ if (!win3.isDestroyed())
62733
+ win3.close();
62734
+ } catch {
62735
+ }
62736
+ resolve12(r);
62737
+ };
62738
+ pendingInput.set(reqId, finish);
62739
+ win3.on("closed", () => finish({ submitted: false, values: {} }));
62740
+ if (signal?.aborted)
62741
+ onAbort();
62742
+ else
62743
+ signal?.addEventListener("abort", onAbort);
62744
+ });
62745
+ }
62746
+ async function confirmAction(spec, opts) {
62747
+ const r = await showFormWindow("confirm", { spec }, opts?.signal);
62748
+ return { confirmed: r.submitted };
62749
+ }
62584
62750
  var spotlightWin = null;
62585
62751
  function widgetDisplayRect() {
62586
62752
  return widgetDisplayBounds(spotlightWin) ?? null;
@@ -62598,15 +62764,15 @@ function widgetDisplayBounds(w) {
62598
62764
  }
62599
62765
 
62600
62766
  // ../../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";
62767
+ import { join as join21, dirname as dirname9 } from "node:path";
62768
+ import { randomUUID as randomUUID9 } from "node:crypto";
62603
62769
  init_dist4();
62604
62770
  var OWN_PID = process.pid;
62605
62771
  var runner = createDaemonOsControlRunner();
62606
62772
  var controlAbort = new AbortController();
62607
62773
  var IDLE_RELEASE_MS = 15 * 6e4;
62608
62774
  function uiPath(name) {
62609
- return join20(daemonUiDir(), name);
62775
+ return join21(daemonUiDir(), name);
62610
62776
  }
62611
62777
  var consentWin = null;
62612
62778
  var pendingConsent = /* @__PURE__ */ new Map();
@@ -62616,7 +62782,7 @@ function askRdeskConsent(opts) {
62616
62782
  return Promise.resolve(false);
62617
62783
  const disp = widgetDisplayRect() ?? el2.screen.getPrimaryDisplay().bounds;
62618
62784
  const W = 470, H = 320;
62619
- const win2 = new el2.BrowserWindow({
62785
+ const win3 = new el2.BrowserWindow({
62620
62786
  x: disp.x + Math.round((disp.width - W) / 2),
62621
62787
  y: disp.y + Math.round((disp.height - H) / 3),
62622
62788
  width: W,
@@ -62634,15 +62800,15 @@ function askRdeskConsent(opts) {
62634
62800
  backgroundColor: "#00000000",
62635
62801
  webPreferences: { preload: daemonPreloadPath(), contextIsolation: true, sandbox: false }
62636
62802
  });
62637
- win2.setAlwaysOnTop(true, "screen-saver");
62638
- win2.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
62639
- const reqId = randomUUID8();
62803
+ win3.setAlwaysOnTop(true, "screen-saver");
62804
+ win3.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
62805
+ const reqId = randomUUID9();
62640
62806
  const who = opts.viewerName ? `\u201C${opts.viewerName}\u201D` : "Someone";
62641
- void win2.loadFile(uiPath("consent.html")).then(() => {
62807
+ void win3.loadFile(uiPath("consent.html")).then(() => {
62642
62808
  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();
62809
+ win3.webContents.send("consent:show", { reqId, title: "Allow remote desktop access?", body: `${who} wants to view and control this computer.` });
62810
+ win3.show();
62811
+ win3.focus();
62646
62812
  el2.app.focus?.({ steal: true });
62647
62813
  log("rdesk: consent dialog shown");
62648
62814
  } catch (e) {
@@ -62657,16 +62823,16 @@ function askRdeskConsent(opts) {
62657
62823
  done = true;
62658
62824
  pendingConsent.delete(reqId);
62659
62825
  try {
62660
- if (!win2.isDestroyed())
62661
- win2.close();
62826
+ if (!win3.isDestroyed())
62827
+ win3.close();
62662
62828
  } catch {
62663
62829
  }
62664
- if (consentWin === win2)
62830
+ if (consentWin === win3)
62665
62831
  consentWin = null;
62666
62832
  resolve12(r.ok);
62667
62833
  };
62668
62834
  pendingConsent.set(reqId, finish);
62669
- win2.on("closed", () => finish({ ok: false, remember: false }));
62835
+ win3.on("closed", () => finish({ ok: false, remember: false }));
62670
62836
  setTimeout(() => finish({ ok: false, remember: false }), 6e4);
62671
62837
  });
62672
62838
  }
@@ -62676,7 +62842,7 @@ var MOONDREAM_URL = (process.env.CCB_MOONDREAM_URL?.trim() || "http://127.0.0.1:
62676
62842
  // ../../packages/host-core/dist/voice-control.js
62677
62843
  init_base_paths();
62678
62844
  import { existsSync as existsSync10, readFileSync as readFileSync12 } from "node:fs";
62679
- import { basename as basename2, join as join21 } from "node:path";
62845
+ import { basename as basename2, join as join22 } from "node:path";
62680
62846
 
62681
62847
  // ../../packages/voice-core/dist/providers/tool-adapters.js
62682
62848
  function toELClientTool(t) {
@@ -62936,7 +63102,7 @@ async function createElevenLabsAgent(args) {
62936
63102
  }
62937
63103
 
62938
63104
  // ../../packages/voice-core/dist/orchestrator.js
62939
- import { randomUUID as randomUUID9 } from "node:crypto";
63105
+ import { randomUUID as randomUUID10 } from "node:crypto";
62940
63106
 
62941
63107
  // ../../packages/voice-core/dist/summarizer.js
62942
63108
  var TURN_SYSTEM = [
@@ -63294,7 +63460,7 @@ Additional context: ${context}` : task;
63294
63460
  this.emitFocusedState();
63295
63461
  const sessionId = track.sessionId || void 0;
63296
63462
  this.deps.emit({ type: "voice:dispatched", text: message, sessionId });
63297
- const clientRequestId = randomUUID9();
63463
+ const clientRequestId = randomUUID10();
63298
63464
  track.pendingClientRequestId = clientRequestId;
63299
63465
  try {
63300
63466
  const sid = await this.deps.agent.dispatch({ message, clientRequestId, sessionId });
@@ -63327,7 +63493,7 @@ Additional context: ${context}` : task;
63327
63493
  this.emitFocusedState();
63328
63494
  const sessionId = track.sessionId || void 0;
63329
63495
  this.deps.emit({ type: "voice:dispatched", text: message, sessionId });
63330
- const clientRequestId = randomUUID9();
63496
+ const clientRequestId = randomUUID10();
63331
63497
  track.pendingClientRequestId = clientRequestId;
63332
63498
  try {
63333
63499
  const sid = await this.deps.agent.dispatch({ message, clientRequestId, sessionId });
@@ -63350,7 +63516,7 @@ Additional context: ${context}` : task;
63350
63516
  this.voice?.respondToTool(inv.callId, "Got it \u2014 passing that along.");
63351
63517
  this.focusTrack(track);
63352
63518
  this.deps.emit({ type: "voice:dispatched", text: addendum, sessionId: track.sessionId });
63353
- const clientRequestId = randomUUID9();
63519
+ const clientRequestId = randomUUID10();
63354
63520
  track.pendingClientRequestId = clientRequestId;
63355
63521
  try {
63356
63522
  await this.deps.agent.dispatch({ message: addendum, sessionId: track.sessionId, clientRequestId });
@@ -63388,7 +63554,7 @@ Additional context: ${context}` : task;
63388
63554
  this.voice?.respondToTool(inv.callId, "Got it \u2014 passing that along.");
63389
63555
  this.focusTrack(track);
63390
63556
  this.deps.emit({ type: "voice:dispatched", text: answer, sessionId: track.sessionId });
63391
- const clientRequestId = randomUUID9();
63557
+ const clientRequestId = randomUUID10();
63392
63558
  track.pendingClientRequestId = clientRequestId;
63393
63559
  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
63560
  track.state = "working";
@@ -63921,7 +64087,7 @@ async function mintOpenAi(apiKey, model, voice) {
63921
64087
  function readGeminiKeyFile() {
63922
64088
  try {
63923
64089
  const home = tesseractHome();
63924
- const f = join21(home, "daemon", "gemini.key");
64090
+ const f = join22(home, "daemon", "gemini.key");
63925
64091
  if (existsSync10(f)) {
63926
64092
  const k = readFileSync12(f, "utf-8").trim();
63927
64093
  if (k)
@@ -64345,9 +64511,9 @@ import { mkdirSync as mkdirSync14 } from "node:fs";
64345
64511
 
64346
64512
  // ../../packages/host-core/dist/image-gen-runner.js
64347
64513
  init_base_paths();
64348
- import { randomUUID as randomUUID10 } from "node:crypto";
64514
+ import { randomUUID as randomUUID11 } from "node:crypto";
64349
64515
  import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync13 } from "node:fs";
64350
- import { join as join22 } from "node:path";
64516
+ import { join as join23 } from "node:path";
64351
64517
 
64352
64518
  // ../../packages/image-edit-core/dist/transparent.js
64353
64519
  var import_fast_png = __toESM(require_lib(), 1);
@@ -64552,8 +64718,8 @@ var ImageEditError = class extends Error {
64552
64718
  function toBase64(bytes2) {
64553
64719
  return Buffer.from(bytes2).toString("base64");
64554
64720
  }
64555
- function fromBase64(b64) {
64556
- return new Uint8Array(Buffer.from(b64, "base64"));
64721
+ function fromBase64(b642) {
64722
+ return new Uint8Array(Buffer.from(b642, "base64"));
64557
64723
  }
64558
64724
  function toBlob(img) {
64559
64725
  return new Blob([img.bytes], { type: img.mime });
@@ -64694,7 +64860,7 @@ var OpenAiImageProvider = class {
64694
64860
  if (!res.ok) {
64695
64861
  throw new ImageEditError(json2.error?.message ?? `OpenAI request failed (${res.status})`, this.id, res.status);
64696
64862
  }
64697
- const out = (json2.data ?? []).map((d) => d.b64_json).filter((b) => !!b).map((b64) => ({ bytes: fromBase64(b64), mime: "image/png" }));
64863
+ const out = (json2.data ?? []).map((d) => d.b64_json).filter((b) => !!b).map((b642) => ({ bytes: fromBase64(b642), mime: "image/png" }));
64698
64864
  if (!out.length)
64699
64865
  throw new ImageEditError("OpenAI returned no image", this.id, res.status);
64700
64866
  return out;
@@ -64723,10 +64889,10 @@ var OpenAiImageProvider = class {
64723
64889
  if (!res.ok) {
64724
64890
  throw new ImageEditError(json2.error?.message ?? `OpenAI request failed (${res.status})`, this.id, res.status);
64725
64891
  }
64726
- const b64 = json2.data?.[0]?.b64_json;
64727
- if (!b64)
64892
+ const b642 = json2.data?.[0]?.b64_json;
64893
+ if (!b642)
64728
64894
  throw new ImageEditError("OpenAI returned no image", this.id, res.status);
64729
- return { bytes: fromBase64(b64), mime: "image/png" };
64895
+ return { bytes: fromBase64(b642), mime: "image/png" };
64730
64896
  }
64731
64897
  };
64732
64898
 
@@ -64802,7 +64968,7 @@ function createImageEditProvider(id, cfg2) {
64802
64968
  // ../../packages/host-core/dist/image-gen-runner.js
64803
64969
  function generatedImagesDir() {
64804
64970
  const home = tesseractHome();
64805
- return join22(home, "generated-images");
64971
+ return join23(home, "generated-images");
64806
64972
  }
64807
64973
  function creds() {
64808
64974
  return readDaemonAgentSettings().imageGen;
@@ -64854,11 +65020,11 @@ function createDaemonImageGenRunner() {
64854
65020
  });
64855
65021
  if (!images.length)
64856
65022
  return { ok: false, error: "The provider returned no image." };
64857
- const dir2 = req.outDir ?? join22(generatedImagesDir(), randomUUID10());
65023
+ const dir2 = req.outDir ?? join23(generatedImagesDir(), randomUUID11());
64858
65024
  mkdirSync9(dir2, { recursive: true });
64859
65025
  const files = images.map((img, i) => {
64860
65026
  const ext = img.mime.split("/")[1]?.split("+")[0] || "png";
64861
- const path6 = join22(dir2, `${i + 1}.${ext}`);
65027
+ const path6 = join23(dir2, `${i + 1}.${ext}`);
64862
65028
  writeFileSync10(path6, img.bytes);
64863
65029
  return { path: path6, mime: img.mime };
64864
65030
  });
@@ -64908,8 +65074,8 @@ var VideoGenError = class extends Error {
64908
65074
  function toBase642(bytes2) {
64909
65075
  return Buffer.from(bytes2).toString("base64");
64910
65076
  }
64911
- function fromBase642(b64) {
64912
- return new Uint8Array(Buffer.from(b64, "base64"));
65077
+ function fromBase642(b642) {
65078
+ return new Uint8Array(Buffer.from(b642, "base64"));
64913
65079
  }
64914
65080
 
64915
65081
  // ../../packages/video-core/dist/gemini.js
@@ -66169,16 +66335,17 @@ async function listVoicesElevenLabs(cfg2, opts) {
66169
66335
  }
66170
66336
 
66171
66337
  // ../../packages/video-core/dist/heygen.js
66172
- var BASE_URL2 = "https://api.heygen.com";
66338
+ var HEYGEN_DIRECT_BASE_URL = "https://api.heygen.com";
66173
66339
  var DEFAULT_DIM = { width: 1280, height: 720 };
66174
66340
  var HG_REQUEST_TIMEOUT_MS = 6e4;
66175
66341
  var HG_DOWNLOAD_TIMEOUT_MS = 18e4;
66176
- async function hgFetch(path6, apiKey, init, signal) {
66342
+ async function hgFetch(path6, cfg2, init, signal) {
66343
+ const base = (cfg2.baseUrl || HEYGEN_DIRECT_BASE_URL).replace(/\/+$/, "");
66177
66344
  let res;
66178
66345
  try {
66179
- res = await fetch(`${BASE_URL2}${path6}`, {
66346
+ res = await fetch(`${base}${path6}`, {
66180
66347
  method: init.method,
66181
- headers: { "X-Api-Key": apiKey, ...init.body != null ? { "Content-Type": "application/json" } : {} },
66348
+ headers: { "X-Api-Key": cfg2.apiKey, ...init.body != null ? { "Content-Type": "application/json" } : {} },
66182
66349
  ...init.body != null ? { body: JSON.stringify(init.body) } : {},
66183
66350
  signal: signal ?? AbortSignal.timeout(HG_REQUEST_TIMEOUT_MS)
66184
66351
  });
@@ -66195,7 +66362,7 @@ async function hgFetch(path6, apiKey, init, signal) {
66195
66362
  return json2;
66196
66363
  }
66197
66364
  async function listAvatarsHeyGen(cfg2, signal) {
66198
- const j = await hgFetch("/v2/avatars", cfg2.apiKey, { method: "GET" }, signal);
66365
+ const j = await hgFetch("/v2/avatars", cfg2, { method: "GET" }, signal);
66199
66366
  return (j.data?.avatars ?? []).map((a) => ({ avatarId: a.avatar_id, name: a.avatar_name ?? a.avatar_id }));
66200
66367
  }
66201
66368
  async function submitAvatarVideoHeyGen(spec, cfg2, signal) {
@@ -66212,14 +66379,14 @@ async function submitAvatarVideoHeyGen(spec, cfg2, signal) {
66212
66379
  ],
66213
66380
  dimension: { width: spec.width ?? DEFAULT_DIM.width, height: spec.height ?? DEFAULT_DIM.height }
66214
66381
  };
66215
- const j = await hgFetch("/v2/video/generate", cfg2.apiKey, { method: "POST", body }, signal);
66382
+ const j = await hgFetch("/v2/video/generate", cfg2, { method: "POST", body }, signal);
66216
66383
  const id = j.data?.video_id;
66217
66384
  if (!id)
66218
66385
  throw new Error("HeyGen returned no video_id.");
66219
66386
  return id;
66220
66387
  }
66221
66388
  async function pollAvatarVideoHeyGen(videoId, cfg2, signal) {
66222
- const j = await hgFetch(`/v1/video_status.get?video_id=${encodeURIComponent(videoId)}`, cfg2.apiKey, { method: "GET" }, signal);
66389
+ const j = await hgFetch(`/v1/video_status.get?video_id=${encodeURIComponent(videoId)}`, cfg2, { method: "GET" }, signal);
66223
66390
  const d = j.data ?? {};
66224
66391
  const error51 = typeof d.error === "string" ? d.error : d.error?.message;
66225
66392
  return { status: d.status ?? "processing", videoUrl: d.video_url, error: error51 };
@@ -66270,11 +66437,11 @@ function createVideoProvider(id, cfg2) {
66270
66437
  init_base_paths();
66271
66438
  import { mkdirSync as mkdirSync10, readdirSync as readdirSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync11, rmSync as rmSync3 } from "node:fs";
66272
66439
  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";
66440
+ import { dirname as dirname10, join as join24 } from "node:path";
66441
+ import { randomUUID as randomUUID12 } from "node:crypto";
66275
66442
  function mediaJobsDir() {
66276
66443
  const home = tesseractHome();
66277
- return join23(home, "media-jobs");
66444
+ return join24(home, "media-jobs");
66278
66445
  }
66279
66446
  var TERMINAL = /* @__PURE__ */ new Set(["succeeded", "failed", "canceled"]);
66280
66447
  var POLL_MS2 = 1e4;
@@ -66291,8 +66458,8 @@ var VideoJobRegistry = class {
66291
66458
  /** Submit a spec and start tracking it. Returns the persisted record. */
66292
66459
  async submit(provider, spec, meta4) {
66293
66460
  const handle3 = await provider.submit(spec);
66294
- const id = randomUUID11();
66295
- const outDir = meta4.outDir ?? join23(mediaJobsDir(), id);
66461
+ const id = randomUUID12();
66462
+ const outDir = meta4.outDir ?? join24(mediaJobsDir(), id);
66296
66463
  const job = {
66297
66464
  id,
66298
66465
  sessionId: meta4.sessionId,
@@ -66305,7 +66472,7 @@ var VideoJobRegistry = class {
66305
66472
  resolution: spec.resolution ?? "720p",
66306
66473
  estimateUsd: meta4.estimateUsd,
66307
66474
  state: "running",
66308
- outPath: join23(outDir, "video.mp4"),
66475
+ outPath: join24(outDir, "video.mp4"),
66309
66476
  createdAt: Date.now(),
66310
66477
  updatedAt: Date.now()
66311
66478
  };
@@ -66319,7 +66486,7 @@ var VideoJobRegistry = class {
66319
66486
  if (mem)
66320
66487
  return { ...mem };
66321
66488
  try {
66322
- const raw = readFileSync14(join23(mediaJobsDir(), `${jobId}.json`), "utf-8");
66489
+ const raw = readFileSync14(join24(mediaJobsDir(), `${jobId}.json`), "utf-8");
66323
66490
  return JSON.parse(raw);
66324
66491
  } catch {
66325
66492
  return null;
@@ -66369,7 +66536,7 @@ var VideoJobRegistry = class {
66369
66536
  }
66370
66537
  const now2 = Date.now();
66371
66538
  for (const f of files) {
66372
- const fp = join23(mediaJobsDir(), f);
66539
+ const fp = join24(mediaJobsDir(), f);
66373
66540
  try {
66374
66541
  const job = JSON.parse(readFileSync14(fp, "utf-8"));
66375
66542
  if (TERMINAL.has(job.state)) {
@@ -66492,7 +66659,7 @@ var VideoJobRegistry = class {
66492
66659
  this.jobs.set(job.id, job);
66493
66660
  try {
66494
66661
  mkdirSync10(mediaJobsDir(), { recursive: true });
66495
- writeFileSync11(join23(mediaJobsDir(), `${job.id}.json`), JSON.stringify(job, null, 2));
66662
+ writeFileSync11(join24(mediaJobsDir(), `${job.id}.json`), JSON.stringify(job, null, 2));
66496
66663
  } catch {
66497
66664
  }
66498
66665
  }
@@ -66646,15 +66813,15 @@ function createDaemonVideoGenRunner() {
66646
66813
  init_base_paths();
66647
66814
  import { existsSync as existsSync13, readFileSync as readFileSync20 } from "node:fs";
66648
66815
  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";
66816
+ import { join as join28, basename as basename4 } from "node:path";
66817
+ import { randomUUID as randomUUID14 } from "node:crypto";
66651
66818
 
66652
66819
  // ../../packages/host-core/dist/video-edit.js
66653
66820
  init_base_paths();
66654
66821
  import { spawn as spawn7 } from "node:child_process";
66655
66822
  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";
66823
+ import { join as join25, isAbsolute as isAbsolute4 } from "node:path";
66824
+ import { randomUUID as randomUUID13 } from "node:crypto";
66658
66825
 
66659
66826
  // ../../packages/host-core/dist/ffmpeg.js
66660
66827
  import { existsSync as existsSync11 } from "node:fs";
@@ -66718,9 +66885,9 @@ function requireAbsolutePath(p, label = "path") {
66718
66885
  const raw = typeof p === "string" ? p.trim() : "";
66719
66886
  if (!raw)
66720
66887
  throw new Error(`${label} is required.`);
66721
- const expanded = raw === "~" ? userHome() : raw.startsWith("~/") || raw.startsWith("~\\") ? join24(userHome(), raw.slice(2)) : raw;
66888
+ const expanded = raw === "~" ? userHome() : raw.startsWith("~/") || raw.startsWith("~\\") ? join25(userHome(), raw.slice(2)) : raw;
66722
66889
  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")}.`);
66890
+ 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
66891
  }
66725
66892
  return expanded;
66726
66893
  }
@@ -66734,7 +66901,7 @@ function mediaSpawnCwd() {
66734
66901
  }
66735
66902
  var workDir = () => {
66736
66903
  const home = tesseractHome();
66737
- const dir2 = join24(home, "media-work", randomUUID12());
66904
+ const dir2 = join25(home, "media-work", randomUUID13());
66738
66905
  mkdirSync11(dir2, { recursive: true });
66739
66906
  return dir2;
66740
66907
  };
@@ -66826,10 +66993,10 @@ async function renderEdl(edl, opts) {
66826
66993
  const dir2 = opts.outDir ?? workDir();
66827
66994
  mkdirSync11(dir2, { recursive: true });
66828
66995
  const format = edl.output?.format ?? "mp4";
66829
- const outPath = join24(dir2, `edit.${format}`);
66996
+ const outPath = join25(dir2, `edit.${format}`);
66830
66997
  let assFile;
66831
66998
  if (edl.captions?.cues.length) {
66832
- assFile = join24(dir2, "captions.ass");
66999
+ assFile = join25(dir2, "captions.ass");
66833
67000
  const W = edl.output?.width ?? 1920;
66834
67001
  const H = edl.output?.height ?? 1080;
66835
67002
  writeFileSync12(assFile, buildAssSubtitles(edl.captions.cues, edl.captions.style ?? "tiktok-bold", W, H));
@@ -66899,7 +67066,7 @@ async function enhanceVideo(path6, spec, opts) {
66899
67066
  }
66900
67067
  const dir2 = opts?.outDir ?? workDir();
66901
67068
  mkdirSync11(dir2, { recursive: true });
66902
- const outPath = join24(dir2, "enhanced.mp4");
67069
+ const outPath = join25(dir2, "enhanced.mp4");
66903
67070
  const audioArgs = probe.hasAudio ? ["-c:a", "copy"] : ["-an"];
66904
67071
  const args = [
66905
67072
  "-i",
@@ -66979,7 +67146,7 @@ async function extractFrames(path6, timestamps, outDir) {
66979
67146
  mkdirSync11(dir2, { recursive: true });
66980
67147
  const out = [];
66981
67148
  for (const [i, ts] of timestamps.slice(0, 20).entries()) {
66982
- const file2 = join24(dir2, `frame-${i + 1}-${Math.round(ts * 1e3)}ms.jpg`);
67149
+ const file2 = join25(dir2, `frame-${i + 1}-${Math.round(ts * 1e3)}ms.jpg`);
66983
67150
  const r = await run(resolveFfmpeg(), ["-y", "-ss", String(Math.max(0, ts)), "-i", path6, "-frames:v", "1", "-q:v", "2", file2]);
66984
67151
  if (!r.ok)
66985
67152
  throw new Error(`Frame extraction at ${ts}s failed: ${r.stderr.slice(-300)}`);
@@ -67012,7 +67179,7 @@ async function extractAudio(path6, format, outDir, opts) {
67012
67179
  path6 = requireAbsolutePath(path6, "path");
67013
67180
  const dir2 = outDir ?? workDir();
67014
67181
  mkdirSync11(dir2, { recursive: true });
67015
- const out = join24(dir2, `audio.${format}`);
67182
+ const out = join25(dir2, `audio.${format}`);
67016
67183
  const codec2 = format === "mp3" ? ["-c:a", "libmp3lame", "-q:a", "2"] : format === "wav" ? ["-c:a", "pcm_s16le"] : ["-c:a", "aac", "-b:a", "192k"];
67017
67184
  const gain = opts?.gainDb ? ["-af", `volume=${opts.gainDb}dB`] : [];
67018
67185
  const r = await run(resolveFfmpeg(), ["-y", "-i", path6, "-vn", ...gain, ...codec2, out], { timeoutMs: LONG_TIMEOUT_MS });
@@ -67028,7 +67195,7 @@ async function isolateSpeakerAudio(path6, windows2, outDir) {
67028
67195
  const enable = merged.map((w) => `between(t,${w.startSec},${w.endSec})`).join("+");
67029
67196
  const dir2 = outDir ?? workDir();
67030
67197
  mkdirSync11(dir2, { recursive: true });
67031
- const out = join24(dir2, "speaker.m4a");
67198
+ const out = join25(dir2, "speaker.m4a");
67032
67199
  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
67200
  timeoutMs: LONG_TIMEOUT_MS
67034
67201
  });
@@ -67059,7 +67226,7 @@ async function extractAudioChunks(path6, opts) {
67059
67226
  const chunks = [];
67060
67227
  try {
67061
67228
  for (let start3 = 0, i = 0; start3 < total; start3 += chunkSec, i++) {
67062
- const out = join24(chunkDir, `chunk-${i}.m4a`);
67229
+ const out = join25(chunkDir, `chunk-${i}.m4a`);
67063
67230
  const len = Math.min(chunkSec + overlapSec, total - start3);
67064
67231
  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
67232
  timeoutMs: LONG_TIMEOUT_MS
@@ -67088,7 +67255,7 @@ function readChunkBase64(path6) {
67088
67255
  init_base_paths();
67089
67256
  import { spawn as spawn8 } from "node:child_process";
67090
67257
  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";
67258
+ import { join as join26, dirname as dirname11 } from "node:path";
67092
67259
  var upscaleCwd;
67093
67260
  function upscaleSpawnCwd() {
67094
67261
  if (!upscaleCwd) {
@@ -67104,12 +67271,12 @@ function resolveRealesrgan() {
67104
67271
  const candidates = [
67105
67272
  envBin,
67106
67273
  // Packaged app: extraResources → Resources/vendor/realesrgan/<plat>/…
67107
- process.resourcesPath ? join25(process.resourcesPath, "vendor", "realesrgan", platDir, exe) : void 0
67274
+ process.resourcesPath ? join26(process.resourcesPath, "vendor", "realesrgan", platDir, exe) : void 0
67108
67275
  ].filter((p) => !!p && existsSync12(p));
67109
67276
  const bin = candidates[0] ?? fromPath2("realesrgan-ncnn-vulkan");
67110
67277
  if (!bin)
67111
67278
  return null;
67112
- const models = process.env.TESSERACT_REALESRGAN_MODELS?.trim() || join25(dirname11(bin), "models");
67279
+ const models = process.env.TESSERACT_REALESRGAN_MODELS?.trim() || join26(dirname11(bin), "models");
67113
67280
  return existsSync12(models) ? { bin, models } : null;
67114
67281
  }
67115
67282
  function fromPath2(cmd) {
@@ -67132,8 +67299,8 @@ function installedRealesrModels() {
67132
67299
  return [];
67133
67300
  return REALESRGAN_MODELS.filter((m) => {
67134
67301
  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`));
67302
+ return m.scales.some((s) => existsSync12(join26(re.models, `${m.id}-x${s}.bin`)));
67303
+ return existsSync12(join26(re.models, `${m.id}.bin`));
67137
67304
  }).map((m) => m.id);
67138
67305
  }
67139
67306
  function upscaleAdvert() {
@@ -67152,7 +67319,7 @@ async function realesrganGpuWorks() {
67152
67319
  }
67153
67320
  try {
67154
67321
  const dir2 = workDir();
67155
- const inP = join25(dir2, "p.png"), outP = join25(dir2, "p2.png");
67322
+ const inP = join26(dir2, "p.png"), outP = join26(dir2, "p2.png");
67156
67323
  const gen = await run(resolveFfmpeg(), ["-y", "-f", "lavfi", "-i", "testsrc=size=64x64:duration=1", "-frames:v", "1", inP], { timeoutMs: 15e3 });
67157
67324
  if (!gen.ok || !existsSync12(inP)) {
67158
67325
  gpuProbe = false;
@@ -67191,7 +67358,7 @@ async function upscaleCapabilities(opts) {
67191
67358
  }
67192
67359
  function resolveFalKey() {
67193
67360
  const home = tesseractHome();
67194
- const file2 = join25(home, "daemon", "fal.key");
67361
+ const file2 = join26(home, "daemon", "fal.key");
67195
67362
  if (existsSync12(file2)) {
67196
67363
  const k = readFileSync17(file2, "utf-8").trim();
67197
67364
  if (k)
@@ -67239,19 +67406,19 @@ async function realesrganUpscale(path6, opts) {
67239
67406
  const model = opts.model ?? "realesr-animevideov3";
67240
67407
  const scale = opts.scale ?? 2;
67241
67408
  const dir2 = opts.outDir ?? workDir();
67242
- const fin = join25(dir2, "in"), fup = join25(dir2, "up");
67409
+ const fin = join26(dir2, "in"), fup = join26(dir2, "up");
67243
67410
  mkdirSync12(fin, { recursive: true });
67244
67411
  mkdirSync12(fup, { recursive: true });
67245
67412
  const fps = p.fps ?? 30;
67246
67413
  const src = path6;
67247
67414
  let extractSrc = src;
67248
67415
  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);
67416
+ const cleaned = await enhanceVideo(src, { ...opts.restore, sharpen: opts.restore.sharpen ?? "none" }, { outDir: join26(dir2, "restore") }).catch(() => null);
67250
67417
  if (cleaned)
67251
67418
  extractSrc = cleaned.path;
67252
67419
  }
67253
67420
  opts.onProgress?.(2, "Extracting frames\u2026");
67254
- const ext = await run(resolveFfmpeg(), ["-i", extractSrc, "-fps_mode", "passthrough", join25(fin, "f%08d.png")], { timeoutMs: LONG });
67421
+ const ext = await run(resolveFfmpeg(), ["-i", extractSrc, "-fps_mode", "passthrough", join26(fin, "f%08d.png")], { timeoutMs: LONG });
67255
67422
  if (!ext.ok)
67256
67423
  throw new Error(`Frame extraction failed: ${ext.stderr.slice(-300)}`);
67257
67424
  const nFrames = readdirSync5(fin).filter((f) => f.endsWith(".png")).length;
@@ -67277,14 +67444,14 @@ async function realesrganUpscale(path6, opts) {
67277
67444
  });
67278
67445
  });
67279
67446
  opts.onProgress?.(96, "Reassembling\u2026");
67280
- const outPath = join25(dir2, "enhanced.mp4");
67447
+ const outPath = join26(dir2, "enhanced.mp4");
67281
67448
  const vf = opts.targetHeight ? `scale=w=-2:h=${Math.round(opts.targetHeight / 2) * 2}:flags=lanczos` : "null";
67282
67449
  const audio = p.hasAudio ? ["-i", src, "-map", "0:v:0", "-map", "1:a:0?", "-c:a", "copy"] : [];
67283
67450
  const asm = await run(resolveFfmpeg(), [
67284
67451
  "-r",
67285
67452
  String(fps),
67286
67453
  "-i",
67287
- join25(fup, "f%08d.png"),
67454
+ join26(fup, "f%08d.png"),
67288
67455
  ...audio,
67289
67456
  "-vf",
67290
67457
  vf,
@@ -67336,7 +67503,7 @@ async function falTopazEnhance(path6, spec, opts) {
67336
67503
  throw new Error(`Downloading the fal result failed (HTTP ${dl.status}).`);
67337
67504
  const dir2 = opts.outDir ?? workDir();
67338
67505
  mkdirSync12(dir2, { recursive: true });
67339
- const outPath = join25(dir2, "enhanced.mp4");
67506
+ const outPath = join26(dir2, "enhanced.mp4");
67340
67507
  const { writeFileSync: writeFileSync29 } = await import("node:fs");
67341
67508
  writeFileSync29(outPath, Buffer.from(await dl.arrayBuffer()));
67342
67509
  const out = await probeMedia(outPath).catch(() => null);
@@ -67382,7 +67549,7 @@ async function smartEnhance(path6, args) {
67382
67549
 
67383
67550
  // ../../packages/host-core/dist/video-effects.js
67384
67551
  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";
67552
+ import { join as join27, basename as basename3, extname } from "node:path";
67386
67553
  var IMG_MIME = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", heic: "image/heic" };
67387
67554
  var LONG2 = 6e5;
67388
67555
  function listEffects() {
@@ -67469,7 +67636,7 @@ async function applyEffect(effectId, inputs, opts) {
67469
67636
  const dir2 = opts?.outDir ?? workDir();
67470
67637
  mkdirSync13(dir2, { recursive: true });
67471
67638
  const ext = media.isVideo ? "mp4" : extname(new URL(media.url).pathname).slice(1) || "png";
67472
- const outPath = join26(dir2, `${effectId}.${ext}`);
67639
+ const outPath = join27(dir2, `${effectId}.${ext}`);
67473
67640
  writeFileSync13(outPath, Buffer.from(await dl.arrayBuffer()));
67474
67641
  opts?.onProgress?.(100, "Done");
67475
67642
  return { path: outPath, mime: media.isVideo ? "video/mp4" : IMG_MIME[ext] ?? "image/png", isVideo: media.isVideo, effectId, label: def.label };
@@ -67495,7 +67662,7 @@ ${inputs.prompt}` : "");
67495
67662
  const out = await runImageEdit(provider, { op: "prompt", imageB64: readFileSync18(imgPath).toString("base64"), mime: IMG_MIME[ext] ?? "image/jpeg", prompt });
67496
67663
  const dir2 = opts?.outDir ?? workDir();
67497
67664
  mkdirSync13(dir2, { recursive: true });
67498
- const outPath = join26(dir2, `${def.id}.png`);
67665
+ const outPath = join27(dir2, `${def.id}.png`);
67499
67666
  writeFileSync13(outPath, Buffer.from(out.bytes));
67500
67667
  opts?.onProgress?.(100, "Done");
67501
67668
  return { path: outPath, mime: out.mime || "image/png", isVideo: false, effectId: def.id, label: def.label };
@@ -67503,13 +67670,13 @@ ${inputs.prompt}` : "");
67503
67670
 
67504
67671
  // ../../packages/host-core/dist/video-transcribe.js
67505
67672
  var DEFAULT_MODEL5 = "gemini-3.5-flash";
67506
- var BASE_URL3 = "https://generativelanguage.googleapis.com/v1beta";
67673
+ var BASE_URL2 = "https://generativelanguage.googleapis.com/v1beta";
67507
67674
  var CHUNK_PROMPT = `Transcribe this audio VERBATIM with speaker diarization.
67508
67675
  Return ONLY a JSON array; each element: {"start": <seconds from the START OF THIS AUDIO, number>, "end": <seconds, number>, "speaker": "<S1|S2|S3\u2026, stable within this audio>", "text": "<utterance>"}.
67509
67676
  Rules: keep the original language(s); split on speaker changes and natural sentence boundaries (segments \u2264 15s); timecodes must be accurate to the second; no commentary, no markdown.`;
67510
67677
  var REQUEST_TIMEOUT_MS3 = 18e4;
67511
67678
  async function geminiJson(opts) {
67512
- const res = await fetch(`${BASE_URL3}/models/${encodeURIComponent(opts.model)}:generateContent`, {
67679
+ const res = await fetch(`${BASE_URL2}/models/${encodeURIComponent(opts.model)}:generateContent`, {
67513
67680
  method: "POST",
67514
67681
  headers: { "Content-Type": "application/json", "x-goog-api-key": opts.apiKey },
67515
67682
  body: JSON.stringify({
@@ -67615,18 +67782,18 @@ ${compact2}`;
67615
67782
 
67616
67783
  // ../../packages/host-core/dist/video-vision.js
67617
67784
  import { readFileSync as readFileSync19 } from "node:fs";
67618
- var BASE_URL4 = "https://generativelanguage.googleapis.com/v1beta";
67785
+ var BASE_URL3 = "https://generativelanguage.googleapis.com/v1beta";
67619
67786
  var MODEL = "gemini-3.5-flash";
67620
67787
  var TIMEOUT_MS = 6e4;
67621
67788
  async function reviewFrameGemini(framePath, intent, apiKey) {
67622
67789
  try {
67623
- const b64 = readFileSync19(framePath).toString("base64");
67790
+ const b642 = readFileSync19(framePath).toString("base64");
67624
67791
  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
- const res = await fetch(`${BASE_URL4}/models/${MODEL}:generateContent`, {
67792
+ const res = await fetch(`${BASE_URL3}/models/${MODEL}:generateContent`, {
67626
67793
  method: "POST",
67627
67794
  headers: { "Content-Type": "application/json", "x-goog-api-key": apiKey },
67628
67795
  body: JSON.stringify({
67629
- contents: [{ role: "user", parts: [{ text: prompt }, { inlineData: { mimeType: "image/jpeg", data: b64 } }] }],
67796
+ contents: [{ role: "user", parts: [{ text: prompt }, { inlineData: { mimeType: "image/jpeg", data: b642 } }] }],
67630
67797
  generationConfig: { temperature: 0, responseMimeType: "application/json" }
67631
67798
  }),
67632
67799
  signal: AbortSignal.timeout(TIMEOUT_MS)
@@ -67650,13 +67817,23 @@ function googleKey() {
67650
67817
  function elevenLabsKey() {
67651
67818
  return readDaemonAgentSettings().voiceKeys?.elevenlabs?.trim() || null;
67652
67819
  }
67653
- function heygenKey() {
67654
- return readDaemonAgentSettings().heygenApiKey?.trim() || readDaemonKeyFile("heygen.key") || process.env.HEYGEN_API_KEY?.trim() || null;
67820
+ function resolveAvatarCreds(snap, keyFile, envKey2) {
67821
+ const fromSnapshot = (snap.heygen?.baseUrl ? void 0 : snap.heygen?.apiKey?.trim()) || snap.heygenApiKey?.trim();
67822
+ const personal = fromSnapshot || keyFile?.trim() || envKey2?.trim();
67823
+ if (personal)
67824
+ return { apiKey: personal };
67825
+ if (snap.heygen?.baseUrl && snap.heygen.apiKey)
67826
+ return { apiKey: snap.heygen.apiKey, baseUrl: snap.heygen.baseUrl };
67827
+ return null;
67828
+ }
67829
+ function heygenCreds() {
67830
+ return resolveAvatarCreds(readDaemonAgentSettings(), readDaemonKeyFile("heygen.key"), process.env.HEYGEN_API_KEY ?? null);
67655
67831
  }
67832
+ var NO_AVATAR_CREDS = "Avatar videos need either a HeyGen API key (add it in Settings, or set HEYGEN_API_KEY) or a signed-in Tesseract account for the free tier.";
67656
67833
  function readDaemonKeyFile(name) {
67657
67834
  try {
67658
67835
  const home = tesseractHome();
67659
- const f = join27(home, "daemon", name);
67836
+ const f = join28(home, "daemon", name);
67660
67837
  if (existsSync13(f)) {
67661
67838
  const k = readFileSync20(f, "utf-8").trim();
67662
67839
  if (k)
@@ -67670,7 +67847,7 @@ var HEYGEN_POLL_MS = 8e3;
67670
67847
  var HEYGEN_MAX_MS = 8 * 6e4;
67671
67848
  function musicDir() {
67672
67849
  const home = tesseractHome();
67673
- return join27(home, "generated-music");
67850
+ return join28(home, "generated-music");
67674
67851
  }
67675
67852
  function audioMimeForPath(p) {
67676
67853
  const lower = p.toLowerCase();
@@ -67786,10 +67963,10 @@ function createDaemonVideoEditRunner() {
67786
67963
  return { ok: false, error: "Music generation needs an ElevenLabs key \u2014 ask the user to add one in Settings \u2192 Voice." };
67787
67964
  try {
67788
67965
  const music = await generateMusicElevenLabs({ prompt, durationSec }, { apiKey });
67789
- const dir2 = outDir ?? join27(musicDir(), randomUUID13());
67966
+ const dir2 = outDir ?? join28(musicDir(), randomUUID14());
67790
67967
  await mkdir6(dir2, { recursive: true });
67791
67968
  const ext = music.mime.includes("wav") ? "wav" : music.mime.includes("mp4") ? "m4a" : "mp3";
67792
- const path6 = join27(dir2, `music.${ext}`);
67969
+ const path6 = join28(dir2, `music.${ext}`);
67793
67970
  await writeFile7(path6, music.bytes);
67794
67971
  return { ok: true, file: { path: path6, mime: music.mime } };
67795
67972
  } catch (e) {
@@ -67825,21 +68002,21 @@ function createDaemonVideoEditRunner() {
67825
68002
  }
67826
68003
  },
67827
68004
  async listAvatars() {
67828
- const apiKey = heygenKey();
67829
- if (!apiKey)
67830
- return { ok: false, error: "Avatar videos need a HeyGen API key (set HEYGEN_API_KEY or add it in settings)." };
68005
+ const creds3 = heygenCreds();
68006
+ if (!creds3)
68007
+ return { ok: false, error: NO_AVATAR_CREDS };
67831
68008
  try {
67832
- return { ok: true, avatars: await listAvatarsHeyGen({ apiKey }) };
68009
+ return { ok: true, avatars: await listAvatarsHeyGen(creds3) };
67833
68010
  } catch (e) {
67834
68011
  return { ok: false, error: err(e) };
67835
68012
  }
67836
68013
  },
67837
68014
  async generateAvatarVideo(req, outDir, onProgress) {
67838
- const apiKey = heygenKey();
67839
- if (!apiKey)
67840
- return { ok: false, error: "Avatar videos need a HeyGen API key (set HEYGEN_API_KEY or add it in settings)." };
68015
+ const creds3 = heygenCreds();
68016
+ if (!creds3)
68017
+ return { ok: false, error: NO_AVATAR_CREDS };
67841
68018
  try {
67842
- const videoId = await submitAvatarVideoHeyGen({ avatarId: req.avatarId, talkingPhotoId: req.talkingPhotoId, text: req.text, voiceId: req.voiceId, width: req.width, height: req.height, background: req.background }, { apiKey });
68019
+ const videoId = await submitAvatarVideoHeyGen({ avatarId: req.avatarId, talkingPhotoId: req.talkingPhotoId, text: req.text, voiceId: req.voiceId, width: req.width, height: req.height, background: req.background }, creds3);
67843
68020
  onProgress?.("Avatar video submitted \u2014 rendering\u2026");
67844
68021
  const started = Date.now();
67845
68022
  for (; ; ) {
@@ -67848,7 +68025,7 @@ function createDaemonVideoEditRunner() {
67848
68025
  await new Promise((r) => setTimeout(r, HEYGEN_POLL_MS));
67849
68026
  let poll;
67850
68027
  try {
67851
- poll = await pollAvatarVideoHeyGen(videoId, { apiKey });
68028
+ poll = await pollAvatarVideoHeyGen(videoId, creds3);
67852
68029
  } catch {
67853
68030
  onProgress?.(`Rendering avatar video\u2026 (${Math.round((Date.now() - started) / 1e3)}s)`);
67854
68031
  continue;
@@ -67857,9 +68034,9 @@ function createDaemonVideoEditRunner() {
67857
68034
  if (!poll.videoUrl)
67858
68035
  return { ok: false, error: "HeyGen reported the render complete but returned no video URL." };
67859
68036
  const dl = await downloadHeyGenVideo(poll.videoUrl);
67860
- const dir2 = outDir ?? join27(musicDir(), randomUUID13());
68037
+ const dir2 = outDir ?? join28(musicDir(), randomUUID14());
67861
68038
  await mkdir6(dir2, { recursive: true });
67862
- const path6 = join27(dir2, "avatar.mp4");
68039
+ const path6 = join28(dir2, "avatar.mp4");
67863
68040
  await writeFile7(path6, dl.bytes);
67864
68041
  return { ok: true, file: { path: path6, mime: dl.mime } };
67865
68042
  }
@@ -67877,9 +68054,9 @@ function createDaemonVideoEditRunner() {
67877
68054
  return { ok: false, error: "Text-to-speech needs an ElevenLabs key (Settings \u2192 Voice)." };
67878
68055
  try {
67879
68056
  const audio = await synthesizeSpeechElevenLabs(text8, voiceId, { apiKey, stability: opts?.stability, similarityBoost: opts?.similarityBoost, speed: opts?.speed });
67880
- const dir2 = outDir ?? join27(musicDir(), randomUUID13());
68057
+ const dir2 = outDir ?? join28(musicDir(), randomUUID14());
67881
68058
  await mkdir6(dir2, { recursive: true });
67882
- const path6 = join27(dir2, "speech.mp3");
68059
+ const path6 = join28(dir2, "speech.mp3");
67883
68060
  await writeFile7(path6, audio.bytes);
67884
68061
  return { ok: true, file: { path: path6, mime: audio.mime } };
67885
68062
  } catch (e) {
@@ -67913,7 +68090,7 @@ function createDaemonVideoEditRunner() {
67913
68090
 
67914
68091
  // ../../packages/host-core/dist/support-submit.js
67915
68092
  import { openSync as openSync3, fstatSync, readSync as readSync2, closeSync as closeSync3 } from "node:fs";
67916
- import { join as join28 } from "node:path";
68093
+ import { join as join29 } from "node:path";
67917
68094
 
67918
68095
  // ../../packages/host-core/dist/host-metrics.js
67919
68096
  import os from "node:os";
@@ -67976,7 +68153,7 @@ function createDaemonSupportSubmit() {
67976
68153
  if (!creds3) {
67977
68154
  throw new Error("Not signed in on this machine \u2014 sign in (or share this machine) to file a report from chat.");
67978
68155
  }
67979
- const daemonLog = tail2(join28(daemonDir2(), "daemon.log"));
68156
+ const daemonLog = tail2(join29(daemonDir2(), "daemon.log"));
67980
68157
  const diagnostics = {
67981
68158
  collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
67982
68159
  source: "daemon",
@@ -68018,12 +68195,12 @@ function createDaemonSupportSubmit() {
68018
68195
  init_dist4();
68019
68196
 
68020
68197
  // ../../packages/host-core/dist/app-control-bridge.js
68021
- import { randomUUID as randomUUID14 } from "node:crypto";
68198
+ import { randomUUID as randomUUID15 } from "node:crypto";
68022
68199
  var DEFAULT_APP_REQUEST_TIMEOUT_MS = 2e4;
68023
68200
  var pending = /* @__PURE__ */ new Map();
68024
68201
  function requestDesktopApp(emit2, origin, capabilityId, input, timeoutMs = DEFAULT_APP_REQUEST_TIMEOUT_MS, sessionId) {
68025
68202
  return new Promise((resolve12, reject) => {
68026
- const requestId = `app:${randomUUID14()}`;
68203
+ const requestId = `app:${randomUUID15()}`;
68027
68204
  const timer = setTimeout(() => {
68028
68205
  pending.delete(requestId);
68029
68206
  reject(new Error("App request timed out \u2014 the Tesseract desktop app did not respond."));
@@ -68078,7 +68255,7 @@ var flushTimer = null;
68078
68255
  var keepalive = null;
68079
68256
  var gateTimer = null;
68080
68257
  var stageGraceTimer = null;
68081
- var log3 = () => {
68258
+ var log4 = () => {
68082
68259
  };
68083
68260
  function snapshot() {
68084
68261
  if (!state)
@@ -68179,7 +68356,7 @@ function attach(sub, hello) {
68179
68356
  if (!accepted)
68180
68357
  return;
68181
68358
  subs.set(sub.deviceId, s);
68182
- log3(`[present] attach ${sub.role} ${sub.deviceId} (${sub.name})`);
68359
+ log4(`[present] attach ${sub.role} ${sub.deviceId} (${sub.name})`);
68183
68360
  if (sub.role === "stage" && stageGraceTimer) {
68184
68361
  clearTimeout(stageGraceTimer);
68185
68362
  stageGraceTimer = null;
@@ -68199,7 +68376,7 @@ function detach(deviceId2) {
68199
68376
  if (!s)
68200
68377
  return;
68201
68378
  subs.delete(deviceId2);
68202
- log3(`[present] detach ${s.role} ${deviceId2}`);
68379
+ log4(`[present] detach ${s.role} ${deviceId2}`);
68203
68380
  if (s.role === "stage" && state) {
68204
68381
  state.phase = "idle";
68205
68382
  if (!stageGraceTimer) {
@@ -68263,7 +68440,7 @@ function start(msg, actorId) {
68263
68440
  ensureKeepalive();
68264
68441
  lastContextIndex = -1;
68265
68442
  scheduleContext();
68266
- log3(`[present] start "${msg.deckTitle}" epoch=${epoch} slides=${slides.length}`);
68443
+ log4(`[present] start "${msg.deckTitle}" epoch=${epoch} slides=${slides.length}`);
68267
68444
  for (const s of subs.values())
68268
68445
  sendSlides(s);
68269
68446
  bump();
@@ -68272,7 +68449,7 @@ function end(reason = "ended") {
68272
68449
  if (!presentId)
68273
68450
  return;
68274
68451
  const id = presentId;
68275
- log3(`[present] end (${reason})`);
68452
+ log4(`[present] end (${reason})`);
68276
68453
  for (const s of subs.values()) {
68277
68454
  try {
68278
68455
  s.send({ type: "present:closed", presentId: id, reason });
@@ -68311,7 +68488,7 @@ function gotoIndex(index, by = "remote") {
68311
68488
  state.build = 0;
68312
68489
  scheduleContext();
68313
68490
  }
68314
- log3(`[present] goto ${next} by=${by}`);
68491
+ log4(`[present] goto ${next} by=${by}`);
68315
68492
  return bump();
68316
68493
  }
68317
68494
  function gotoSlideId(slideId, by = "remote") {
@@ -68367,7 +68544,7 @@ function setAiListening(listening, holdMs) {
68367
68544
  gateTimer = setTimeout(() => {
68368
68545
  gateTimer = null;
68369
68546
  if (state?.ai.listening) {
68370
- log3("[present] AI input gate expired (dead-man) \u2014 closing");
68547
+ log4("[present] AI input gate expired (dead-man) \u2014 closing");
68371
68548
  state.ai.listening = false;
68372
68549
  bump();
68373
68550
  }
@@ -68478,7 +68655,7 @@ function onClientFrame(deviceId2, msg, send3, meta4) {
68478
68655
  return;
68479
68656
  }
68480
68657
  if (!sub) {
68481
- log3(`[present] auto-attaching ${deviceId2} on ${msg.type} \u2014 no hello, protocol assumed v${PRESENT_PROTOCOL_VERSION}`);
68658
+ log4(`[present] auto-attaching ${deviceId2} on ${msg.type} \u2014 no hello, protocol assumed v${PRESENT_PROTOCOL_VERSION}`);
68482
68659
  attach({ deviceId: deviceId2, role: meta4?.role ?? "remote", name: meta4?.name ?? deviceId2, send: send3 }, {
68483
68660
  presentId: msg.presentId ?? "",
68484
68661
  protocolVersion: PRESENT_PROTOCOL_VERSION
@@ -68976,7 +69153,7 @@ function buildSharedBackend(engineId) {
68976
69153
  },
68977
69154
  resolver,
68978
69155
  // Daemon plugins (review #3): operator-controlled <daemonDir>/plugins, env-var secrets.
68979
- buildDaemonPluginLoader(join29(daemonDir2(), "plugins"))
69156
+ buildDaemonPluginLoader(join30(daemonDir2(), "plugins"))
68980
69157
  ).then((b) => {
68981
69158
  const sendMessage = b.sendMessage.bind(b);
68982
69159
  b.sendMessage = (params) => {
@@ -69083,7 +69260,7 @@ function refreshDaemonScheduling(b) {
69083
69260
  }
69084
69261
  b.configureScheduling({
69085
69262
  enabled,
69086
- file: join29(dir2, "scheduled_tasks.json"),
69263
+ file: join30(dir2, "scheduled_tasks.json"),
69087
69264
  // projectDir omitted ⇒ fires track the daemon's currently-open project (setConfig).
69088
69265
  isOwner: schedulingLock ? () => schedulingLock.isOwner() : void 0
69089
69266
  });
@@ -69102,14 +69279,14 @@ var VOICE_GATEWAY = process.env.CCB_VOICE_GATEWAY !== "0";
69102
69279
  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
69280
  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
69281
  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";
69282
+ 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
69283
  import { execFile as execFile3 } from "node:child_process";
69107
69284
  import { promisify as promisify2 } from "node:util";
69108
69285
 
69109
69286
  // ../../packages/host-core/dist/fs-stats.js
69110
69287
  init_dist();
69111
69288
  import { readdir as readdir2, lstat, readFile as readFile8 } from "node:fs/promises";
69112
- import { join as join30 } from "node:path";
69289
+ import { join as join31 } from "node:path";
69113
69290
  var TEXT_CAP = 5 * 1024 * 1024;
69114
69291
  async function dirStats(root, opts) {
69115
69292
  const start3 = Date.now();
@@ -69140,11 +69317,11 @@ async function dirStats(root, opts) {
69140
69317
  if (opts?.excludeHeavy && STATS_HEAVY_DIRS.has(e.name))
69141
69318
  continue;
69142
69319
  dirCount++;
69143
- stack.push(join30(dir2, e.name));
69320
+ stack.push(join31(dir2, e.name));
69144
69321
  } else if (e.isFile()) {
69145
69322
  fileCount++;
69146
69323
  try {
69147
- totalBytes += (await lstat(join30(dir2, e.name))).size;
69324
+ totalBytes += (await lstat(join31(dir2, e.name))).size;
69148
69325
  } catch {
69149
69326
  }
69150
69327
  }
@@ -69179,7 +69356,7 @@ async function codeStats(root) {
69179
69356
  continue;
69180
69357
  if (e.isDirectory()) {
69181
69358
  if (!STATS_HEAVY_DIRS.has(e.name))
69182
- stack.push(join30(dir2, e.name));
69359
+ stack.push(join31(dir2, e.name));
69183
69360
  continue;
69184
69361
  }
69185
69362
  if (!e.isFile())
@@ -69187,7 +69364,7 @@ async function codeStats(root) {
69187
69364
  const lang = langForFilename(e.name);
69188
69365
  if (!lang)
69189
69366
  continue;
69190
- const full = join30(dir2, e.name);
69367
+ const full = join31(dir2, e.name);
69191
69368
  let size = 0;
69192
69369
  try {
69193
69370
  size = (await lstat(full)).size;
@@ -69280,7 +69457,7 @@ async function listAllFilesUnder(dir2) {
69280
69457
  return;
69281
69458
  if (ent.name === ".DS_Store")
69282
69459
  continue;
69283
- const full = join31(d, ent.name);
69460
+ const full = join32(d, ent.name);
69284
69461
  if (ent.isDirectory()) {
69285
69462
  if (!LISTALL_SKIP_DIRS.has(ent.name))
69286
69463
  await walk(full);
@@ -69397,18 +69574,18 @@ function resolveParent(raw, root) {
69397
69574
  if (raw === "" || raw === "~")
69398
69575
  return root;
69399
69576
  if (raw.startsWith("~/"))
69400
- return join31(root, raw.slice(2));
69401
- return isAbsolute5(raw) ? raw : join31(root, raw);
69577
+ return join32(root, raw.slice(2));
69578
+ return isAbsolute5(raw) ? raw : join32(root, raw);
69402
69579
  }
69403
69580
  function resolveUniqueName(dir2, name) {
69404
- const direct = join31(dir2, name);
69581
+ const direct = join32(dir2, name);
69405
69582
  if (!existsSync14(direct))
69406
69583
  return direct;
69407
69584
  const dot = name.lastIndexOf(".");
69408
69585
  const base = dot > 0 ? name.slice(0, dot) : name;
69409
69586
  const ext = dot > 0 ? name.slice(dot) : "";
69410
69587
  for (let i = 2; i < 1e4; i++) {
69411
- const candidate = join31(dir2, `${base} ${i}${ext}`);
69588
+ const candidate = join32(dir2, `${base} ${i}${ext}`);
69412
69589
  if (!existsSync14(candidate))
69413
69590
  return candidate;
69414
69591
  }
@@ -69481,12 +69658,12 @@ async function renderThumbnailImpl(real, maxPx) {
69481
69658
  return { error: "Thumbnails are only available on macOS hosts." };
69482
69659
  let dir2 = null;
69483
69660
  try {
69484
- dir2 = mkdtempSync(join31(tmpdir3(), "fp-thumb-"));
69661
+ dir2 = mkdtempSync(join32(tmpdir3(), "fp-thumb-"));
69485
69662
  await execFileAsync("qlmanage", ["-t", "-s", String(maxPx), "-o", dir2, real], { timeout: 1e4, killSignal: "SIGKILL" }).catch(() => void 0);
69486
69663
  const produced = readdirSync6(dir2).find((f) => f.toLowerCase().endsWith(".png"));
69487
69664
  if (!produced)
69488
69665
  return { error: "No thumbnail available." };
69489
- return { png: readFileSync21(join31(dir2, produced)).toString("base64") };
69666
+ return { png: readFileSync21(join32(dir2, produced)).toString("base64") };
69490
69667
  } catch (err2) {
69491
69668
  return { error: String(err2.message) };
69492
69669
  } finally {
@@ -69526,7 +69703,7 @@ var MAX_STAT_PATHS = 4096;
69526
69703
  var SAFE_SEG = /^[A-Za-z0-9._-]+$/;
69527
69704
  var TRASH_RETENTION_MS = 14 * 24 * 60 * 60 * 1e3;
69528
69705
  function pruneTrash(root) {
69529
- const trashDir = join31(root, TRASH_DIR);
69706
+ const trashDir = join32(root, TRASH_DIR);
69530
69707
  if (!existsSync14(trashDir))
69531
69708
  return;
69532
69709
  const cutoff = Date.now() - TRASH_RETENTION_MS;
@@ -69535,9 +69712,9 @@ function pruneTrash(root) {
69535
69712
  if (!e.isDirectory())
69536
69713
  continue;
69537
69714
  try {
69538
- const meta4 = JSON.parse(readFileSync21(join31(trashDir, e.name, ".fp-meta.json"), "utf-8"));
69715
+ const meta4 = JSON.parse(readFileSync21(join32(trashDir, e.name, ".fp-meta.json"), "utf-8"));
69539
69716
  if ((meta4.deletedAt ?? 0) < cutoff)
69540
- rmSync5(join31(trashDir, e.name), { recursive: true, force: true });
69717
+ rmSync5(join32(trashDir, e.name), { recursive: true, force: true });
69541
69718
  } catch {
69542
69719
  }
69543
69720
  }
@@ -69563,12 +69740,12 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69563
69740
  return entries.map((e) => {
69564
69741
  let id = null;
69565
69742
  try {
69566
- id = statId(join31(real, e.name));
69743
+ id = statId(join32(real, e.name));
69567
69744
  } catch {
69568
69745
  }
69569
69746
  return {
69570
69747
  name: e.name,
69571
- path: join31(dir2, e.name),
69748
+ path: join32(dir2, e.name),
69572
69749
  isDir: e.isDirectory(),
69573
69750
  ignored: ignored.has(e.name),
69574
69751
  size: id?.size,
@@ -69735,7 +69912,7 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69735
69912
  const name = String(a[1] ?? "").trim();
69736
69913
  if (!validName(name))
69737
69914
  return { ok: false, error: "Invalid project name." };
69738
- const target = join31(parent, name);
69915
+ const target = join32(parent, name);
69739
69916
  if (!insideOk(target))
69740
69917
  return { ok: false, error: "Refusing to create a project outside the shared root." };
69741
69918
  if (existsSync14(target))
@@ -69753,7 +69930,7 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69753
69930
  const name = String(a[1] ?? "").trim();
69754
69931
  if (!validName(name))
69755
69932
  return { ok: false, error: "Invalid name." };
69756
- const target = join31(parent, name);
69933
+ const target = join32(parent, name);
69757
69934
  if (!insideOk(target))
69758
69935
  return OUTSIDE("create");
69759
69936
  if (existsSync14(target))
@@ -69775,7 +69952,7 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69775
69952
  return { ok: false, error: "Invalid name." };
69776
69953
  if (!insideOk(oldPath))
69777
69954
  return OUTSIDE("rename");
69778
- const newPath = join31(dirname12(oldPath), newName);
69955
+ const newPath = join32(dirname12(oldPath), newName);
69779
69956
  if (existsSync14(newPath))
69780
69957
  return { ok: false, error: `A file named "${newName}" already exists.` };
69781
69958
  try {
@@ -69809,19 +69986,19 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69809
69986
  try {
69810
69987
  pruneTrash(root);
69811
69988
  const id = `${Date.now().toString(36)}-${(trashSeq++).toString(36)}`;
69812
- const itemDir = join31(root, TRASH_DIR, id);
69989
+ const itemDir = join32(root, TRASH_DIR, id);
69813
69990
  await mkdir7(itemDir, { recursive: true });
69814
69991
  const name = basename5(p);
69815
- const trashedPath = join31(itemDir, name);
69992
+ const trashedPath = join32(itemDir, name);
69816
69993
  await moveItem(p, trashedPath);
69817
- await writeFile8(join31(itemDir, ".fp-meta.json"), JSON.stringify({ originalPath: resolve6(p), name, deletedAt: Date.now() }));
69994
+ await writeFile8(join32(itemDir, ".fp-meta.json"), JSON.stringify({ originalPath: resolve6(p), name, deletedAt: Date.now() }));
69818
69995
  return { ok: true, trashId: id, path: trashedPath };
69819
69996
  } catch (err2) {
69820
69997
  return { ok: false, error: String(err2.message) };
69821
69998
  }
69822
69999
  }
69823
70000
  case "listTrash": {
69824
- const trashDir = join31(root, TRASH_DIR);
70001
+ const trashDir = join32(root, TRASH_DIR);
69825
70002
  if (!existsSync14(trashDir))
69826
70003
  return [];
69827
70004
  try {
@@ -69830,8 +70007,8 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69830
70007
  if (!e.isDirectory())
69831
70008
  continue;
69832
70009
  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 });
70010
+ const meta4 = JSON.parse(readFileSync21(join32(trashDir, e.name, ".fp-meta.json"), "utf-8"));
70011
+ out.push({ trashId: e.name, name: meta4.name, path: join32(trashDir, e.name, meta4.name), originalPath: meta4.originalPath, deletedAt: meta4.deletedAt });
69835
70012
  } catch {
69836
70013
  }
69837
70014
  }
@@ -69844,14 +70021,14 @@ async function runRemoteFsOp(root, op, args, quotaKey) {
69844
70021
  const id = String(a[0] ?? "");
69845
70022
  if (!SAFE_SEG.test(id))
69846
70023
  return { ok: false, error: "Bad trash id." };
69847
- const itemDir = join31(root, TRASH_DIR, id);
70024
+ const itemDir = join32(root, TRASH_DIR, id);
69848
70025
  if (!existsSync14(itemDir))
69849
70026
  return { ok: false, error: "Not in trash." };
69850
70027
  try {
69851
- const meta4 = JSON.parse(readFileSync21(join31(itemDir, ".fp-meta.json"), "utf-8"));
70028
+ const meta4 = JSON.parse(readFileSync21(join32(itemDir, ".fp-meta.json"), "utf-8"));
69852
70029
  if (!validName(meta4.name))
69853
70030
  return { ok: false, error: "Bad trashed name." };
69854
- const src = join31(itemDir, meta4.name);
70031
+ const src = join32(itemDir, meta4.name);
69855
70032
  let dest = meta4.originalPath;
69856
70033
  if (!insideOk(dest))
69857
70034
  return OUTSIDE("restore");
@@ -69945,7 +70122,7 @@ function watchRemoteRoot(root, key2, onChange) {
69945
70122
  const rel = String(filename);
69946
70123
  if (rel.split(sep5).some((seg) => IGNORED.has(seg)))
69947
70124
  return;
69948
- pending2.add(join31(resolved, rel));
70125
+ pending2.add(join32(resolved, rel));
69949
70126
  if (timer)
69950
70127
  clearTimeout(timer);
69951
70128
  timer = setTimeout(() => {
@@ -70160,11 +70337,11 @@ function releaseDcClient(clientId) {
70160
70337
  // ../../packages/host-core/dist/fs-transfer.js
70161
70338
  import { readdir as readdir4, lstat as lstat2, mkdir as mkdir8, chmod, rm as rm2, readlink, symlink, stat as stat3 } from "node:fs/promises";
70162
70339
  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";
70340
+ import { basename as basename6, dirname as dirname13, join as join34, relative as relative6, sep as sep7 } from "node:path";
70164
70341
 
70165
70342
  // ../../packages/host-core/dist/active-link/paths.js
70166
70343
  init_base_paths();
70167
- import { join as join32, relative as relative5, sep as sep6 } from "node:path";
70344
+ import { join as join33, relative as relative5, sep as sep6 } from "node:path";
70168
70345
  function activeLinkEnabled() {
70169
70346
  return process.env.CCB_ACTIVE_LINK !== "0";
70170
70347
  }
@@ -70190,7 +70367,7 @@ function toPosixRel(root, abs) {
70190
70367
  return relative5(root, abs).split(sep6).join("/");
70191
70368
  }
70192
70369
  function fromPosixRel(root, rel) {
70193
- return join32(root, rel.split("/").join(sep6));
70370
+ return join33(root, rel.split("/").join(sep6));
70194
70371
  }
70195
70372
  function isExcludedAbs(abs, root) {
70196
70373
  const rel = toPosixRel(root, abs);
@@ -70202,7 +70379,7 @@ function activeLinkRoot() {
70202
70379
  return process.env.CCB_ACTIVE_LINK_DIR ?? tessPath("active-link");
70203
70380
  }
70204
70381
  function linkStateDir(linkId) {
70205
- return join32(activeLinkRoot(), linkId);
70382
+ return join33(activeLinkRoot(), linkId);
70206
70383
  }
70207
70384
 
70208
70385
  // ../../packages/host-core/dist/fs-transfer.js
@@ -70215,11 +70392,43 @@ function clearTimer(t) {
70215
70392
  return null;
70216
70393
  }
70217
70394
  function uniqueDir(parent, name) {
70218
- let candidate = join33(parent, name);
70395
+ let candidate = join34(parent, name);
70396
+ for (let i = 2; existsSync15(candidate) && i < 1e4; i++)
70397
+ candidate = join34(parent, `${name} ${i}`);
70398
+ return candidate;
70399
+ }
70400
+ function uniqueFile(parent, name) {
70401
+ const dot = name.lastIndexOf(".");
70402
+ const stem = dot > 0 ? name.slice(0, dot) : name;
70403
+ const ext = dot > 0 ? name.slice(dot) : "";
70404
+ let candidate = join34(parent, name);
70219
70405
  for (let i = 2; existsSync15(candidate) && i < 1e4; i++)
70220
- candidate = join33(parent, `${name} ${i}`);
70406
+ candidate = join34(parent, `${stem} ${i}${ext}`);
70221
70407
  return candidate;
70222
70408
  }
70409
+ var inboundObserver = null;
70410
+ function setInboundTransferObserver(cb) {
70411
+ inboundObserver = cb;
70412
+ }
70413
+ function notifyInbound(s, phase, transferId, extra) {
70414
+ if (!inboundObserver)
70415
+ return;
70416
+ try {
70417
+ inboundObserver({
70418
+ transferId,
70419
+ phase,
70420
+ fromHost: s.fromHost,
70421
+ destPath: s.singleFilePath ?? s.destRoot,
70422
+ singleFile: !!s.singleFilePath,
70423
+ totalFiles: s.totalFiles,
70424
+ totalBytes: s.totalBytes,
70425
+ filesDone: s.filesDone,
70426
+ bytesDone: s.bytesDone,
70427
+ ...extra
70428
+ });
70429
+ } catch {
70430
+ }
70431
+ }
70223
70432
  var producers = /* @__PURE__ */ new Map();
70224
70433
  function failProducer(transferId, message) {
70225
70434
  const s = producers.get(transferId);
@@ -70260,7 +70469,7 @@ async function buildManifest(root, followSymlinks, excludeHeavy) {
70260
70469
  continue;
70261
70470
  if (e.isDirectory() && excludeHeavy && HEAVY_DIRS.has(e.name))
70262
70471
  continue;
70263
- const abs = join33(dir2, e.name);
70472
+ const abs = join34(dir2, e.name);
70264
70473
  const rel = relative6(root, abs).split(sep7).join("/");
70265
70474
  const st = await lstat2(abs).catch(() => null);
70266
70475
  if (!st)
@@ -70285,15 +70494,30 @@ async function buildManifest(root, followSymlinks, excludeHeavy) {
70285
70494
  }
70286
70495
  }
70287
70496
  }
70497
+ const rootSt = await lstat2(root);
70498
+ if (!rootSt.isDirectory()) {
70499
+ const name = basename6(root);
70500
+ if (rootSt.isSymbolicLink()) {
70501
+ if (!followSymlinks)
70502
+ return { dirs, files: [{ rel: name, mode: rootSt.mode, kind: "symlink", size: 0 }], totalBytes: 0, singleFile: true };
70503
+ const real = await stat3(root);
70504
+ if (real.isDirectory()) {
70505
+ await walk(root);
70506
+ return { dirs, files, totalBytes, singleFile: false };
70507
+ }
70508
+ return { dirs, files: [{ rel: name, mode: real.mode, kind: "file", size: real.size }], totalBytes: real.size, singleFile: true };
70509
+ }
70510
+ return { dirs, files: [{ rel: name, mode: rootSt.mode, kind: "file", size: rootSt.size }], totalBytes: rootSt.size, singleFile: true };
70511
+ }
70288
70512
  await walk(root);
70289
- return { dirs, files, totalBytes };
70513
+ return { dirs, files, totalBytes, singleFile: false };
70290
70514
  }
70291
70515
  async function startOutboundTransfer(msg, controllerClientId, ownHostId2, hostRoot, send3) {
70292
70516
  const { transferId, toHost, srcPath } = msg;
70293
70517
  const state2 = { toHost, controllerClientId, send: send3, sentBytes: 0, ackedBytes: 0, done: false, cancelled: false, wake: null, idle: null };
70294
70518
  producers.set(transferId, state2);
70295
70519
  if (!isInsideRoot(srcPath, hostRoot)) {
70296
- failProducer(transferId, "Source folder is outside the shared root.");
70520
+ failProducer(transferId, "Source is outside the shared root.");
70297
70521
  return;
70298
70522
  }
70299
70523
  armProducerWatchdog(transferId);
@@ -70301,22 +70525,24 @@ async function startOutboundTransfer(msg, controllerClientId, ownHostId2, hostRo
70301
70525
  try {
70302
70526
  manifest = await buildManifest(srcPath, msg.followSymlinks, msg.excludeHeavy);
70303
70527
  } catch (e) {
70304
- failProducer(transferId, `Couldn't read the source folder: ${e.message}`);
70528
+ failProducer(transferId, `Couldn't read the source: ${e.message}`);
70305
70529
  return;
70306
70530
  }
70307
70531
  if (state2.cancelled) {
70308
70532
  producers.delete(transferId);
70309
70533
  return;
70310
70534
  }
70535
+ const srcBase = manifest.singleFile ? dirname13(srcPath) : srcPath;
70311
70536
  send3({
70312
70537
  type: "fs:transferManifest",
70313
70538
  transferId,
70314
70539
  toHost,
70315
70540
  fromHost: ownHostId2,
70316
70541
  controllerClientId,
70317
- destName: msg.destName,
70542
+ destName: manifest.singleFile ? msg.destName || basename6(srcPath) : msg.destName,
70318
70543
  destParent: msg.destParent,
70319
70544
  onCollision: msg.onCollision,
70545
+ singleFile: manifest.singleFile,
70320
70546
  totalFiles: manifest.files.length,
70321
70547
  totalBytes: manifest.totalBytes,
70322
70548
  dirs: manifest.dirs
@@ -70327,11 +70553,11 @@ async function startOutboundTransfer(msg, controllerClientId, ownHostId2, hostRo
70327
70553
  for (let fileIx = 0; fileIx < manifest.files.length && !state2.cancelled; fileIx++) {
70328
70554
  const f = manifest.files[fileIx];
70329
70555
  if (f.kind === "symlink") {
70330
- const target = await readlink(join33(srcPath, f.rel.split("/").join(sep7))).catch(() => "");
70556
+ const target = await readlink(join34(srcBase, f.rel.split("/").join(sep7))).catch(() => "");
70331
70557
  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
70558
  continue;
70333
70559
  }
70334
- const abs = join33(srcPath, f.rel.split("/").join(sep7));
70560
+ const abs = join34(srcBase, f.rel.split("/").join(sep7));
70335
70561
  let fd = null;
70336
70562
  try {
70337
70563
  fd = openSync5(abs, "r");
@@ -70388,6 +70614,7 @@ function emitProgress(transferId, send3, currentFile) {
70388
70614
  const s = consumers.get(transferId);
70389
70615
  if (!s)
70390
70616
  return;
70617
+ notifyInbound(s, "progress", transferId, { currentFile });
70391
70618
  send3({ type: "fs:transferProgress", transferId, filesDone: s.filesDone, totalFiles: s.totalFiles, bytesDone: s.bytesDone, totalBytes: s.totalBytes, currentFile }, { clientId: s.controllerClientId });
70392
70619
  }
70393
70620
  function finishConsumer(transferId, send3) {
@@ -70395,7 +70622,8 @@ function finishConsumer(transferId, send3) {
70395
70622
  if (!s)
70396
70623
  return;
70397
70624
  s.idle = clearTimer(s.idle);
70398
- send3({ type: "fs:transferDone", transferId, destRoot: s.destRoot, filesWritten: s.filesDone, bytesWritten: s.bytesDone }, { clientId: s.controllerClientId });
70625
+ notifyInbound(s, "done", transferId);
70626
+ send3({ type: "fs:transferDone", transferId, destRoot: s.singleFilePath ?? s.destRoot, filesWritten: s.filesDone, bytesWritten: s.bytesDone }, { clientId: s.controllerClientId });
70399
70627
  consumers.delete(transferId);
70400
70628
  }
70401
70629
  async function abortConsumer(transferId) {
@@ -70416,13 +70644,20 @@ async function abortConsumer(transferId) {
70416
70644
  await rm2(s.destRoot, { recursive: true, force: true });
70417
70645
  } catch {
70418
70646
  }
70647
+ } else if (s.singleFilePath && s.createdFile) {
70648
+ try {
70649
+ await rm2(s.singleFilePath, { force: true });
70650
+ } catch {
70651
+ }
70419
70652
  }
70420
70653
  consumers.delete(transferId);
70421
70654
  }
70422
70655
  function failConsumer(transferId, message, send3) {
70423
70656
  const s = consumers.get(transferId);
70424
- if (s)
70657
+ if (s) {
70658
+ notifyInbound(s, "error", transferId, { message });
70425
70659
  send3({ type: "fs:transferError", transferId, where: "dest", message }, { clientId: s.controllerClientId });
70660
+ }
70426
70661
  void abortConsumer(transferId);
70427
70662
  }
70428
70663
  function armConsumerWatchdog(transferId, send3) {
@@ -70438,12 +70673,56 @@ async function beginInboundTransfer(msg, hostRoot, send3) {
70438
70673
  send3({ type: "fs:transferError", transferId, where: "dest", message }, { clientId: controllerClientId });
70439
70674
  send3({ type: "fs:transferCancel", transferId, toHost: fromHost }, { hostId: fromHost });
70440
70675
  };
70441
- const parent = msg.destParent && isInsideRoot(msg.destParent, hostRoot) ? msg.destParent : hostRoot;
70676
+ const parent = canonicalForGuard(msg.destParent && isInsideRoot(msg.destParent, hostRoot) ? msg.destParent : hostRoot);
70442
70677
  if (!msg.destName || msg.destName.includes("/") || msg.destName.includes("\\") || msg.destName.includes("..")) {
70443
70678
  failNoState("Invalid destination name.");
70444
70679
  return;
70445
70680
  }
70446
- let destRoot = join33(parent, msg.destName);
70681
+ const register = (state2) => {
70682
+ consumers.set(transferId, {
70683
+ ...state2,
70684
+ totalFiles: msg.totalFiles,
70685
+ totalBytes: msg.totalBytes,
70686
+ filesDone: 0,
70687
+ bytesDone: 0,
70688
+ curIx: -1,
70689
+ curStream: null,
70690
+ cancelled: false,
70691
+ lastProgress: 0,
70692
+ idle: null
70693
+ });
70694
+ armConsumerWatchdog(transferId, send3);
70695
+ const s = consumers.get(transferId);
70696
+ notifyInbound(s, "begin", transferId);
70697
+ emitProgress(transferId, send3, "");
70698
+ };
70699
+ if (msg.singleFile) {
70700
+ let destFile = join34(parent, msg.destName);
70701
+ let createdFile = true;
70702
+ if (existsSync15(destFile)) {
70703
+ if (msg.onCollision === "abort") {
70704
+ failNoState(`"${msg.destName}" already exists on the destination.`);
70705
+ return;
70706
+ }
70707
+ if (msg.onCollision === "suffix")
70708
+ destFile = uniqueFile(parent, msg.destName);
70709
+ else
70710
+ createdFile = false;
70711
+ }
70712
+ if (!isInsideRoot(destFile, hostRoot)) {
70713
+ failNoState("Destination is outside the shared root.");
70714
+ return;
70715
+ }
70716
+ register({ fromHost, controllerClientId, destRoot: parent, createdRoot: false, singleFilePath: destFile, createdFile });
70717
+ if (msg.totalFiles === 0) {
70718
+ finishConsumer(transferId, send3);
70719
+ return;
70720
+ }
70721
+ await mkdir8(parent, { recursive: true }).catch(() => {
70722
+ });
70723
+ return;
70724
+ }
70725
+ let destRoot = join34(parent, msg.destName);
70447
70726
  let createdRoot = false;
70448
70727
  if (existsSync15(destRoot)) {
70449
70728
  if (msg.onCollision === "abort") {
@@ -70461,23 +70740,7 @@ async function beginInboundTransfer(msg, hostRoot, send3) {
70461
70740
  failNoState("Destination is outside the shared root.");
70462
70741
  return;
70463
70742
  }
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, "");
70743
+ register({ fromHost, controllerClientId, destRoot, createdRoot, singleFilePath: null, createdFile: false });
70481
70744
  if (msg.totalFiles === 0) {
70482
70745
  finishConsumer(transferId, send3);
70483
70746
  return;
@@ -70485,7 +70748,7 @@ async function beginInboundTransfer(msg, hostRoot, send3) {
70485
70748
  try {
70486
70749
  await mkdir8(destRoot, { recursive: true });
70487
70750
  for (const d of msg.dirs) {
70488
- const abs = join33(destRoot, d.rel.split("/").join(sep7));
70751
+ const abs = join34(destRoot, d.rel.split("/").join(sep7));
70489
70752
  if (isInsideRoot(abs, destRoot))
70490
70753
  await mkdir8(abs, { recursive: true }).catch(() => {
70491
70754
  });
@@ -70499,15 +70762,15 @@ async function writeInboundChunk(msg, send3) {
70499
70762
  if (!s || s.cancelled)
70500
70763
  return;
70501
70764
  armConsumerWatchdog(msg.transferId, send3);
70502
- const abs = join33(s.destRoot, msg.rel.split("/").join(sep7));
70503
- if (msg.rel.includes("..") || !isInsideRoot(abs, s.destRoot)) {
70765
+ const abs = s.singleFilePath ?? join34(s.destRoot, msg.rel.split("/").join(sep7));
70766
+ if (!s.singleFilePath && (msg.rel.includes("..") || !isInsideRoot(abs, s.destRoot))) {
70504
70767
  failConsumer(msg.transferId, `Refusing a path outside the destination: ${msg.rel}`, send3);
70505
70768
  return;
70506
70769
  }
70507
70770
  try {
70508
70771
  if (msg.kind === "symlink") {
70509
70772
  const target = Buffer.from(msg.base64, "base64").toString("utf-8");
70510
- const resolved = join33(dirname13(abs), target);
70773
+ const resolved = join34(dirname13(abs), target);
70511
70774
  if (isInsideRoot(resolved, s.destRoot)) {
70512
70775
  await mkdir8(dirname13(abs), { recursive: true }).catch(() => {
70513
70776
  });
@@ -70594,7 +70857,7 @@ function cancelTransfersForClient(clientId, send3) {
70594
70857
 
70595
70858
  // ../../packages/host-core/dist/peer-link/index.js
70596
70859
  init_base_paths();
70597
- import { join as join34 } from "node:path";
70860
+ import { join as join35 } from "node:path";
70598
70861
 
70599
70862
  // ../../packages/host-core/dist/peer-link/manager.js
70600
70863
  init_tls_direct();
@@ -70778,7 +71041,7 @@ var PeerLinkManager = class {
70778
71041
  }
70779
71042
  const socket2 = await q.makeQuicSocket(this.udpPort);
70780
71043
  this.quicSocket = socket2;
70781
- await q.startQuicServerOnSocket({ socket: socket2, identity, onStream: (bs3) => this.acceptInbound(bs3, "punch") });
71044
+ await q.startQuicServerOnSocket({ socket: socket2, identity, onStream: (bs4) => this.acceptInbound(bs4, "punch") });
70782
71045
  const refresh = async () => {
70783
71046
  const found = [];
70784
71047
  for (const { host, port } of resolveStunServers()) {
@@ -71097,7 +71360,7 @@ var PeerLinkManager = class {
71097
71360
  var P2P_ENABLED = process.env.CCB_P2P !== "0";
71098
71361
  function identityPaths() {
71099
71362
  const dir2 = process.env.CCB_P2P_DIR ?? tesseractHome();
71100
- return { certPath: join34(dir2, "peer-cert.pem"), keyPath: join34(dir2, "peer-key.pem") };
71363
+ return { certPath: join35(dir2, "peer-cert.pem"), keyPath: join35(dir2, "peer-key.pem") };
71101
71364
  }
71102
71365
  var manager = null;
71103
71366
  function peerLinkManager() {
@@ -71114,7 +71377,7 @@ async function openPeerLink(peerHostId, ctx2) {
71114
71377
  // ../../packages/host-core/dist/active-link/engine.js
71115
71378
  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
71379
  import { existsSync as existsSync18 } from "node:fs";
71117
- import { dirname as dirname18, join as join39, resolve as resolve9 } from "node:path";
71380
+ import { dirname as dirname18, join as join40, resolve as resolve9 } from "node:path";
71118
71381
 
71119
71382
  // ../../packages/host-core/dist/active-link/blocks.js
71120
71383
  import { createHash as createHash4 } from "node:crypto";
@@ -71379,7 +71642,7 @@ var BlockAssembler = class {
71379
71642
 
71380
71643
  // ../../packages/host-core/dist/active-link/manifest.js
71381
71644
  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";
71645
+ import { join as join36 } from "node:path";
71383
71646
 
71384
71647
  // ../../packages/host-core/dist/active-link/reconcile.js
71385
71648
  var ABSENT = { kind: "absent", t: -Infinity, hash: "" };
@@ -71415,7 +71678,7 @@ var LinkManifest = class {
71415
71678
  this.dir = dir2;
71416
71679
  }
71417
71680
  file() {
71418
- return join35(this.dir, "manifest.json");
71681
+ return join36(this.dir, "manifest.json");
71419
71682
  }
71420
71683
  load() {
71421
71684
  try {
@@ -71517,7 +71780,7 @@ var LinkManifest = class {
71517
71780
 
71518
71781
  // ../../packages/host-core/dist/active-link/scanner.js
71519
71782
  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";
71783
+ import { join as join37, sep as sep8 } from "node:path";
71521
71784
  async function walkRels(root, extraIgnore) {
71522
71785
  const out = [];
71523
71786
  async function rec(dir2) {
@@ -71532,7 +71795,7 @@ async function walkRels(root, extraIgnore) {
71532
71795
  continue;
71533
71796
  if (e.isDirectory() && HEAVY_DIRS.has(e.name))
71534
71797
  continue;
71535
- const abs = join36(dir2, e.name);
71798
+ const abs = join37(dir2, e.name);
71536
71799
  if (extraIgnore?.(abs))
71537
71800
  continue;
71538
71801
  if (e.isDirectory())
@@ -71571,7 +71834,7 @@ async function scanTree(root, prior, allow, extraIgnore) {
71571
71834
  return { records };
71572
71835
  }
71573
71836
  function absOf(root, rel) {
71574
- return join36(root, rel.split("/").join(sep8));
71837
+ return join37(root, rel.split("/").join(sep8));
71575
71838
  }
71576
71839
 
71577
71840
  // ../../packages/host-core/dist/active-link/gitignore.js
@@ -71941,10 +72204,10 @@ var ReaddirpStream = class extends Readable {
71941
72204
  }
71942
72205
  async _formatEntry(dirent, path6) {
71943
72206
  let entry;
71944
- const basename9 = this._isDirent ? dirent.name : dirent;
72207
+ const basename12 = this._isDirent ? dirent.name : dirent;
71945
72208
  try {
71946
- const fullPath = presolve(pjoin(path6, basename9));
71947
- entry = { path: prelative(this._root, fullPath), fullPath, basename: basename9 };
72209
+ const fullPath = presolve(pjoin(path6, basename12));
72210
+ entry = { path: prelative(this._root, fullPath), fullPath, basename: basename12 };
71948
72211
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
71949
72212
  } catch (err2) {
71950
72213
  this._onError(err2);
@@ -72483,9 +72746,9 @@ var NodeFsHandler = class {
72483
72746
  _watchWithNodeFs(path6, listener) {
72484
72747
  const opts = this.fsw.options;
72485
72748
  const directory = sysPath.dirname(path6);
72486
- const basename9 = sysPath.basename(path6);
72749
+ const basename12 = sysPath.basename(path6);
72487
72750
  const parent = this.fsw._getWatchedDir(directory);
72488
- parent.add(basename9);
72751
+ parent.add(basename12);
72489
72752
  const absolutePath = sysPath.resolve(path6);
72490
72753
  const options = {
72491
72754
  persistent: opts.persistent
@@ -72495,7 +72758,7 @@ var NodeFsHandler = class {
72495
72758
  let closer;
72496
72759
  if (opts.usePolling) {
72497
72760
  const enableBin = opts.interval !== opts.binaryInterval;
72498
- options.interval = enableBin && isBinaryPath(basename9) ? opts.binaryInterval : opts.interval;
72761
+ options.interval = enableBin && isBinaryPath(basename12) ? opts.binaryInterval : opts.interval;
72499
72762
  closer = setFsWatchFileListener(path6, absolutePath, options, {
72500
72763
  listener,
72501
72764
  rawEmitter: this.fsw._emitRaw
@@ -72517,11 +72780,11 @@ var NodeFsHandler = class {
72517
72780
  if (this.fsw.closed) {
72518
72781
  return;
72519
72782
  }
72520
- const dirname26 = sysPath.dirname(file2);
72521
- const basename9 = sysPath.basename(file2);
72522
- const parent = this.fsw._getWatchedDir(dirname26);
72783
+ const dirname28 = sysPath.dirname(file2);
72784
+ const basename12 = sysPath.basename(file2);
72785
+ const parent = this.fsw._getWatchedDir(dirname28);
72523
72786
  let prevStats = stats;
72524
- if (parent.has(basename9))
72787
+ if (parent.has(basename12))
72525
72788
  return;
72526
72789
  const listener = async (path6, newStats) => {
72527
72790
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file2, 5))
@@ -72546,9 +72809,9 @@ var NodeFsHandler = class {
72546
72809
  prevStats = newStats2;
72547
72810
  }
72548
72811
  } catch (error51) {
72549
- this.fsw._remove(dirname26, basename9);
72812
+ this.fsw._remove(dirname28, basename12);
72550
72813
  }
72551
- } else if (parent.has(basename9)) {
72814
+ } else if (parent.has(basename12)) {
72552
72815
  const at = newStats.atimeMs;
72553
72816
  const mt = newStats.mtimeMs;
72554
72817
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -73596,7 +73859,7 @@ var ActiveLinkEngine = class {
73596
73859
  }
73597
73860
  async start() {
73598
73861
  this.emit({ type: "status", linkId: this.opts.linkId, state: "scanning" });
73599
- await rm3(join39(this.stateDir, ".staging"), { recursive: true, force: true }).catch(() => {
73862
+ await rm3(join40(this.stateDir, ".staging"), { recursive: true, force: true }).catch(() => {
73600
73863
  });
73601
73864
  this.manifest.load();
73602
73865
  await this.reconcileScan();
@@ -74224,7 +74487,7 @@ var ActiveLinkEngine = class {
74224
74487
  this.inboundWatchdog.clear();
74225
74488
  }
74226
74489
  stagingPath(rel, tag) {
74227
- return join39(this.stateDir, ".staging", `${hashString(rel)}.${tag}`);
74490
+ return join40(this.stateDir, ".staging", `${hashString(rel)}.${tag}`);
74228
74491
  }
74229
74492
  pruneSuppress() {
74230
74493
  const now2 = this.now();
@@ -74250,9 +74513,9 @@ var ActiveLinkEngine = class {
74250
74513
 
74251
74514
  // ../../packages/host-core/dist/active-link/config.js
74252
74515
  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";
74516
+ import { join as join41 } from "node:path";
74254
74517
  function configPath2() {
74255
- return join40(activeLinkRoot(), "links.json");
74518
+ return join41(activeLinkRoot(), "links.json");
74256
74519
  }
74257
74520
  function loadLinkConfigs() {
74258
74521
  try {
@@ -74289,7 +74552,7 @@ function saveLinkConfig(cfg2) {
74289
74552
  writeAll(links);
74290
74553
  }
74291
74554
  function pendingPath() {
74292
- return join40(activeLinkRoot(), "pending-deprovisions.json");
74555
+ return join41(activeLinkRoot(), "pending-deprovisions.json");
74293
74556
  }
74294
74557
  function loadPendingDeprovisions() {
74295
74558
  try {
@@ -74911,6 +75174,12 @@ function connectedFsHosts() {
74911
75174
  const mine = ownHostId();
74912
75175
  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
75176
  }
75177
+ function listHosts() {
75178
+ return presentedHosts();
75179
+ }
75180
+ function ownHost() {
75181
+ return ownHostId();
75182
+ }
74914
75183
  function assertServerSelection() {
74915
75184
  if (!socket || socket.readyState !== 1)
74916
75185
  return;
@@ -75088,7 +75357,7 @@ function sendActiveLinkProvision(cfg2) {
75088
75357
  type: "provision",
75089
75358
  linkId: cfg2.linkId,
75090
75359
  fromHost: ownHostId(),
75091
- projectName: cfg2.remoteSubfolder ?? basename8(cfg2.localRoot),
75360
+ projectName: cfg2.remoteSubfolder ?? basename9(cfg2.localRoot),
75092
75361
  ...cfg2.remoteParent ? { remoteParent: cfg2.remoteParent } : {},
75093
75362
  ...cfg2.remoteSubfolder ? { remoteSubfolder: cfg2.remoteSubfolder } : {},
75094
75363
  ...cfg2.workspaceLinkId ? { workspaceLinkId: cfg2.workspaceLinkId } : {}
@@ -75115,9 +75384,9 @@ function provisionActiveLinkMirror(payload) {
75115
75384
  const remoteSubfolder = typeof payload.remoteSubfolder === "string" ? payload.remoteSubfolder : void 0;
75116
75385
  let localRoot;
75117
75386
  if (remoteParent && remoteSubfolder) {
75118
- localRoot = join41(remoteParent, safeFolderName(remoteSubfolder));
75387
+ localRoot = join42(remoteParent, safeFolderName(remoteSubfolder));
75119
75388
  } else {
75120
- localRoot = join41(HOST_ROOT, "Tesseract Mirrors", safeFolderName(projectName));
75389
+ localRoot = join42(HOST_ROOT, "Tesseract Mirrors", safeFolderName(projectName));
75121
75390
  }
75122
75391
  if (loadLinkConfigs().some((c) => c.localRoot === localRoot && c.linkId !== linkId)) {
75123
75392
  localRoot = `${localRoot}-${linkId.slice(0, 6)}`;
@@ -75630,6 +75899,7 @@ function registerAsHost() {
75630
75899
  const name = kind === "cloud" ? "Cloud agent" : hostname3();
75631
75900
  const caps = hostSessions ? ["agent", "fs", "term", "e2e"] : ["agent", "fs", "term"];
75632
75901
  caps.push("rdesk");
75902
+ caps.push("xfer");
75633
75903
  const upAdvert = upscaleAdvert();
75634
75904
  if (upAdvert.gpu)
75635
75905
  caps.push("gpu");
@@ -75703,6 +75973,10 @@ function connect() {
75703
75973
  hooks?.onChat?.(env.payload);
75704
75974
  return;
75705
75975
  }
75976
+ if (channel === "drop") {
75977
+ hooks?.onDrop?.(env.payload);
75978
+ return;
75979
+ }
75706
75980
  if (channel === "p2p") {
75707
75981
  const m = env.payload;
75708
75982
  for (const cb of [...p2pSubscribers])
@@ -76233,6 +76507,14 @@ var RdeskSessionManager = class {
76233
76507
  this.audit({ event: "mute", sessionId, detail: `mic=${!!mute.mic} sys=${!!mute.systemAudio}` });
76234
76508
  this.deps.sendControl(sessionId, s.viewer.clientId, { type: "rdesk:muted", mic: !!mute.mic, systemAudio: !!mute.systemAudio });
76235
76509
  }
76510
+ /** Host operator → viewer: send an in-session chat line. No-op if the session is gone. */
76511
+ sendChat(sessionId, text8) {
76512
+ const s = this.sessions.get(sessionId);
76513
+ if (!s)
76514
+ return;
76515
+ this.deps.sendControl(sessionId, s.viewer.clientId, { type: "rdesk:chat", text: text8, from: "host", at: Date.now() });
76516
+ this.audit({ event: "chat", sessionId, detail: `host: ${text8.slice(0, 80)}` });
76517
+ }
76236
76518
  /** Switch the captured display. */
76237
76519
  async setMonitor(sessionId, monitorId) {
76238
76520
  const s = this.sessions.get(sessionId);
@@ -76261,6 +76543,19 @@ var RdeskSessionManager = class {
76261
76543
  return this.setSource(sessionId, msg.sourceId);
76262
76544
  if (msg.type === "rdesk:send-keys")
76263
76545
  return this.sendKeys(sessionId, msg.combo);
76546
+ if (msg.type === "rdesk:chat") {
76547
+ const s = this.sessions.get(sessionId);
76548
+ const text8 = msg.text.slice(0, 4e3);
76549
+ this.deps.onChat?.({ sessionId, viewerName: s?.viewer.name, text: text8 });
76550
+ this.audit({ event: "chat", sessionId, detail: text8.slice(0, 80) });
76551
+ return;
76552
+ }
76553
+ if (msg.type === "rdesk:av") {
76554
+ const s = this.sessions.get(sessionId);
76555
+ this.deps.onCall?.({ sessionId, on: msg.on, viewerName: s?.viewer.name });
76556
+ this.audit({ event: "call", sessionId, detail: msg.on ? "viewer joined call" : "viewer left call" });
76557
+ return;
76558
+ }
76264
76559
  if (msg.type === "rdesk:set-av") {
76265
76560
  if (typeof msg.systemAudio === "boolean")
76266
76561
  this.sessions.get(sessionId)?.engine.setMute({ systemAudio: msg.systemAudio });
@@ -76290,7 +76585,7 @@ var RdeskSessionManager = class {
76290
76585
  };
76291
76586
 
76292
76587
  // ../../packages/host-core/dist/rdesk/publisher-window.js
76293
- import { join as join42 } from "node:path";
76588
+ import { join as join43 } from "node:path";
76294
76589
  var windows = /* @__PURE__ */ new Map();
76295
76590
  var ipcInstalled = false;
76296
76591
  function ensureIpc() {
@@ -76404,7 +76699,7 @@ var PublisherWindow = class {
76404
76699
  throw new Error("Publisher requires the daemon GUI shell (no BrowserWindow).");
76405
76700
  ensureIpc();
76406
76701
  const sourceId = opts.sourceId ?? await sourceIdForMonitor(opts.monitorId ?? await this.primaryMonitorId());
76407
- const win2 = new el2.BrowserWindow({
76702
+ const win3 = new el2.BrowserWindow({
76408
76703
  width: 320,
76409
76704
  height: 200,
76410
76705
  show: false,
@@ -76416,13 +76711,13 @@ var PublisherWindow = class {
76416
76711
  y: -32e3,
76417
76712
  webPreferences: { preload: daemonPreloadPath(), contextIsolation: true, sandbox: false, backgroundThrottling: false }
76418
76713
  });
76419
- this.win = win2;
76714
+ this.win = win3;
76420
76715
  windows.set(this.sessionId, this);
76421
76716
  try {
76422
- win2.setContentProtection(true);
76717
+ win3.setContentProtection(true);
76423
76718
  } catch {
76424
76719
  }
76425
- await win2.loadFile(join42(daemonUiDir(), "publisher.html"));
76720
+ await win3.loadFile(join43(daemonUiDir(), "publisher.html"));
76426
76721
  this.send("rdesk:pub:start", {
76427
76722
  sessionId: this.sessionId,
76428
76723
  sourceId,
@@ -76447,7 +76742,7 @@ var PublisherWindow = class {
76447
76742
  if (!el2?.BrowserWindow)
76448
76743
  throw new Error("Publisher requires the daemon GUI shell (no BrowserWindow).");
76449
76744
  ensureIpc();
76450
- const win2 = new el2.BrowserWindow({
76745
+ const win3 = new el2.BrowserWindow({
76451
76746
  width: 320,
76452
76747
  height: 200,
76453
76748
  show: false,
@@ -76458,13 +76753,13 @@ var PublisherWindow = class {
76458
76753
  y: -32e3,
76459
76754
  webPreferences: { preload: daemonPreloadPath(), contextIsolation: true, sandbox: false, backgroundThrottling: false }
76460
76755
  });
76461
- this.win = win2;
76756
+ this.win = win3;
76462
76757
  windows.set(this.sessionId, this);
76463
76758
  try {
76464
- win2.setContentProtection(true);
76759
+ win3.setContentProtection(true);
76465
76760
  } catch {
76466
76761
  }
76467
- await win2.loadFile(join42(daemonUiDir(), "publisher.html"));
76762
+ await win3.loadFile(join43(daemonUiDir(), "publisher.html"));
76468
76763
  this.send("rdesk:pub:start", {
76469
76764
  sessionId: this.sessionId,
76470
76765
  mode: "canvas",
@@ -78299,7 +78594,17 @@ var XAAD = utf8("tesseract-vault-export-v1");
78299
78594
  var XJSON_AAD = utf8("tesseract-vault-encrypted-json-v1");
78300
78595
 
78301
78596
  // ../../packages/host-core/dist/vault/store.js
78302
- import { randomUUID as randomUUID15 } from "node:crypto";
78597
+ import { randomUUID as randomUUID16 } from "node:crypto";
78598
+ var b64 = (u) => Buffer.from(u).toString("base64");
78599
+ var u82 = (s) => new Uint8Array(Buffer.from(s, "base64"));
78600
+ var isServerRecoverableMeta = (m) => !m.wrappedVaultKey;
78601
+ var metaFromWire = (w) => ({
78602
+ format: "TVK1",
78603
+ kdf: w.kdf,
78604
+ wrappedVaultKey: w.wrappedVaultKey,
78605
+ recoveryWrappedVaultKey: w.recoveryWrappedVaultKey ?? void 0,
78606
+ clientUpdatedAt: w.updatedAt
78607
+ });
78303
78608
  var TOMBSTONE_TTL_MS = 180 * 24 * 60 * 60 * 1e3;
78304
78609
  var VaultStore = class {
78305
78610
  persistence;
@@ -78311,6 +78616,15 @@ var VaultStore = class {
78311
78616
  // decrypted, only while unlocked
78312
78617
  dirty = /* @__PURE__ */ new Set();
78313
78618
  // item ids changed locally since last successful push
78619
+ // Mode 1: a no-password local vault whose random key is cached in the 0600 daemon dir (zero
78620
+ // friction; a fresh install starts with an empty usable vault). Upgraded via setMasterPassword,
78621
+ // or merged into the account + deleted on sign-in.
78622
+ deviceVault = false;
78623
+ // Mode 3: signed-in with NO master password. The key is cached locally (non-ZK by design → offline
78624
+ // auto-unlock) AND server-recoverable so any signed-in device auto-unlocks.
78625
+ serverRecoverable = false;
78626
+ // One-shot: on the mode-3 → mode-4 upgrade, tell the server to drop its recoverable key copy.
78627
+ pendingClearServerKey = false;
78314
78628
  constructor(persistence, now2 = () => Date.now()) {
78315
78629
  this.persistence = persistence;
78316
78630
  this.now = now2;
@@ -78324,7 +78638,163 @@ var VaultStore = class {
78324
78638
  for (const it of this.enc.values())
78325
78639
  if (!it.deleted)
78326
78640
  live++;
78327
- return { exists: this.meta !== null, unlocked: this.vaultKey !== null, itemCount: live };
78641
+ return {
78642
+ exists: this.meta !== null || this.deviceVault,
78643
+ unlocked: this.vaultKey !== null,
78644
+ itemCount: live,
78645
+ deviceVault: this.deviceVault,
78646
+ serverRecoverable: this.serverRecoverable,
78647
+ hasMasterPassword: !!(this.meta && this.meta.wrappedVaultKey && !this.serverRecoverable)
78648
+ };
78649
+ }
78650
+ isDeviceVault() {
78651
+ return this.deviceVault;
78652
+ }
78653
+ isServerRecoverable() {
78654
+ return this.serverRecoverable;
78655
+ }
78656
+ /** For sync push: the raw key to hand the server to seal (mode 3), so other devices auto-unlock. */
78657
+ serverRecoverableKeyForPush() {
78658
+ return this.serverRecoverable && this.vaultKey ? b64(this.vaultKey) : null;
78659
+ }
78660
+ /** For sync push: consume the one-shot "clear the server's recoverable key" flag (mode-4 upgrade). */
78661
+ /** Peek the one-shot "clear the server's recoverable key" flag (mode-4 upgrade) WITHOUT consuming it. */
78662
+ hasPendingClearServerKey() {
78663
+ return this.pendingClearServerKey;
78664
+ }
78665
+ /** Consume the flag — call ONLY after the meta PUT carrying `serverRecoverableKey: null` succeeds. */
78666
+ clearPendingServerKeyFlag() {
78667
+ this.pendingClearServerKey = false;
78668
+ }
78669
+ /** On sign-out, drop an ACCOUNT vault's local state so this process-wide singleton can't leak one
78670
+ * user's vault into the next user's sync (the daemon isn't restarted on an in-process account
78671
+ * switch). The account's ciphertext + meta are safe on the server (re-pulled on the next sign-in),
78672
+ * and the mode-3 device key IS the account key, so it's cleared too. A machine-local NO-PASSWORD
78673
+ * device vault (mode 1) is NOT account-bound — it's left intact. The next ensureVault re-establishes
78674
+ * a fresh device vault from the now-empty state. */
78675
+ resetForSignOut() {
78676
+ if (this.deviceVault)
78677
+ return;
78678
+ this.meta = null;
78679
+ this.enc.clear();
78680
+ this.vaultKey = null;
78681
+ this.items = null;
78682
+ this.dirty.clear();
78683
+ this.serverRecoverable = false;
78684
+ this.pendingClearServerKey = false;
78685
+ this.persistence.clearDeviceKey();
78686
+ this.persist();
78687
+ }
78688
+ // ── zero-friction lifecycle (device vault / server-recoverable / merge) ────────────
78689
+ /** Call once on load: if no vault exists, AUTO-CREATE an empty no-password device vault (mode 1,
78690
+ * no prompt); if a mode-3 (server-recoverable) meta + cached key exist, auto-unlock. No-op when a
78691
+ * master-password vault exists (it stays locked for the unlock screen). Mirrors the extension. */
78692
+ async ensureVault() {
78693
+ const stored = this.persistence.loadDeviceKey();
78694
+ if (this.meta) {
78695
+ if (isServerRecoverableMeta(this.meta) && stored) {
78696
+ this.vaultKey = u82(stored);
78697
+ this.serverRecoverable = true;
78698
+ await this.decryptAll();
78699
+ }
78700
+ return;
78701
+ }
78702
+ if (stored) {
78703
+ this.vaultKey = u82(stored);
78704
+ } else {
78705
+ this.vaultKey = newVaultKey();
78706
+ this.persistence.saveDeviceKey(b64(this.vaultKey));
78707
+ this.items = /* @__PURE__ */ new Map();
78708
+ }
78709
+ this.deviceVault = true;
78710
+ if (this.enc.size)
78711
+ await this.decryptAll();
78712
+ else
78713
+ this.items = this.items ?? /* @__PURE__ */ new Map();
78714
+ }
78715
+ /** On sign-in, reconcile the local vault against the account snapshot:
78716
+ * - account has a master-password vault → `needsMasterPassword` (caller collects it → mergeIntoAccount);
78717
+ * - else establish mode 3 from the device/server key + fold local items in (auto-unlock, no prompt). */
78718
+ async reconcileSignIn(snap) {
78719
+ if (snap.meta && !snap.meta.serverKey && snap.meta.wrappedVaultKey) {
78720
+ return { status: "needsMasterPassword" };
78721
+ }
78722
+ const localItems = this.items ? [...this.items.values()] : [];
78723
+ const cached3 = this.persistence.loadDeviceKey();
78724
+ let key2;
78725
+ if (snap.meta?.serverKey)
78726
+ key2 = u82(snap.meta.serverKey);
78727
+ else if (this.vaultKey)
78728
+ key2 = this.vaultKey;
78729
+ else if (cached3)
78730
+ key2 = u82(cached3);
78731
+ else
78732
+ key2 = newVaultKey();
78733
+ this.vaultKey = key2;
78734
+ this.persistence.saveDeviceKey(b64(key2));
78735
+ this.serverRecoverable = true;
78736
+ this.deviceVault = false;
78737
+ this.meta = { format: "TVK1", kdf: defaultKdfParams(), wrappedVaultKey: "", recoveryWrappedVaultKey: void 0, clientUpdatedAt: this.now() };
78738
+ this.enc = new Map((snap.items ?? []).map((i) => [i.id, i]));
78739
+ for (const it of localItems) {
78740
+ this.enc.set(it.id, await encryptItem(key2, it));
78741
+ this.dirty.add(it.id);
78742
+ }
78743
+ await this.decryptAll();
78744
+ this.persist();
78745
+ return { status: "unlocked" };
78746
+ }
78747
+ /** Merge the local vault's items INTO the account vault: unlock it with the master password (or
78748
+ * create it if the account has none), re-encrypt local items under the account key, and drop the
78749
+ * device vault. Returns a recovery key only when a NEW account vault was created. */
78750
+ async reconcileMergeIntoAccount(password, snap) {
78751
+ const localItems = this.items ? [...this.items.values()] : [];
78752
+ let accountKey;
78753
+ let recoveryKey;
78754
+ if (snap.meta) {
78755
+ const accountMeta = metaFromWire(snap.meta);
78756
+ accountKey = await unlockVault(password, accountMeta);
78757
+ this.meta = accountMeta;
78758
+ } else {
78759
+ const created = await createVault(password);
78760
+ const rec = await createRecoveryKey(created.vaultKey);
78761
+ this.meta = { ...created.meta, recoveryWrappedVaultKey: rec.recoveryWrappedVaultKey, clientUpdatedAt: this.now() };
78762
+ accountKey = created.vaultKey;
78763
+ recoveryKey = rec.recoveryKey;
78764
+ }
78765
+ this.enc = new Map((snap.items ?? []).map((i) => [i.id, i]));
78766
+ for (const it of localItems) {
78767
+ this.enc.set(it.id, await encryptItem(accountKey, it));
78768
+ this.dirty.add(it.id);
78769
+ }
78770
+ this.vaultKey = accountKey;
78771
+ this.deviceVault = false;
78772
+ this.serverRecoverable = false;
78773
+ this.persistence.clearDeviceKey();
78774
+ await this.decryptAll();
78775
+ this.persist();
78776
+ return { recoveryKey };
78777
+ }
78778
+ /** Set a master password on a no-password vault (mode 1 → 2, or mode 3 → 4). Same key, re-wrapped;
78779
+ * items unchanged. Drops the local key cache (now zero-knowledge) and — if it was server-recoverable
78780
+ * — schedules clearing the server's recoverable key copy on the next sync. Returns a recovery key. */
78781
+ async setMasterPassword(password) {
78782
+ if (!this.vaultKey)
78783
+ throw new Error("Vault is locked.");
78784
+ if (this.meta && this.meta.wrappedVaultKey && !this.serverRecoverable)
78785
+ throw new Error("A master password is already set.");
78786
+ const kdf = defaultKdfParams();
78787
+ const wrappedVaultKey = await wrapVaultKey(deriveMasterKey(password, kdf), this.vaultKey);
78788
+ const rec = await createRecoveryKey(this.vaultKey);
78789
+ const wasServerRecoverable = this.serverRecoverable;
78790
+ this.meta = { format: "TVK1", kdf, wrappedVaultKey, recoveryWrappedVaultKey: rec.recoveryWrappedVaultKey, clientUpdatedAt: this.now() };
78791
+ this.deviceVault = false;
78792
+ this.serverRecoverable = false;
78793
+ this.persistence.clearDeviceKey();
78794
+ if (wasServerRecoverable)
78795
+ this.pendingClearServerKey = true;
78796
+ this.persist();
78797
+ return { recoveryKey: rec.recoveryKey };
78328
78798
  }
78329
78799
  isUnlocked() {
78330
78800
  return this.vaultKey !== null;
@@ -78342,6 +78812,12 @@ var VaultStore = class {
78342
78812
  throw new Error("No vault to unlock");
78343
78813
  this.vaultKey = new Uint8Array(Buffer.from(base64Key, "base64"));
78344
78814
  await this.decryptAll();
78815
+ const live = [...this.enc.values()].filter((e) => !e.deleted).length;
78816
+ if (live > 0 && (this.items?.size ?? 0) === 0) {
78817
+ this.vaultKey = null;
78818
+ this.items = null;
78819
+ throw new Error("Key did not match this vault");
78820
+ }
78345
78821
  }
78346
78822
  persist() {
78347
78823
  this.persistence.save({ meta: this.meta, items: [...this.enc.values()] });
@@ -78386,7 +78862,8 @@ var VaultStore = class {
78386
78862
  if (enc.deleted)
78387
78863
  continue;
78388
78864
  try {
78389
- map2.set(enc.id, await decryptItem(key2, enc));
78865
+ const it = await decryptItem(key2, enc);
78866
+ map2.set(it.id, it);
78390
78867
  } catch {
78391
78868
  }
78392
78869
  }
@@ -78419,7 +78896,7 @@ var VaultStore = class {
78419
78896
  const existing = input.id ? items.get(input.id) : void 0;
78420
78897
  const { id: _drop, name, folderId, favorite, reprompt, notes, tags, ...content } = input;
78421
78898
  const item = {
78422
- id: input.id ?? randomUUID15(),
78899
+ id: input.id ?? randomUUID16(),
78423
78900
  name,
78424
78901
  folderId,
78425
78902
  favorite,
@@ -78448,7 +78925,7 @@ var VaultStore = class {
78448
78925
  const now2 = this.now();
78449
78926
  for (const d of drafts) {
78450
78927
  const { name, notes, favorite, tags, ...content } = d;
78451
- const item = { id: randomUUID15(), name, notes, favorite, tags, createdAt: now2, updatedAt: now2, ...content };
78928
+ const item = { id: randomUUID16(), name, notes, favorite, tags, createdAt: now2, updatedAt: now2, ...content };
78452
78929
  items.set(item.id, item);
78453
78930
  this.enc.set(item.id, await encryptItem(key2, item));
78454
78931
  this.dirty.add(item.id);
@@ -78474,6 +78951,8 @@ var VaultStore = class {
78474
78951
  }
78475
78952
  /** Adopt a newer remote meta (e.g. a master-password change on another device). */
78476
78953
  applyRemoteMeta(meta4) {
78954
+ if (this.deviceVault)
78955
+ return;
78477
78956
  if (meta4 && (!this.meta || (meta4.clientUpdatedAt ?? 0) > (this.meta.clientUpdatedAt ?? 0))) {
78478
78957
  this.meta = meta4;
78479
78958
  this.persist();
@@ -78531,9 +79010,18 @@ var VaultSync = class {
78531
79010
  this.getToken = getToken2;
78532
79011
  this.debounceMs = debounceMs;
78533
79012
  }
79013
+ /** A local NO-PASSWORD device vault (mode 1) has no account to sync with — its items are encrypted
79014
+ * under the on-disk device key, so pushing them would poison the account with undecryptable
79015
+ * ciphertext AND pulling the account meta would overwrite the device vault's null meta → orphan
79016
+ * the local items on the next lock/restart (the exact data-loss reconcileSignIn guards against).
79017
+ * Sign-in (reconcileSignIn/mergeIntoAccount) is what turns a device vault INTO an account vault;
79018
+ * until then, sync must stay OFF. Mirrors the extension/WebVaultClient `if (deviceVault) return`. */
79019
+ syncable() {
79020
+ return !!this.getToken() && !this.store.isDeviceVault();
79021
+ }
78534
79022
  /** Call after any local mutation (upsert/remove/import/meta change). Debounced. */
78535
79023
  markLocalChange() {
78536
- if (!this.getToken())
79024
+ if (!this.syncable())
78537
79025
  return;
78538
79026
  if (this.timer)
78539
79027
  clearTimeout(this.timer);
@@ -78543,13 +79031,19 @@ var VaultSync = class {
78543
79031
  /** Once per signed-in session (deduped by token): pull + reconcile, then push. */
78544
79032
  async ensureSynced() {
78545
79033
  const token3 = this.getToken();
78546
- if (!token3 || token3 === this.lastSyncedToken)
79034
+ if (!this.syncable() || token3 === this.lastSyncedToken)
78547
79035
  return;
78548
79036
  this.lastSyncedToken = token3;
78549
79037
  await this.pullAndReconcile().catch(() => {
78550
79038
  this.lastSyncedToken = null;
78551
79039
  });
78552
79040
  }
79041
+ /** Push the current cache (dirty items + meta, incl. mode-3 serverRecoverableKey) NOW — used right
79042
+ * after a sign-in reconcile / merge so the account is updated immediately, not on the debounce. */
79043
+ async pushNow() {
79044
+ await this.push().catch(() => {
79045
+ });
79046
+ }
78553
79047
  onSignedOut() {
78554
79048
  this.lastSyncedToken = null;
78555
79049
  this.lastMetaPushed = -1;
@@ -78573,7 +79067,7 @@ var VaultSync = class {
78573
79067
  }
78574
79068
  }
78575
79069
  async push() {
78576
- if (!this.getToken())
79070
+ if (!this.syncable())
78577
79071
  return;
78578
79072
  const dirty = this.store.takeDirty();
78579
79073
  if (dirty.length) {
@@ -78583,8 +79077,17 @@ var VaultSync = class {
78583
79077
  const { meta: meta4 } = this.store.metaForPush();
78584
79078
  const clock = meta4?.clientUpdatedAt ?? -1;
78585
79079
  if (meta4 && clock > this.lastMetaPushed) {
78586
- await this.api.putMeta(toWire(meta4));
79080
+ const wire = toWire(meta4);
79081
+ const srk = this.store.serverRecoverableKeyForPush();
79082
+ const clearing = !srk && this.store.hasPendingClearServerKey();
79083
+ if (srk)
79084
+ wire.serverRecoverableKey = srk;
79085
+ else if (clearing)
79086
+ wire.serverRecoverableKey = null;
79087
+ await this.api.putMeta(wire);
78587
79088
  this.lastMetaPushed = clock;
79089
+ if (clearing)
79090
+ this.store.clearPendingServerKeyFlag();
78588
79091
  }
78589
79092
  }
78590
79093
  };
@@ -78608,12 +79111,15 @@ function makeDaemonVaultApi(getOrigin, getToken2) {
78608
79111
 
78609
79112
  // ../../packages/host-core/dist/vault/persistence.js
78610
79113
  init_base_paths();
78611
- import { existsSync as existsSync21, mkdirSync as mkdirSync19, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "node:fs";
79114
+ import { existsSync as existsSync21, mkdirSync as mkdirSync19, readFileSync as readFileSync25, writeFileSync as writeFileSync17, unlinkSync as unlinkSync2 } from "node:fs";
78612
79115
  import { dirname as dirname19 } from "node:path";
78613
79116
  function vaultStoreFile() {
78614
79117
  return homePath(".tesseract", "vault", "vault.json");
78615
79118
  }
78616
- function makeVaultPersistence(file2 = vaultStoreFile) {
79119
+ function vaultDeviceKeyFile() {
79120
+ return homePath(".tesseract", "vault", "device-key");
79121
+ }
79122
+ function makeVaultPersistence(file2 = vaultStoreFile, deviceKeyFile = vaultDeviceKeyFile) {
78617
79123
  return {
78618
79124
  load() {
78619
79125
  try {
@@ -78633,6 +79139,27 @@ function makeVaultPersistence(file2 = vaultStoreFile) {
78633
79139
  writeFileSync17(file2(), JSON.stringify(state2), { mode: 384 });
78634
79140
  } catch {
78635
79141
  }
79142
+ },
79143
+ loadDeviceKey() {
79144
+ try {
79145
+ return existsSync21(deviceKeyFile()) ? readFileSync25(deviceKeyFile(), "utf8").trim() || null : null;
79146
+ } catch {
79147
+ return null;
79148
+ }
79149
+ },
79150
+ saveDeviceKey(base64Key) {
79151
+ try {
79152
+ mkdirSync19(dirname19(deviceKeyFile()), { recursive: true, mode: 448 });
79153
+ writeFileSync17(deviceKeyFile(), base64Key, { mode: 384 });
79154
+ } catch {
79155
+ }
79156
+ },
79157
+ clearDeviceKey() {
79158
+ try {
79159
+ if (existsSync21(deviceKeyFile()))
79160
+ unlinkSync2(deviceKeyFile());
79161
+ } catch {
79162
+ }
78636
79163
  }
78637
79164
  };
78638
79165
  }
@@ -78640,7 +79167,7 @@ function makeVaultPersistence(file2 = vaultStoreFile) {
78640
79167
  // ../../packages/host-core/dist/vault/biometric.js
78641
79168
  init_base_paths();
78642
79169
  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";
79170
+ import { join as join44 } from "node:path";
78644
79171
  async function electronBio() {
78645
79172
  if (!electronModule())
78646
79173
  return null;
@@ -78661,7 +79188,7 @@ function bioDir() {
78661
79188
  return d;
78662
79189
  }
78663
79190
  function bioFile(id) {
78664
- return join43(bioDir(), `${String(id).replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64)}.enc`);
79191
+ return join44(bioDir(), `${String(id).replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64)}.enc`);
78665
79192
  }
78666
79193
  async function biometricAvailable() {
78667
79194
  if (process.platform !== "darwin")
@@ -78709,13 +79236,15 @@ function settingsFile() {
78709
79236
  var VaultManager = class {
78710
79237
  store;
78711
79238
  sync;
79239
+ api;
78712
79240
  autoLockMinutes = 0;
78713
79241
  idleTimer = null;
78714
79242
  constructor() {
78715
79243
  this.store = new VaultStore(makeVaultPersistence());
78716
- this.sync = new VaultSync(this.store, makeDaemonVaultApi(() => readCredentials()?.apiOrigin ?? "", () => readCredentials()?.accessToken ?? null), () => readCredentials()?.accessToken ?? null);
79244
+ this.api = makeDaemonVaultApi(() => readCredentials()?.apiOrigin ?? "", () => readCredentials()?.accessToken ?? null);
79245
+ this.sync = new VaultSync(this.store, this.api, () => readCredentials()?.accessToken ?? null);
78717
79246
  this.autoLockMinutes = this.loadAutoLock();
78718
- void this.sync.ensureSynced().catch(() => {
79247
+ void this.ensureVault().then(() => this.sync.ensureSynced()).catch(() => {
78719
79248
  });
78720
79249
  }
78721
79250
  // ── auto-lock ────────────────────────────────────────────────────────────────────
@@ -78764,12 +79293,49 @@ var VaultManager = class {
78764
79293
  exists: s.exists,
78765
79294
  unlocked: s.unlocked,
78766
79295
  itemCount: s.itemCount,
78767
- hasMasterPassword: s.exists,
79296
+ hasMasterPassword: !!s.hasMasterPassword,
79297
+ deviceVault: !!s.deviceVault,
79298
+ serverRecoverable: !!s.serverRecoverable,
78768
79299
  biometricAvailable: await biometricAvailable(),
78769
79300
  biometricEnrolled: id ? biometricEnrolled(id) : false,
78770
79301
  autoLockMinutes: this.autoLockMinutes
78771
79302
  };
78772
79303
  }
79304
+ // ── zero-friction lifecycle (device vault / sign-in reconcile / merge) ─────────────
79305
+ /** Auto-create/auto-unlock the no-password device vault (mode 1) or auto-unlock mode 3. Idempotent;
79306
+ * called on construction + on the app's vault-tab mount. Mirrors the extension's ensureLoaded. */
79307
+ async ensureVault() {
79308
+ await this.store.ensureVault();
79309
+ if (this.store.isServerRecoverable())
79310
+ void this.sync.ensureSynced().catch(() => {
79311
+ });
79312
+ this.touch();
79313
+ }
79314
+ /** On sign-in: reconcile local ↔ account. `needsMasterPassword` → the app collects it + calls
79315
+ * mergeIntoAccount; `unlocked` → mode 3 established from the device key + local items folded in. */
79316
+ async signIn() {
79317
+ const snap = await this.api.getSnapshot();
79318
+ const r = await this.store.reconcileSignIn(snap);
79319
+ if (r.status === "unlocked")
79320
+ await this.sync.pushNow();
79321
+ this.touch();
79322
+ return r;
79323
+ }
79324
+ /** Merge the local vault into the account (unlock/create it with `masterPassword`), then sync. */
79325
+ async mergeIntoAccount(masterPassword) {
79326
+ const snap = await this.api.getSnapshot();
79327
+ const r = await this.store.reconcileMergeIntoAccount(masterPassword, snap);
79328
+ await this.sync.pushNow();
79329
+ this.touch();
79330
+ return r;
79331
+ }
79332
+ /** Optional upgrade: set a master password on a no-password vault (mode 1→2 or 3→4). */
79333
+ async setMasterPassword(masterPassword) {
79334
+ const r = await this.store.setMasterPassword(masterPassword);
79335
+ await this.sync.pushNow();
79336
+ this.touch();
79337
+ return r;
79338
+ }
78773
79339
  // ── lifecycle ────────────────────────────────────────────────────────────────────
78774
79340
  async create(masterPassword) {
78775
79341
  const res = await this.store.create(masterPassword);
@@ -78820,6 +79386,20 @@ var VaultManager = class {
78820
79386
  });
78821
79387
  this.touch();
78822
79388
  }
79389
+ // ── cross-device unlock (phone approve → desktop unlock) ───────────────────────────
79390
+ /** The (non-secret) vault crypto header for the QR-unlock payload — kdf + wrapped keys, NO raw key.
79391
+ * Returned to the renderer so it can render the QR; the raw key never leaves the daemon. */
79392
+ exportMetaWire() {
79393
+ const m = this.store.getMeta();
79394
+ return m ? { format: m.format, kdf: m.kdf, wrappedVaultKey: m.wrappedVaultKey, recoveryWrappedVaultKey: m.recoveryWrappedVaultKey ?? null, updatedAt: m.clientUpdatedAt ?? 0 } : null;
79395
+ }
79396
+ /** Adopt a raw vault key recovered cross-device (the phone approved + sealed it back over the relay). */
79397
+ async adoptKey(base64Key) {
79398
+ await this.store.adoptVaultKeyBase64(base64Key);
79399
+ void this.sync.ensureSynced().catch(() => {
79400
+ });
79401
+ this.touch();
79402
+ }
78823
79403
  disableBiometric() {
78824
79404
  const id = this.accountId();
78825
79405
  if (id)
@@ -78966,6 +79546,9 @@ var VaultManager = class {
78966
79546
  onSignedOut() {
78967
79547
  this.sync.onSignedOut();
78968
79548
  this.lock();
79549
+ this.store.resetForSignOut();
79550
+ void this.store.ensureVault().catch(() => {
79551
+ });
78969
79552
  }
78970
79553
  };
78971
79554
  var singleton = null;
@@ -78996,7 +79579,7 @@ function originAllowed(origin) {
78996
79579
  }
78997
79580
  }
78998
79581
  async function startLocalServer(opts) {
78999
- const log5 = opts.log ?? (() => {
79582
+ const log9 = opts.log ?? (() => {
79000
79583
  });
79001
79584
  const wss3 = new import_websocket_server.default({ host: "127.0.0.1", port: 0 });
79002
79585
  await new Promise((resolve12, reject) => {
@@ -79005,7 +79588,7 @@ async function startLocalServer(opts) {
79005
79588
  });
79006
79589
  const addr = wss3.address();
79007
79590
  const port = typeof addr === "object" && addr ? addr.port : 0;
79008
- log5(`local listener on 127.0.0.1:${port}`);
79591
+ log9(`local listener on 127.0.0.1:${port}`);
79009
79592
  wss3.on("connection", (ws, req) => {
79010
79593
  const url2 = new URL(req.url ?? "/", "http://127.0.0.1");
79011
79594
  const origin = req.headers.origin ?? void 0;
@@ -79365,6 +79948,19 @@ async function startLocalServer(opts) {
79365
79948
  case "status":
79366
79949
  result = await vm.status();
79367
79950
  break;
79951
+ case "ensureVault":
79952
+ await vm.ensureVault();
79953
+ result = { ok: true };
79954
+ break;
79955
+ case "signIn":
79956
+ result = await vm.signIn();
79957
+ break;
79958
+ case "mergeIntoAccount":
79959
+ result = await vm.mergeIntoAccount(String(a[0]));
79960
+ break;
79961
+ case "setMasterPassword":
79962
+ result = await vm.setMasterPassword(String(a[0]));
79963
+ break;
79368
79964
  case "create":
79369
79965
  result = await vm.create(String(a[0]));
79370
79966
  break;
@@ -79380,6 +79976,13 @@ async function startLocalServer(opts) {
79380
79976
  await vm.unlockBiometric();
79381
79977
  result = { ok: true };
79382
79978
  break;
79979
+ case "exportMeta":
79980
+ result = vm.exportMetaWire();
79981
+ break;
79982
+ case "adoptKey":
79983
+ await vm.adoptKey(String(a[0]));
79984
+ result = { ok: true };
79985
+ break;
79383
79986
  case "lock":
79384
79987
  vm.lock();
79385
79988
  result = { ok: true };
@@ -79482,18 +80085,18 @@ async function startLocalServer(opts) {
79482
80085
 
79483
80086
  // ../../packages/host-core/dist/daemon-runtime.js
79484
80087
  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";
80088
+ import { readFileSync as readFileSync30, writeFileSync as writeFileSync25, existsSync as existsSync28, mkdirSync as mkdirSync26, unlinkSync as unlinkSync4, chmodSync as chmodSync5 } from "node:fs";
79486
80089
  import { execFileSync as execFileSync4 } from "node:child_process";
79487
- import { join as join50 } from "node:path";
80090
+ import { join as join54 } from "node:path";
79488
80091
  import { randomBytes as randomBytes8 } from "node:crypto";
79489
80092
  import { hostname as hostname4 } from "node:os";
79490
80093
 
79491
80094
  // ../../packages/host-core/dist/device-identity.js
79492
- import { join as join44 } from "node:path";
80095
+ import { join as join45 } from "node:path";
79493
80096
  import { readFileSync as readFileSync28, writeFileSync as writeFileSync20, existsSync as existsSync23, mkdirSync as mkdirSync22, renameSync as renameSync8, chmodSync as chmodSync3 } from "node:fs";
79494
80097
  var cached2 = null;
79495
80098
  function identityPath() {
79496
- return join44(daemonDir2(), "identity.json");
80099
+ return join45(daemonDir2(), "identity.json");
79497
80100
  }
79498
80101
  async function loadOrCreateDeviceIdentity() {
79499
80102
  if (cached2)
@@ -79521,6 +80124,40 @@ async function loadOrCreateDeviceIdentity() {
79521
80124
  cached2 = kp;
79522
80125
  return kp;
79523
80126
  }
80127
+ var sealingCached = null;
80128
+ function sealingPath() {
80129
+ return join45(daemonDir2(), "sealing.json");
80130
+ }
80131
+ var EC_DH = { name: "ECDH", namedCurve: "P-256" };
80132
+ async function loadOrCreateSealingKey() {
80133
+ if (sealingCached)
80134
+ return sealingCached;
80135
+ const { subtle: subtle4 } = globalThis.crypto;
80136
+ try {
80137
+ const stored = JSON.parse(readFileSync28(sealingPath(), "utf-8"));
80138
+ if (stored?.privateJwk && stored.publicRaw) {
80139
+ const privateKey = await subtle4.importKey("jwk", stored.privateJwk, EC_DH, true, ["deriveBits"]);
80140
+ sealingCached = { privateKey, publicRaw: unb64u(stored.publicRaw) };
80141
+ return sealingCached;
80142
+ }
80143
+ } catch {
80144
+ }
80145
+ const kp = await subtle4.generateKey(EC_DH, true, ["deriveBits"]);
80146
+ const publicRaw = new Uint8Array(await subtle4.exportKey("raw", kp.publicKey));
80147
+ const privateJwk = await subtle4.exportKey("jwk", kp.privateKey);
80148
+ const dir2 = daemonDir2();
80149
+ if (!existsSync23(dir2))
80150
+ mkdirSync22(dir2, { recursive: true, mode: 448 });
80151
+ const tmp = `${sealingPath()}.tmp`;
80152
+ writeFileSync20(tmp, JSON.stringify({ privateJwk, publicRaw: b64u(publicRaw), alg: "P256-ECDH" }, null, 2), { mode: 384 });
80153
+ renameSync8(tmp, sealingPath());
80154
+ try {
80155
+ chmodSync3(sealingPath(), 384);
80156
+ } catch {
80157
+ }
80158
+ sealingCached = { privateKey: kp.privateKey, publicRaw };
80159
+ return sealingCached;
80160
+ }
79524
80161
  var DEVICES_BASE = (apiOrigin) => `${apiOrigin.replace(/\/$/, "")}/api/core/tesseract/devices`;
79525
80162
  async function registerDeviceKey(opts) {
79526
80163
  try {
@@ -79532,7 +80169,8 @@ async function registerDeviceKey(opts) {
79532
80169
  identityPub: b64u(opts.publicRaw),
79533
80170
  alg: "P256-ECDSA",
79534
80171
  tlsFingerprint256: opts.tlsFingerprint256,
79535
- label: opts.label
80172
+ label: opts.label,
80173
+ ...opts.sealingPub ? { sealingPub: opts.sealingPub, sealingPubSig: opts.sealingPubSig } : {}
79536
80174
  })
79537
80175
  });
79538
80176
  return r.ok;
@@ -79569,12 +80207,584 @@ async function getPeerIdentityPub(opts) {
79569
80207
  }
79570
80208
  }
79571
80209
 
80210
+ // ../../packages/host-core/dist/drops/receive-ui.js
80211
+ import { basename as basename10, dirname as dirname21 } from "node:path";
80212
+ var log5 = (...a) => console.log("[drops]", ...a);
80213
+ function formatBytes(n) {
80214
+ if (!Number.isFinite(n) || n <= 0)
80215
+ return "0 B";
80216
+ const units = ["B", "KB", "MB", "GB", "TB"];
80217
+ const i = Math.min(units.length - 1, Math.floor(Math.log(n) / Math.log(1024)));
80218
+ const v = n / 1024 ** i;
80219
+ return `${i === 0 ? v : v.toFixed(v < 10 ? 1 : 0)} ${units[i]}`;
80220
+ }
80221
+ function initDropReceiveUi(deps = {}) {
80222
+ const live = /* @__PURE__ */ new Map();
80223
+ const clearActivity = () => {
80224
+ if (live.size === 0)
80225
+ setTrayActivity(null);
80226
+ };
80227
+ const describe3 = (e) => {
80228
+ const from = deps.hostLabel?.(e.fromHost) ?? "another machine";
80229
+ const what = e.singleFile || e.totalFiles === 1 ? basename10(e.destPath) : `${e.totalFiles} items`;
80230
+ return `${what} from ${from}`;
80231
+ };
80232
+ setInboundTransferObserver((e) => {
80233
+ try {
80234
+ if (e.phase === "begin") {
80235
+ live.set(e.transferId, { destPath: e.destPath, singleFile: e.singleFile, totalFiles: e.totalFiles, lastTrayAt: 0 });
80236
+ setTrayActivity(`receiving ${e.totalFiles === 1 ? "1 file" : `${e.totalFiles} files`}`);
80237
+ return;
80238
+ }
80239
+ const l = live.get(e.transferId);
80240
+ if (e.phase === "progress") {
80241
+ if (!l)
80242
+ return;
80243
+ const now2 = Date.now();
80244
+ if (now2 - l.lastTrayAt < 1e3)
80245
+ return;
80246
+ l.lastTrayAt = now2;
80247
+ const pct = e.totalBytes > 0 ? Math.min(99, Math.floor(e.bytesDone / e.totalBytes * 100)) : 0;
80248
+ setTrayActivity(`receiving ${formatBytes(e.totalBytes)} (${pct}%)`);
80249
+ return;
80250
+ }
80251
+ live.delete(e.transferId);
80252
+ clearActivity();
80253
+ if (e.phase === "error") {
80254
+ log5(`transfer ${e.transferId} failed: ${e.message ?? "unknown error"}`);
80255
+ if (deps.appIsUp?.())
80256
+ return;
80257
+ daemonNotify({ title: "Couldn't receive files", body: e.message ?? "The transfer failed.", silent: true });
80258
+ return;
80259
+ }
80260
+ const destPath = e.destPath || l?.destPath || "";
80261
+ if (!destPath)
80262
+ return;
80263
+ log5(`received ${e.totalFiles} item(s), ${formatBytes(e.bytesDone)} \u2192 ${destPath}`);
80264
+ if (deps.appIsUp?.())
80265
+ return;
80266
+ const reveal = () => {
80267
+ const el2 = electronModule();
80268
+ if (!el2)
80269
+ return;
80270
+ try {
80271
+ el2.shell.showItemInFolder(destPath);
80272
+ } catch (err2) {
80273
+ log5("reveal failed:", err2 instanceof Error ? err2.message : String(err2));
80274
+ }
80275
+ };
80276
+ const open5 = () => {
80277
+ const el2 = electronModule();
80278
+ if (!el2)
80279
+ return;
80280
+ void el2.shell.openPath(destPath).catch(() => {
80281
+ });
80282
+ };
80283
+ daemonNotify({
80284
+ title: "Files received",
80285
+ body: `${describe3(e)} \xB7 ${formatBytes(e.bytesDone)}`,
80286
+ actions: [{ id: "reveal", label: "Show in Finder" }, { id: "open", label: "Open" }],
80287
+ onAction: (id) => id === "open" ? open5() : reveal(),
80288
+ // Clicking the banner body is the only reliable action on macOS (buttons need
80289
+ // an expanded banner and are suppressed under Focus), so make it do the useful
80290
+ // thing rather than nothing.
80291
+ onClick: reveal
80292
+ });
80293
+ } catch (err2) {
80294
+ log5("receive-ui error:", err2 instanceof Error ? err2.message : String(err2));
80295
+ }
80296
+ });
80297
+ }
80298
+
80299
+ // ../../packages/host-core/dist/drops/handler.js
80300
+ import { basename as basename11 } from "node:path";
80301
+
80302
+ // ../../packages/host-core/dist/drops/paths.js
80303
+ init_base_paths();
80304
+ import { mkdir as mkdir11 } from "node:fs/promises";
80305
+ import { join as join46 } from "node:path";
80306
+
80307
+ // ../../packages/host-core/dist/drops/names.js
80308
+ var WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
80309
+ var CONTROL = /[\u0000-\u001f\u007f-\u009f]/g;
80310
+ var BIDI = /[\u200e\u200f\u202a-\u202e\u2066-\u2069]/g;
80311
+ var MAX_NAME_BYTES = 200;
80312
+ var MAX_EXT_BYTES = 32;
80313
+ var MAX_DEPTH = 32;
80314
+ var utf82 = new TextEncoder();
80315
+ var byteLen = (s) => utf82.encode(s).length;
80316
+ function capBytes(stem, ext, max) {
80317
+ if (byteLen(stem + ext) <= max)
80318
+ return stem + ext;
80319
+ const budget = Math.max(1, max - byteLen(ext));
80320
+ let out = "";
80321
+ let used = 0;
80322
+ for (const ch of stem) {
80323
+ const n = byteLen(ch);
80324
+ if (used + n > budget)
80325
+ break;
80326
+ out += ch;
80327
+ used += n;
80328
+ }
80329
+ return out + ext;
80330
+ }
80331
+ function sanitizeDropName(raw, fallback = "file") {
80332
+ let s = String(raw ?? "").split(/[/\\]/).pop() ?? "";
80333
+ s = s.replace(CONTROL, "").replace(BIDI, "").normalize("NFC");
80334
+ s = s.replace(/[ .]+$/g, "");
80335
+ s = s.replace(/^\.+/, "");
80336
+ s = s.trim();
80337
+ if (!s)
80338
+ return fallback;
80339
+ const dot = s.lastIndexOf(".");
80340
+ let stem = dot > 0 ? s.slice(0, dot) : s;
80341
+ let ext = dot > 0 ? s.slice(dot) : "";
80342
+ if (byteLen(ext) > MAX_EXT_BYTES) {
80343
+ stem = s;
80344
+ ext = "";
80345
+ }
80346
+ if (!stem)
80347
+ return fallback;
80348
+ if (WINDOWS_RESERVED.test(stem))
80349
+ stem = `_${stem}`;
80350
+ const out = capBytes(stem, ext, MAX_NAME_BYTES);
80351
+ return out || fallback;
80352
+ }
80353
+ function sanitizeDropRel(rel) {
80354
+ const parts = String(rel ?? "").split("/").filter((p) => p !== "");
80355
+ if (!parts.length || parts.length > MAX_DEPTH)
80356
+ return null;
80357
+ const out = [];
80358
+ for (const p of parts) {
80359
+ if (p === "." || p === "..")
80360
+ return null;
80361
+ const clean = sanitizeDropName(p, "");
80362
+ if (!clean)
80363
+ return null;
80364
+ out.push(clean);
80365
+ }
80366
+ return out.join("/");
80367
+ }
80368
+ async function uniqueDropPath(abs) {
80369
+ const { access } = await import("node:fs/promises");
80370
+ const exists2 = async (p) => access(p).then(() => true, () => false);
80371
+ if (!await exists2(abs))
80372
+ return abs;
80373
+ const dir2 = abs.slice(0, abs.length - basenameOf(abs).length);
80374
+ const name = basenameOf(abs);
80375
+ const dot = name.lastIndexOf(".");
80376
+ const stem = dot > 0 ? name.slice(0, dot) : name;
80377
+ const ext = dot > 0 ? name.slice(dot) : "";
80378
+ for (let i = 2; i < 1e4; i++) {
80379
+ const candidate = `${dir2}${stem} ${i}${ext}`;
80380
+ if (!await exists2(candidate))
80381
+ return candidate;
80382
+ }
80383
+ return `${dir2}${stem} ${Date.now()}${ext}`;
80384
+ }
80385
+ function basenameOf(p) {
80386
+ const i = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"));
80387
+ return i === -1 ? p : p.slice(i + 1);
80388
+ }
80389
+
80390
+ // ../../packages/host-core/dist/drops/paths.js
80391
+ function resolveDropsRoot() {
80392
+ const root = resolveHostRoot();
80393
+ const preferred = dropsDir();
80394
+ return isInsideRoot(preferred, root) ? preferred : join46(root, "Drops");
80395
+ }
80396
+ async function ensureDropsRoot() {
80397
+ const dir2 = resolveDropsRoot();
80398
+ await mkdir11(dir2, { recursive: true });
80399
+ return dir2;
80400
+ }
80401
+ async function ensureDropDir(opts) {
80402
+ const root = await ensureDropsRoot();
80403
+ if (opts.singleItem && !opts.sender)
80404
+ return root;
80405
+ const parts = [root];
80406
+ if (opts.sender)
80407
+ parts.push(sanitizeDropName(opts.sender, "Unknown sender"));
80408
+ if (!opts.singleItem && opts.label)
80409
+ parts.push(sanitizeDropName(opts.label, "Files"));
80410
+ const dir2 = join46(...parts);
80411
+ await mkdir11(dir2, { recursive: true });
80412
+ return dir2;
80413
+ }
80414
+
80415
+ // ../../packages/host-core/dist/drops/handler.js
80416
+ var log6 = (...a) => console.log("[drops]", ...a);
80417
+ var CONSENT_TIMEOUT_MS = 12e4;
80418
+ function createDropHandler(deps) {
80419
+ const pending2 = /* @__PURE__ */ new Map();
80420
+ const seen = /* @__PURE__ */ new Set();
80421
+ async function claim(offer) {
80422
+ const hostId = deps.ownHostId();
80423
+ if (!hostId) {
80424
+ log6(`drop ${offer.dropId}: no hostId yet, leaving it to another device`);
80425
+ return;
80426
+ }
80427
+ const singleItem = offer.totalFiles === 1;
80428
+ const destDir = await ensureDropDir({
80429
+ singleItem,
80430
+ // Cross-user drops are grouped by sender so "delete everything they sent me" is
80431
+ // one action. Safe only because the name is sanitized on the way in.
80432
+ sender: offer.kind === "peer" ? offer.from.email || offer.from.name : void 0,
80433
+ label: offer.label
80434
+ });
80435
+ let res;
80436
+ try {
80437
+ res = await deps.api(`/${offer.dropId}/claim`, {
80438
+ method: "POST",
80439
+ body: {
80440
+ deviceId: deps.deviceId(),
80441
+ hostId,
80442
+ // We run the transfer engine, so a LIVE drop is ours to take.
80443
+ caps: ["fs", "xfer"],
80444
+ destParent: destDir,
80445
+ destName: sanitizeDropName(singleItem ? basename11(offer.label) : offer.label, "Files")
80446
+ }
80447
+ });
80448
+ } catch (e) {
80449
+ log6(`drop ${offer.dropId}: claim failed:`, e instanceof Error ? e.message : String(e));
80450
+ return;
80451
+ }
80452
+ if (!res.won) {
80453
+ log6(`drop ${offer.dropId}: ${res.reason ?? "not available"}`);
80454
+ return;
80455
+ }
80456
+ if (res.transport === "blob" && deps.fetchBlobDrop) {
80457
+ try {
80458
+ await deps.fetchBlobDrop({
80459
+ dropId: offer.dropId,
80460
+ destDir,
80461
+ claim: res,
80462
+ sender: offer.kind === "peer" ? offer.from.email || offer.from.name : void 0
80463
+ });
80464
+ await deps.api(`/${offer.dropId}/complete`, { method: "POST", body: { ok: true } });
80465
+ } catch (e) {
80466
+ const message = e instanceof Error ? e.message : String(e);
80467
+ log6(`drop ${offer.dropId}: download failed:`, message);
80468
+ await deps.api(`/${offer.dropId}/complete`, { method: "POST", body: { ok: false, error: message } }).catch(() => {
80469
+ });
80470
+ }
80471
+ }
80472
+ }
80473
+ async function handleOffer(offer) {
80474
+ if (seen.has(offer.dropId))
80475
+ return;
80476
+ seen.add(offer.dropId);
80477
+ if (seen.size > 500)
80478
+ seen.clear();
80479
+ if (offer.autoAccept) {
80480
+ await claim(offer);
80481
+ return;
80482
+ }
80483
+ if (!deps.canPrompt()) {
80484
+ log6(`drop ${offer.dropId}: headless, staying silent`);
80485
+ return;
80486
+ }
80487
+ const ac = new AbortController();
80488
+ pending2.set(offer.dropId, ac);
80489
+ const timer = setTimeout(() => ac.abort(), CONSENT_TIMEOUT_MS);
80490
+ let accepted = false;
80491
+ try {
80492
+ accepted = await deps.confirm({
80493
+ title: "Accept files?",
80494
+ // Sender identity is the SERVER-RESOLVED one. Rendering a sender-supplied
80495
+ // display name here would make "IT Department ✓" a one-line phishing kit.
80496
+ // No thumbnails and no raw filenames: the offer rings every device the user
80497
+ // owns, so the preview is the harassment payload.
80498
+ body: `${offer.from.email || offer.from.name} wants to send you ${offer.totalFiles === 1 ? "1 file" : `${offer.totalFiles} files`} (${formatBytes(offer.totalBytes)}).`
80499
+ }, ac.signal);
80500
+ } finally {
80501
+ clearTimeout(timer);
80502
+ pending2.delete(offer.dropId);
80503
+ }
80504
+ if (ac.signal.aborted) {
80505
+ log6(`drop ${offer.dropId}: prompt withdrawn`);
80506
+ return;
80507
+ }
80508
+ if (!accepted) {
80509
+ await deps.api(`/${offer.dropId}/decline`, { method: "POST" }).catch(() => {
80510
+ });
80511
+ return;
80512
+ }
80513
+ await claim(offer);
80514
+ }
80515
+ return {
80516
+ onFrame(msg) {
80517
+ switch (msg.type) {
80518
+ case "drop:offer":
80519
+ void handleOffer(msg).catch((e) => log6("offer failed:", e instanceof Error ? e.message : String(e)));
80520
+ return;
80521
+ case "drop:dismiss":
80522
+ pending2.get(msg.dropId)?.abort();
80523
+ pending2.delete(msg.dropId);
80524
+ seen.add(msg.dropId);
80525
+ return;
80526
+ case "drop:claimed":
80527
+ if (msg.senderHostId && msg.senderHostId === deps.ownHostId())
80528
+ deps.startLiveSend?.(msg);
80529
+ return;
80530
+ default:
80531
+ return;
80532
+ }
80533
+ },
80534
+ /** Catches offers that arrived while this device was offline. */
80535
+ async reconcile() {
80536
+ try {
80537
+ const rows = await deps.api("/inbox");
80538
+ for (const r of rows) {
80539
+ if (r.status && r.status !== "PENDING")
80540
+ continue;
80541
+ void handleOffer({ ...r, type: "drop:offer" });
80542
+ }
80543
+ } catch (e) {
80544
+ log6("inbox reconcile failed:", e instanceof Error ? e.message : String(e));
80545
+ }
80546
+ }
80547
+ };
80548
+ }
80549
+
80550
+ // ../../packages/host-core/dist/drops/blob-fetch.js
80551
+ import { createWriteStream as createWriteStream2 } from "node:fs";
80552
+ import { mkdir as mkdir12, rm as rm4, chmod as chmod3 } from "node:fs/promises";
80553
+ import { dirname as dirname22, join as join47 } from "node:path";
80554
+ import { Readable as Readable2 } from "node:stream";
80555
+ import { pipeline } from "node:stream/promises";
80556
+
80557
+ // ../../packages/host-core/dist/drops/crypto.js
80558
+ import { Transform } from "node:stream";
80559
+ var MAGIC = Buffer.from("TSD1", "ascii");
80560
+ var NONCE_BYTES = 12;
80561
+ var HEADER_BYTES = MAGIC.length + NONCE_BYTES;
80562
+ var FRAME_BYTES = 1024 * 1024;
80563
+ var TAG_BYTES = 16;
80564
+ var MAX_FRAME_CIPHERTEXT = FRAME_BYTES + TAG_BYTES + 1024;
80565
+ var subtle3 = () => globalThis.crypto.subtle;
80566
+ var bs3 = (b) => b;
80567
+ async function importKey(raw) {
80568
+ return subtle3().importKey("raw", bs3(raw), { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
80569
+ }
80570
+ function nonceFor(base, index) {
80571
+ const n = Buffer.from(base);
80572
+ n[8] ^= index >>> 24 & 255;
80573
+ n[9] ^= index >>> 16 & 255;
80574
+ n[10] ^= index >>> 8 & 255;
80575
+ n[11] ^= index & 255;
80576
+ return n;
80577
+ }
80578
+ function aadFor(index) {
80579
+ const a = Buffer.alloc(4);
80580
+ a.writeUInt32BE(index >>> 0, 0);
80581
+ return a;
80582
+ }
80583
+ var ByteQueue = class {
80584
+ chunks = [];
80585
+ length = 0;
80586
+ push(b) {
80587
+ if (b.length) {
80588
+ this.chunks.push(b);
80589
+ this.length += b.length;
80590
+ }
80591
+ }
80592
+ /** The first `n` bytes, or null when we don't have them yet. Does not consume. */
80593
+ peek(n) {
80594
+ if (this.length < n)
80595
+ return null;
80596
+ const first = this.chunks[0];
80597
+ if (first.length >= n)
80598
+ return first.subarray(0, n);
80599
+ const out = Buffer.allocUnsafe(n);
80600
+ let off = 0;
80601
+ for (const c of this.chunks) {
80602
+ const take = Math.min(c.length, n - off);
80603
+ c.copy(out, off, 0, take);
80604
+ off += take;
80605
+ if (off === n)
80606
+ break;
80607
+ }
80608
+ return out;
80609
+ }
80610
+ consume(n) {
80611
+ let left = n;
80612
+ while (left > 0) {
80613
+ const c = this.chunks[0];
80614
+ if (c.length <= left) {
80615
+ left -= c.length;
80616
+ this.length -= c.length;
80617
+ this.chunks.shift();
80618
+ } else {
80619
+ this.chunks[0] = c.subarray(left);
80620
+ this.length -= left;
80621
+ left = 0;
80622
+ }
80623
+ }
80624
+ }
80625
+ };
80626
+ function decryptStream(source, keyRaw) {
80627
+ let key2 = null;
80628
+ let index = 0;
80629
+ let base = null;
80630
+ const q = new ByteQueue();
80631
+ let sawEnd = false;
80632
+ const t = new Transform({
80633
+ async transform(chunk, _enc, cb) {
80634
+ try {
80635
+ q.push(chunk);
80636
+ if (!base) {
80637
+ const head = q.peek(HEADER_BYTES);
80638
+ if (!head) {
80639
+ cb();
80640
+ return;
80641
+ }
80642
+ if (!head.subarray(0, MAGIC.length).equals(MAGIC))
80643
+ throw new Error("not an encrypted drop");
80644
+ base = Buffer.from(head.subarray(MAGIC.length, HEADER_BYTES));
80645
+ q.consume(HEADER_BYTES);
80646
+ key2 = await importKey(keyRaw);
80647
+ }
80648
+ while (!sawEnd) {
80649
+ const lenBuf = q.peek(4);
80650
+ if (!lenBuf)
80651
+ break;
80652
+ const len = lenBuf.readUInt32BE(0);
80653
+ if (len === 0) {
80654
+ sawEnd = true;
80655
+ q.consume(4);
80656
+ break;
80657
+ }
80658
+ if (len > MAX_FRAME_CIPHERTEXT)
80659
+ throw new Error("encrypted frame is implausibly large");
80660
+ const frame = q.peek(4 + len);
80661
+ if (!frame)
80662
+ break;
80663
+ const plain = Buffer.from(await subtle3().decrypt({ name: "AES-GCM", iv: bs3(nonceFor(base, index)), additionalData: bs3(aadFor(index)) }, key2, bs3(frame.subarray(4))));
80664
+ this.push(plain);
80665
+ index += 1;
80666
+ q.consume(4 + len);
80667
+ }
80668
+ cb();
80669
+ } catch (e) {
80670
+ cb(e);
80671
+ }
80672
+ },
80673
+ flush(cb) {
80674
+ cb(sawEnd ? null : new Error("the transfer ended early (incomplete or tampered)"));
80675
+ }
80676
+ });
80677
+ source.on("error", (e) => t.destroy(e));
80678
+ source.pipe(t);
80679
+ return t;
80680
+ }
80681
+
80682
+ // ../../packages/host-core/dist/drops/quarantine.js
80683
+ import { execFile as execFile5 } from "node:child_process";
80684
+ import { writeFile as writeFile10 } from "node:fs/promises";
80685
+ import { promisify as promisify4 } from "node:util";
80686
+ var exec = promisify4(execFile5);
80687
+ async function markQuarantined(path6, sourceLabel) {
80688
+ try {
80689
+ if (process.platform === "darwin") {
80690
+ const stamp2 = Math.floor(Date.now() / 1e3).toString(16);
80691
+ const agent = sourceLabel.replace(/[^\w.@-]/g, "_").slice(0, 64) || "Tesseract";
80692
+ await exec("xattr", ["-w", "com.apple.quarantine", `0081;${stamp2};Tesseract;${agent}`, path6]);
80693
+ return;
80694
+ }
80695
+ if (process.platform === "win32") {
80696
+ await writeFile10(`${path6}:Zone.Identifier`, "[ZoneTransfer]\r\nZoneId=3\r\n", "utf-8");
80697
+ }
80698
+ } catch {
80699
+ }
80700
+ }
80701
+
80702
+ // ../../packages/host-core/dist/drops/blob-fetch.js
80703
+ var log7 = (...a) => console.log("[drops]", ...a);
80704
+ async function fetchBlobDrop(opts) {
80705
+ const fetchFn = opts.fetchImpl ?? fetch;
80706
+ const written = [];
80707
+ let contentKey;
80708
+ if (opts.sealedKey) {
80709
+ if (!opts.sealingPrivate)
80710
+ throw new Error("This device has no sealing key, so it cannot open the transfer.");
80711
+ const open5 = opts.openSeal ?? openFromPrivateKey;
80712
+ contentKey = await open5(await opts.sealingPrivate(), opts.sealedKey, new TextEncoder().encode(opts.dropId));
80713
+ }
80714
+ const totalBytes = opts.items.reduce((n, i) => n + (i.kind === "file" ? i.size : 0), 0);
80715
+ let bytesDone = 0;
80716
+ try {
80717
+ for (const item of opts.items) {
80718
+ if (item.kind !== "dir")
80719
+ continue;
80720
+ const rel = sanitizeDropRel(item.rel);
80721
+ if (!rel) {
80722
+ log7(`drop ${opts.dropId}: refusing directory "${item.rel}"`);
80723
+ continue;
80724
+ }
80725
+ const abs = join47(opts.destDir, rel);
80726
+ if (!isInsideRoot(abs, opts.destDir)) {
80727
+ log7(`drop ${opts.dropId}: refusing directory outside the destination`);
80728
+ continue;
80729
+ }
80730
+ await mkdir12(abs, { recursive: true });
80731
+ }
80732
+ for (const item of opts.items) {
80733
+ if (opts.signal?.aborted)
80734
+ throw new Error("Cancelled.");
80735
+ if (item.kind !== "file")
80736
+ continue;
80737
+ if (!item.url) {
80738
+ log7(`drop ${opts.dropId}: item ${item.itemId} has no bytes, skipping`);
80739
+ continue;
80740
+ }
80741
+ const rel = sanitizeDropRel(item.rel);
80742
+ if (!rel) {
80743
+ log7(`drop ${opts.dropId}: refusing "${item.rel}"`);
80744
+ continue;
80745
+ }
80746
+ let abs = join47(opts.destDir, rel);
80747
+ if (!isInsideRoot(abs, opts.destDir)) {
80748
+ log7(`drop ${opts.dropId}: refusing a path outside the destination`);
80749
+ continue;
80750
+ }
80751
+ await mkdir12(dirname22(abs), { recursive: true });
80752
+ abs = await uniqueDropPath(abs);
80753
+ const res = await fetchFn(new URL(item.url, opts.apiOrigin).toString(), { signal: opts.signal });
80754
+ if (!res.ok || !res.body)
80755
+ throw new Error(`Download failed (${res.status}).`);
80756
+ const body = Readable2.fromWeb(res.body);
80757
+ const plain = contentKey ? decryptStream(body, contentKey) : body;
80758
+ try {
80759
+ await pipeline(plain, createWriteStream2(abs));
80760
+ } catch (e) {
80761
+ await rm4(abs, { force: true }).catch(() => {
80762
+ });
80763
+ throw e;
80764
+ }
80765
+ await chmod3(abs, 420).catch(() => {
80766
+ });
80767
+ if (opts.sender)
80768
+ await markQuarantined(abs, opts.sender);
80769
+ written.push(abs);
80770
+ bytesDone += item.size;
80771
+ opts.onProgress?.(bytesDone, totalBytes);
80772
+ }
80773
+ } catch (e) {
80774
+ for (const p of written)
80775
+ await rm4(p, { force: true }).catch(() => {
80776
+ });
80777
+ throw e;
80778
+ }
80779
+ return { written };
80780
+ }
80781
+
79572
80782
  // ../../packages/host-core/dist/chat-agent.js
79573
- import { randomUUID as randomUUID16 } from "node:crypto";
80783
+ import { randomUUID as randomUUID17 } from "node:crypto";
79574
80784
  var TURN_TIMEOUT_MS = 4 * 6e4;
79575
80785
  var PARTIAL_INTERVAL_MS = 700;
79576
80786
  function createChatAgentRunner(deps) {
79577
- const log5 = deps.log ?? (() => {
80787
+ const log9 = deps.log ?? (() => {
79578
80788
  });
79579
80789
  const active = /* @__PURE__ */ new Set();
79580
80790
  async function api(path6, init) {
@@ -79598,7 +80808,7 @@ function createChatAgentRunner(deps) {
79598
80808
  }
79599
80809
  async function runTurn(prompt, onPartial) {
79600
80810
  const backend2 = await getSharedBackend();
79601
- const clientRequestId = `chat-agent-${randomUUID16()}`;
80811
+ const clientRequestId = `chat-agent-${randomUUID17()}`;
79602
80812
  return new Promise((resolve12, reject) => {
79603
80813
  let text8 = "";
79604
80814
  let settled = false;
@@ -79662,7 +80872,7 @@ function createChatAgentRunner(deps) {
79662
80872
  body: { deviceId: deps.deviceId }
79663
80873
  });
79664
80874
  if (!claimed) {
79665
- log5("chat-agent: job already claimed elsewhere", event.jobId);
80875
+ log9("chat-agent: job already claimed elsewhere", event.jobId);
79666
80876
  return;
79667
80877
  }
79668
80878
  const ctx2 = await api(`/api/core/tesseract/chat/agent-jobs/${event.jobId}/context`);
@@ -79686,10 +80896,10 @@ function createChatAgentRunner(deps) {
79686
80896
  await api(`/api/core/tesseract/chat/agent-jobs/${event.jobId}/reply`, { method: "POST", body: { markdown: trimmed } });
79687
80897
  }
79688
80898
  await api(`/api/core/tesseract/chat/agent-jobs/${event.jobId}/complete`, { method: "POST", body: {} });
79689
- log5("chat-agent: answered", event.jobId);
80899
+ log9("chat-agent: answered", event.jobId);
79690
80900
  } catch (err2) {
79691
80901
  const message = err2 instanceof Error ? err2.message : String(err2);
79692
- log5("chat-agent: job failed", event.jobId, message);
80902
+ log9("chat-agent: job failed", event.jobId, message);
79693
80903
  await api(`/api/core/tesseract/chat/agent-jobs/${event.jobId}/complete`, {
79694
80904
  method: "POST",
79695
80905
  body: { error: message }
@@ -79879,9 +81089,9 @@ function registerRdpEngine() {
79879
81089
 
79880
81090
  // ../../packages/host-core/dist/rdesk/audit.js
79881
81091
  import { appendFileSync as appendFileSync2 } from "node:fs";
79882
- import { join as join45 } from "node:path";
81092
+ import { join as join48 } from "node:path";
79883
81093
  function rdeskAuditPath() {
79884
- return join45(daemonDir2(), "rdesk-audit.jsonl");
81094
+ return join48(daemonDir2(), "rdesk-audit.jsonl");
79885
81095
  }
79886
81096
  function appendRdeskAudit(e) {
79887
81097
  try {
@@ -79892,10 +81102,10 @@ function appendRdeskAudit(e) {
79892
81102
 
79893
81103
  // ../../packages/host-core/dist/rdesk/password.js
79894
81104
  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";
81105
+ import { readFileSync as readFileSync29, writeFileSync as writeFileSync21, existsSync as existsSync24, unlinkSync as unlinkSync3 } from "node:fs";
81106
+ import { join as join49 } from "node:path";
79897
81107
  function pwFile() {
79898
- return join46(daemonDir2(), "rdesk-password.json");
81108
+ return join49(daemonDir2(), "rdesk-password.json");
79899
81109
  }
79900
81110
  function verifyRdeskPassword(password) {
79901
81111
  if (!password)
@@ -79992,26 +81202,26 @@ function broadcastFileProviderHostsChanged() {
79992
81202
  }
79993
81203
  var BRIDGE_PORTS = [47821, 47822, 47823, 47824, 47825, 47826, 47827, 47828];
79994
81204
  var BRIDGE_TOKEN = "b7c1f9a4e25d4c3a8f06e1d72b9a3c5e";
79995
- function startFileProviderBridge(log5 = () => {
81205
+ function startFileProviderBridge(log9 = () => {
79996
81206
  }) {
79997
81207
  if (process.platform !== "darwin" || wss)
79998
81208
  return;
79999
81209
  stopping = false;
80000
81210
  token = BRIDGE_TOKEN;
80001
- bindBridge(0, log5);
81211
+ bindBridge(0, log9);
80002
81212
  }
80003
- function bindBridge(i, log5) {
81213
+ function bindBridge(i, log9) {
80004
81214
  if (stopping || wss)
80005
81215
  return;
80006
81216
  if (i >= BRIDGE_PORTS.length) {
80007
- log5("fileprovider: no free bridge port in the fixed set");
81217
+ log9("fileprovider: no free bridge port in the fixed set");
80008
81218
  return;
80009
81219
  }
80010
81220
  const port = BRIDGE_PORTS[i];
80011
81221
  const server = new import_websocket_server.default({ host: "127.0.0.1", port });
80012
81222
  server.on("listening", () => {
80013
81223
  wss = server;
80014
- log5(`fileprovider bridge listening on 127.0.0.1:${port}`);
81224
+ log9(`fileprovider bridge listening on 127.0.0.1:${port}`);
80015
81225
  });
80016
81226
  server.on("connection", (ws, req) => {
80017
81227
  let ok = false;
@@ -80037,9 +81247,9 @@ function bindBridge(i, log5) {
80037
81247
  server.close();
80038
81248
  } catch {
80039
81249
  }
80040
- bindBridge(i + 1, log5);
81250
+ bindBridge(i + 1, log9);
80041
81251
  } else
80042
- log5("fileprovider bridge error", e?.message);
81252
+ log9("fileprovider bridge error", e?.message);
80043
81253
  });
80044
81254
  }
80045
81255
 
@@ -80047,12 +81257,12 @@ function bindBridge(i, log5) {
80047
81257
  import { randomBytes as randomBytes7 } from "node:crypto";
80048
81258
  import { existsSync as existsSync25, mkdirSync as mkdirSync23, writeFileSync as writeFileSync22, rmSync as rmSync7 } from "node:fs";
80049
81259
  import { homedir as homedir6 } from "node:os";
80050
- import { join as join47 } from "node:path";
81260
+ import { join as join50 } from "node:path";
80051
81261
 
80052
81262
  // ../../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);
81263
+ import { execFile as execFile6 } from "node:child_process";
81264
+ import { promisify as promisify5 } from "node:util";
81265
+ var run2 = promisify5(execFile6);
80056
81266
  var APPEX_IDENTIFIER = "dev.tesseract.AutoFill";
80057
81267
  async function teamIdOf(path6) {
80058
81268
  try {
@@ -80109,13 +81319,13 @@ async function isTrustedAutofillPeer(remotePort) {
80109
81319
  var APPEX_BUNDLE_ID = "dev.tesseract.AutoFill";
80110
81320
  var BRIDGE_PORTS2 = [17872, 17873, 17874, 17875];
80111
81321
  function appexContainerData() {
80112
- return join47(homedir6(), "Library", "Containers", APPEX_BUNDLE_ID, "Data");
81322
+ return join50(homedir6(), "Library", "Containers", APPEX_BUNDLE_ID, "Data");
80113
81323
  }
80114
81324
  function bridgeInfoPath() {
80115
- return join47(appexContainerData(), "Library", "Application Support", "tesseract-autofill-bridge.json");
81325
+ return join50(appexContainerData(), "Library", "Application Support", "tesseract-autofill-bridge.json");
80116
81326
  }
80117
81327
  var infoLoggedOnce = false;
80118
- function writeBridgeInfo(port, token3, log5) {
81328
+ function writeBridgeInfo(port, token3, log9) {
80119
81329
  if (process.platform !== "darwin")
80120
81330
  return;
80121
81331
  const containerData = appexContainerData();
@@ -80123,21 +81333,21 @@ function writeBridgeInfo(port, token3, log5) {
80123
81333
  if (!existsSync25(containerData)) {
80124
81334
  if (!infoLoggedOnce) {
80125
81335
  infoLoggedOnce = true;
80126
- log5("autofill bridge-info SKIP: no appex container yet at", containerData);
81336
+ log9("autofill bridge-info SKIP: no appex container yet at", containerData);
80127
81337
  }
80128
81338
  return;
80129
81339
  }
80130
- const dir2 = join47(containerData, "Library", "Application Support");
81340
+ const dir2 = join50(containerData, "Library", "Application Support");
80131
81341
  mkdirSync23(dir2, { recursive: true });
80132
81342
  writeFileSync22(bridgeInfoPath(), JSON.stringify({ port, token: token3, pid: process.pid, ts: Date.now() }), { mode: 384 });
80133
81343
  if (!infoLoggedOnce) {
80134
81344
  infoLoggedOnce = true;
80135
- log5("autofill bridge-info WROTE", port, "\u2192", dir2);
81345
+ log9("autofill bridge-info WROTE", port, "\u2192", dir2);
80136
81346
  }
80137
81347
  } catch (e) {
80138
81348
  if (!infoLoggedOnce) {
80139
81349
  infoLoggedOnce = true;
80140
- log5("autofill bridge-info FAILED:", e?.message);
81350
+ log9("autofill bridge-info FAILED:", e?.message);
80141
81351
  }
80142
81352
  }
80143
81353
  }
@@ -80229,20 +81439,20 @@ var token2 = "";
80229
81439
  var infoTimer = null;
80230
81440
  var stopping2 = false;
80231
81441
  var sockets2 = /* @__PURE__ */ new Set();
80232
- function bind(i, log5) {
81442
+ function bind(i, log9) {
80233
81443
  if (stopping2 || wss2)
80234
81444
  return;
80235
81445
  if (i >= BRIDGE_PORTS2.length) {
80236
- log5("autofill: no free bridge port in the fixed set");
81446
+ log9("autofill: no free bridge port in the fixed set");
80237
81447
  return;
80238
81448
  }
80239
81449
  const port = BRIDGE_PORTS2[i];
80240
81450
  const server = new import_websocket_server.default({ host: "127.0.0.1", port });
80241
81451
  server.on("listening", () => {
80242
81452
  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);
81453
+ log9(`autofill bridge listening on 127.0.0.1:${port}`);
81454
+ writeBridgeInfo(port, token2, log9);
81455
+ infoTimer = setInterval(() => writeBridgeInfo(port, token2, log9), 15e3);
80246
81456
  infoTimer.unref?.();
80247
81457
  });
80248
81458
  server.on("connection", (ws, req) => {
@@ -80270,18 +81480,18 @@ function bind(i, log5) {
80270
81480
  server.close();
80271
81481
  } catch {
80272
81482
  }
80273
- bind(i + 1, log5);
81483
+ bind(i + 1, log9);
80274
81484
  } else
80275
- log5("autofill bridge error", e?.message);
81485
+ log9("autofill bridge error", e?.message);
80276
81486
  });
80277
81487
  }
80278
- function startAutofillBridge(log5 = () => {
81488
+ function startAutofillBridge(log9 = () => {
80279
81489
  }) {
80280
81490
  if (process.platform !== "darwin" || wss2)
80281
81491
  return;
80282
81492
  stopping2 = false;
80283
81493
  token2 = randomBytes7(24).toString("base64url");
80284
- bind(0, log5);
81494
+ bind(0, log9);
80285
81495
  }
80286
81496
 
80287
81497
  // ../../packages/host-core/dist/power-node.js
@@ -80345,7 +81555,7 @@ function createNodePowerController() {
80345
81555
  import { execFileSync as execFileSync3 } from "node:child_process";
80346
81556
  import { chmodSync as chmodSync4, existsSync as existsSync26, mkdirSync as mkdirSync24, writeFileSync as writeFileSync23 } from "node:fs";
80347
81557
  import { homedir as homedir7, platform as platform2 } from "node:os";
80348
- import { dirname as dirname21, join as join48, resolve as resolve10 } from "node:path";
81558
+ import { dirname as dirname23, join as join51, resolve as resolve10 } from "node:path";
80349
81559
  import { fileURLToPath as fileURLToPath3 } from "node:url";
80350
81560
  var HOST_NAME = "com.tesseract.companion";
80351
81561
  var EXTENSION_IDS = ["icpnghhpcmaonjckkpilpajblfehpplo", "giolfpookdhjfafcchaflhmigghaeffn"];
@@ -80354,21 +81564,21 @@ function browserProfileDirs() {
80354
81564
  const home = homedir7();
80355
81565
  const p = platform2();
80356
81566
  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));
81567
+ const base = join51(home, "Library/Application Support");
81568
+ 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
81569
  }
80360
81570
  if (p === "win32")
80361
81571
  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));
81572
+ const cfg2 = join51(home, ".config");
81573
+ return ["google-chrome", "google-chrome-beta", "chromium", "microsoft-edge", "BraveSoftware/Brave-Browser", "vivaldi"].map((b) => join51(cfg2, b));
80364
81574
  }
80365
- function resolveBridge(log5) {
81575
+ function resolveBridge(log9) {
80366
81576
  const candidates = [
80367
81577
  process.env.CCB_COMPANION_HOST_JS,
80368
- process.resourcesPath ? join48(process.resourcesPath, "companion-host", BRIDGE_BASENAME) : void 0
81578
+ process.resourcesPath ? join51(process.resourcesPath, "companion-host", BRIDGE_BASENAME) : void 0
80369
81579
  ];
80370
81580
  try {
80371
- const here = dirname21(fileURLToPath3(import.meta.url));
81581
+ const here = dirname23(fileURLToPath3(import.meta.url));
80372
81582
  candidates.push(resolve10(here, "../../../apps/companion-host/dist", BRIDGE_BASENAME));
80373
81583
  candidates.push(resolve10(here, "../../apps/companion-host/dist", BRIDGE_BASENAME));
80374
81584
  } catch {
@@ -80376,12 +81586,12 @@ function resolveBridge(log5) {
80376
81586
  for (const c of candidates)
80377
81587
  if (c && existsSync26(c))
80378
81588
  return c;
80379
- log5("companion native-host: bridge not found; skipping registration (looked in", candidates.filter(Boolean).join(" , "), ")");
81589
+ log9("companion native-host: bridge not found; skipping registration (looked in", candidates.filter(Boolean).join(" , "), ")");
80380
81590
  return null;
80381
81591
  }
80382
81592
  function writeLauncher(daemonDir3, bridge, runtime) {
80383
81593
  const isWin = platform2() === "win32";
80384
- const launcher = join48(daemonDir3, isWin ? "companion-host.cmd" : "companion-host.sh");
81594
+ const launcher = join51(daemonDir3, isWin ? "companion-host.cmd" : "companion-host.sh");
80385
81595
  if (isWin) {
80386
81596
  writeFileSync23(launcher, `@echo off\r
80387
81597
  set ELECTRON_RUN_AS_NODE=1\r
@@ -80397,9 +81607,9 @@ ELECTRON_RUN_AS_NODE=1 exec "${runtime}" "${bridge}" "$@"
80397
81607
  return launcher;
80398
81608
  }
80399
81609
  function registerNativeHost(opts) {
80400
- const log5 = opts.log ?? (() => void 0);
81610
+ const log9 = opts.log ?? (() => void 0);
80401
81611
  try {
80402
- const bridge = resolveBridge(log5);
81612
+ const bridge = resolveBridge(log9);
80403
81613
  if (!bridge)
80404
81614
  return;
80405
81615
  const runtime = opts.runtime ?? process.execPath;
@@ -80413,27 +81623,27 @@ function registerNativeHost(opts) {
80413
81623
  allowed_origins: EXTENSION_IDS.map((id) => `chrome-extension://${id}/`)
80414
81624
  }, null, 2);
80415
81625
  if (platform2() === "win32")
80416
- return registerWindows(opts.daemonDir, manifestJson, log5);
81626
+ return registerWindows(opts.daemonDir, manifestJson, log9);
80417
81627
  let wrote = 0;
80418
81628
  for (const profile of browserProfileDirs()) {
80419
81629
  if (!existsSync26(profile))
80420
81630
  continue;
80421
81631
  try {
80422
- const dir2 = join48(profile, "NativeMessagingHosts");
81632
+ const dir2 = join51(profile, "NativeMessagingHosts");
80423
81633
  mkdirSync24(dir2, { recursive: true });
80424
- writeFileSync23(join48(dir2, `${HOST_NAME}.json`), manifestJson);
81634
+ writeFileSync23(join51(dir2, `${HOST_NAME}.json`), manifestJson);
80425
81635
  wrote++;
80426
81636
  } catch {
80427
81637
  }
80428
81638
  }
80429
- log5(`companion native-host: registered for ${wrote} browser(s) \u2192 ${launcher}`);
81639
+ log9(`companion native-host: registered for ${wrote} browser(s) \u2192 ${launcher}`);
80430
81640
  } catch (e) {
80431
- log5("companion native-host: registration failed", e.message);
81641
+ log9("companion native-host: registration failed", e.message);
80432
81642
  }
80433
81643
  }
80434
- function registerWindows(daemonDir3, manifestJson, log5) {
81644
+ function registerWindows(daemonDir3, manifestJson, log9) {
80435
81645
  try {
80436
- const manifestPath = join48(daemonDir3, `${HOST_NAME}.json`);
81646
+ const manifestPath = join51(daemonDir3, `${HOST_NAME}.json`);
80437
81647
  writeFileSync23(manifestPath, manifestJson);
80438
81648
  for (const root of ["Software\\Google\\Chrome", "Software\\Microsoft\\Edge", "Software\\BraveSoftware\\Brave-Browser"]) {
80439
81649
  try {
@@ -80441,17 +81651,17 @@ function registerWindows(daemonDir3, manifestJson, log5) {
80441
81651
  } catch {
80442
81652
  }
80443
81653
  }
80444
- log5("companion native-host: registered (Windows registry)");
81654
+ log9("companion native-host: registered (Windows registry)");
80445
81655
  } catch (e) {
80446
- log5("companion native-host: Windows registration failed", e.message);
81656
+ log9("companion native-host: Windows registration failed", e.message);
80447
81657
  }
80448
81658
  }
80449
81659
 
80450
81660
  // ../../packages/host-core/dist/activity/activity-tracker.js
80451
81661
  import { promises as fsp, writeFileSync as writeFileSync24, mkdirSync as mkdirSync25, existsSync as existsSync27 } from "node:fs";
80452
- import { join as join49 } from "node:path";
81662
+ import { join as join52 } from "node:path";
80453
81663
  function activityProfilePath() {
80454
- return join49(daemonDir2(), "activity-profile.json");
81664
+ return join52(daemonDir2(), "activity-profile.json");
80455
81665
  }
80456
81666
  var MAX_APPS = 300;
80457
81667
  var MAX_AGE_DAYS = 120;
@@ -80494,7 +81704,7 @@ function loadMacAx2() {
80494
81704
  function createActivityTracker(opts = {}) {
80495
81705
  const intervalMs = opts.intervalMs ?? 5e3;
80496
81706
  const flushEvery = opts.flushEvery ?? 12;
80497
- const log5 = opts.log ?? (() => {
81707
+ const log9 = opts.log ?? (() => {
80498
81708
  });
80499
81709
  let timer = null;
80500
81710
  let profile = null;
@@ -80512,10 +81722,10 @@ function createActivityTracker(opts = {}) {
80512
81722
  try {
80513
81723
  await fsp.mkdir(daemonDir2(), { recursive: true, mode: 448 });
80514
81724
  await fsp.writeFile(activityProfilePath(), JSON.stringify(profile), { mode: 384 });
80515
- log5(`activity flushed (${Object.keys(profile.apps).length} apps tracked)`);
81725
+ log9(`activity flushed (${Object.keys(profile.apps).length} apps tracked)`);
80516
81726
  dirty = 0;
80517
81727
  } catch (e) {
80518
- log5("activity flush failed", e instanceof Error ? e.message : String(e));
81728
+ log9("activity flush failed", e instanceof Error ? e.message : String(e));
80519
81729
  }
80520
81730
  }
80521
81731
  async function sample() {
@@ -80527,7 +81737,7 @@ function createActivityTracker(opts = {}) {
80527
81737
  const key2 = front.bundleId || front.name;
80528
81738
  await ensureLoaded();
80529
81739
  if (profile && Object.keys(profile.apps).length && !existsSync27(activityProfilePath())) {
80530
- log5("activity profile cleared externally \u2014 resetting in-memory");
81740
+ log9("activity profile cleared externally \u2014 resetting in-memory");
80531
81741
  profile = { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), apps: {} };
80532
81742
  }
80533
81743
  const p = profile;
@@ -80541,14 +81751,14 @@ function createActivityTracker(opts = {}) {
80541
81751
  if (++dirty >= flushEvery)
80542
81752
  await flush2();
80543
81753
  } catch (e) {
80544
- log5("activity sample failed", e instanceof Error ? e.message : String(e));
81754
+ log9("activity sample failed", e instanceof Error ? e.message : String(e));
80545
81755
  }
80546
81756
  }
80547
81757
  return {
80548
81758
  start() {
80549
81759
  if (timer)
80550
81760
  return;
80551
- log5("activity tracking ON (local-only)");
81761
+ log9("activity tracking ON (local-only)");
80552
81762
  timer = setInterval(() => {
80553
81763
  void sample();
80554
81764
  }, intervalMs);
@@ -80567,9 +81777,9 @@ function createActivityTracker(opts = {}) {
80567
81777
  dirty = 0;
80568
81778
  }
80569
81779
  } catch (e) {
80570
- log5("activity final flush failed", e instanceof Error ? e.message : String(e));
81780
+ log9("activity final flush failed", e instanceof Error ? e.message : String(e));
80571
81781
  }
80572
- log5("activity tracking OFF");
81782
+ log9("activity tracking OFF");
80573
81783
  },
80574
81784
  running() {
80575
81785
  return timer !== null;
@@ -80596,16 +81806,68 @@ function getRdeskClientWebContents() {
80596
81806
  } };
80597
81807
  }
80598
81808
 
81809
+ // ../../packages/host-core/dist/gui/rdesk-call-window.js
81810
+ import { join as join53 } from "node:path";
81811
+ var win2 = null;
81812
+ function showRdeskCallWindow(opts) {
81813
+ const el2 = electronModule();
81814
+ if (!el2?.BrowserWindow)
81815
+ return;
81816
+ if (win2 && !win2.isDestroyed()) {
81817
+ try {
81818
+ win2.focus();
81819
+ } catch {
81820
+ }
81821
+ return;
81822
+ }
81823
+ const w = new el2.BrowserWindow({
81824
+ width: 380,
81825
+ height: 320,
81826
+ resizable: true,
81827
+ minimizable: true,
81828
+ fullscreenable: false,
81829
+ alwaysOnTop: true,
81830
+ skipTaskbar: false,
81831
+ title: "Remote desktop \u2014 call",
81832
+ backgroundColor: "#15151a",
81833
+ webPreferences: { contextIsolation: true, sandbox: false }
81834
+ });
81835
+ try {
81836
+ w.setAlwaysOnTop(true, "floating");
81837
+ } catch {
81838
+ }
81839
+ w.on("closed", () => {
81840
+ win2 = null;
81841
+ try {
81842
+ opts.onClosed?.();
81843
+ } catch {
81844
+ }
81845
+ });
81846
+ const loadFile = w.loadFile;
81847
+ 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));
81848
+ win2 = w;
81849
+ }
81850
+ function closeRdeskCallWindow() {
81851
+ const w = win2;
81852
+ win2 = null;
81853
+ if (w && !w.isDestroyed()) {
81854
+ try {
81855
+ w.close();
81856
+ } catch {
81857
+ }
81858
+ }
81859
+ }
81860
+
80599
81861
  // ../../packages/host-core/dist/daemon-runtime.js
80600
81862
  var daemonClipboardSync = null;
80601
81863
  var REFRESH_INTERVAL_MS = 12 * 60 * 60 * 1e3;
80602
81864
  async function runDaemon(opts = {}) {
80603
81865
  const VERSION = opts.version ?? process.env.CCB_DAEMON_VERSION ?? "dev";
80604
81866
  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");
81867
+ const log9 = (...args) => console.log(`[${TAG} ${(/* @__PURE__ */ new Date()).toISOString()}]`, ...args);
81868
+ log9("paths", JSON.stringify(sealBasePaths()));
81869
+ const pidfile = () => join54(daemonDir2(), "daemon.lock");
81870
+ const statusFile = () => join54(daemonDir2(), "status.json");
80609
81871
  const isAlive2 = (pid) => {
80610
81872
  try {
80611
81873
  process.kill(pid, 0);
@@ -80632,7 +81894,7 @@ async function runDaemon(opts = {}) {
80632
81894
  return false;
80633
81895
  if (prev !== process.pid && isAlive2(prev))
80634
81896
  return false;
80635
- unlinkSync3(f);
81897
+ unlinkSync4(f);
80636
81898
  } catch {
80637
81899
  return false;
80638
81900
  }
@@ -80644,7 +81906,7 @@ async function runDaemon(opts = {}) {
80644
81906
  try {
80645
81907
  const f = pidfile();
80646
81908
  if (existsSync28(f) && Number(readFileSync30(f, "utf-8").trim()) === process.pid)
80647
- unlinkSync3(f);
81909
+ unlinkSync4(f);
80648
81910
  } catch {
80649
81911
  }
80650
81912
  }
@@ -80686,25 +81948,38 @@ async function runDaemon(opts = {}) {
80686
81948
  }
80687
81949
  async function takeoverMachine() {
80688
81950
  const rivals = /* @__PURE__ */ new Set();
81951
+ let lockPid = 0, statusPid = 0;
81952
+ const otherPids = [];
80689
81953
  try {
80690
- const lp = Number(readFileSync30(pidfile(), "utf-8").trim());
80691
- if (lp > 0)
80692
- rivals.add(lp);
81954
+ lockPid = Number(readFileSync30(pidfile(), "utf-8").trim());
81955
+ if (lockPid > 0)
81956
+ rivals.add(lockPid);
80693
81957
  } catch {
80694
81958
  }
80695
81959
  try {
80696
- const sp = Number(JSON.parse(readFileSync30(statusFile(), "utf-8")).pid ?? 0);
80697
- if (sp > 0)
80698
- rivals.add(sp);
81960
+ statusPid = Number(JSON.parse(readFileSync30(statusFile(), "utf-8")).pid ?? 0);
81961
+ if (statusPid > 0)
81962
+ rivals.add(statusPid);
80699
81963
  } catch {
80700
81964
  }
80701
- for (const p of otherDaemonPids())
81965
+ for (const p of otherDaemonPids()) {
81966
+ otherPids.push(p);
80702
81967
  rivals.add(p);
81968
+ }
81969
+ try {
81970
+ log9(`takeover: me=${process.pid} lockPid=${lockPid || "-"} statusPid=${statusPid || "-"} otherDaemonPids=[${otherPids.join(",") || "-"}]`);
81971
+ } catch {
81972
+ }
80703
81973
  rivals.delete(process.pid);
80704
81974
  const live = [...rivals].filter(isAlive2);
80705
81975
  if (live.length) {
80706
81976
  for (const pid of live) {
80707
- log5(`takeover: killing rival daemon pid=${pid} (latest-wins single-instance)`);
81977
+ let cmd = "";
81978
+ try {
81979
+ cmd = execFileSync4("ps", ["-o", "command=", "-p", String(pid)], { encoding: "utf8", timeout: 2e3 }).trim().slice(0, 200);
81980
+ } catch {
81981
+ }
81982
+ log9(`takeover: killing rival daemon pid=${pid} (latest-wins single-instance) cmd=[${cmd}]`);
80708
81983
  try {
80709
81984
  process.kill(pid, "SIGTERM");
80710
81985
  } catch {
@@ -80714,7 +81989,7 @@ async function runDaemon(opts = {}) {
80714
81989
  await new Promise((r) => setTimeout(r, 100));
80715
81990
  for (const pid of live)
80716
81991
  if (isAlive2(pid)) {
80717
- log5(`takeover: SIGKILL rival pid=${pid} (ignored SIGTERM)`);
81992
+ log9(`takeover: SIGKILL rival pid=${pid} (ignored SIGTERM)`);
80718
81993
  try {
80719
81994
  process.kill(pid, "SIGKILL");
80720
81995
  } catch {
@@ -80723,7 +81998,7 @@ async function runDaemon(opts = {}) {
80723
81998
  }
80724
81999
  try {
80725
82000
  if (existsSync28(pidfile()))
80726
- unlinkSync3(pidfile());
82001
+ unlinkSync4(pidfile());
80727
82002
  } catch {
80728
82003
  }
80729
82004
  }
@@ -80738,7 +82013,7 @@ async function runDaemon(opts = {}) {
80738
82013
  await takeoverMachine();
80739
82014
  if (!acquireLock2()) {
80740
82015
  try {
80741
- unlinkSync3(pidfile());
82016
+ unlinkSync4(pidfile());
80742
82017
  } catch {
80743
82018
  }
80744
82019
  if (!acquireLock2()) {
@@ -80749,26 +82024,53 @@ async function runDaemon(opts = {}) {
80749
82024
  }
80750
82025
  }
80751
82026
  }
82027
+ try {
82028
+ unlinkSync4(join54(daemonDir2(), "daemon.booting"));
82029
+ } catch {
82030
+ }
80752
82031
  let timer = null;
80753
82032
  let localServer = null;
80754
82033
  let localInfo = null;
80755
82034
  let rdeskManager = null;
80756
82035
  const loopbackRdeskSend = /* @__PURE__ */ new Map();
80757
- const activityTracker = createActivityTracker({ log: log5 });
82036
+ const activityTracker = createActivityTracker({ log: log9 });
80758
82037
  let activityTimer = null;
80759
82038
  const creds3 = readCredentials();
80760
82039
  const deviceId2 = creds3?.deviceId;
80761
82040
  const daemonStartedAt = Date.now();
82041
+ try {
82042
+ const pp = process;
82043
+ log9(`LAUNCH-CTX pid=${pp.pid} ppid=${pp.ppid} exec=${pp.execPath}`);
82044
+ log9(`LAUNCH-CTX argv=${JSON.stringify(pp.argv)}`);
82045
+ log9(`LAUNCH-CTX env: GUI=${process.env.CCB_DAEMON_GUI ?? "-"} FORCE_GUI=${process.env.CCB_FORCE_DAEMON_GUI ?? "-"} RUN_AS_NODE=${process.env.ELECTRON_RUN_AS_NODE ?? "-"} UNMANAGED=${process.env.CCB_DAEMON_UNMANAGED ?? "-"} ACTIVE_LINK=${process.env.CCB_ACTIVE_LINK ?? "-"} RDESK=${process.env.CCB_RDESK_ROLE ?? "-"}`);
82046
+ } catch {
82047
+ }
82048
+ const snapshotProcs = (tag) => {
82049
+ try {
82050
+ const raw = execFileSync4("ps", ["-axww", "-o", "pid=,ppid=,pcpu=,etime=,command="], { encoding: "utf8", timeout: 3e3 });
82051
+ const rows = raw.split("\n").filter((l) => /[Tt]esseract|host-daemon/.test(l) && !/\bgrep\b|ps -axww/.test(l));
82052
+ log9(`PROC-SNAPSHOT [${tag}] me=${process.pid} ppid=${process.ppid} \u2014 ${rows.length} tesseract proc(s):`);
82053
+ for (const r of rows.slice(0, 40))
82054
+ log9(` PROC ${r.trim().slice(0, 240)}`);
82055
+ } catch (e) {
82056
+ try {
82057
+ log9(`PROC-SNAPSHOT [${tag}] ps failed: ${e.message}`);
82058
+ } catch {
82059
+ }
82060
+ }
82061
+ };
80762
82062
  process.on("exit", (code) => {
80763
82063
  try {
80764
- log5(`PROCESS EXIT code=${code} uptimeMs=${Date.now() - daemonStartedAt}`);
82064
+ log9(`PROCESS EXIT code=${code} uptimeMs=${Date.now() - daemonStartedAt} ppid=${process.ppid}`);
80765
82065
  } catch {
80766
82066
  }
80767
82067
  });
80768
82068
  function shutdown(code, reason = "unknown") {
80769
82069
  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(" <- ")}`);
82070
+ log9(`SHUTDOWN reason=${reason} code=${code} uptimeMs=${Date.now() - daemonStartedAt} ppid=${process.ppid} pid=${process.pid}`);
82071
+ log9(`SHUTDOWN caller: ${(new Error("shutdown").stack ?? "").split("\n").slice(2, 7).map((l) => l.trim()).join(" <- ")}`);
82072
+ if (reason.startsWith("signal"))
82073
+ snapshotProcs(reason);
80772
82074
  } catch {
80773
82075
  }
80774
82076
  if (timer) {
@@ -80799,18 +82101,97 @@ async function runDaemon(opts = {}) {
80799
82101
  }
80800
82102
  process.on("SIGTERM", () => shutdown(0, "signal:SIGTERM"));
80801
82103
  process.on("SIGINT", () => shutdown(0, "signal:SIGINT"));
82104
+ try {
82105
+ process.on("SIGHUP", () => {
82106
+ try {
82107
+ log9("SIGHUP received \u2014 IGNORED (kept alive)");
82108
+ snapshotProcs("SIGHUP");
82109
+ } catch {
82110
+ }
82111
+ });
82112
+ } catch {
82113
+ }
82114
+ process.on("uncaughtException", (e) => {
82115
+ try {
82116
+ log9(`UNCAUGHT (kept alive): ${e instanceof Error ? e.stack ?? e.message : String(e)}`);
82117
+ } catch {
82118
+ }
82119
+ });
82120
+ process.on("unhandledRejection", (r) => {
82121
+ try {
82122
+ log9(`UNHANDLED REJECTION (kept alive): ${r instanceof Error ? r.stack ?? r.message : String(r)}`);
82123
+ } catch {
82124
+ }
82125
+ });
82126
+ setInterval(() => {
82127
+ }, 1 << 30);
82128
+ try {
82129
+ const eapp = electronModule()?.app;
82130
+ if (eapp) {
82131
+ eapp.on("before-quit", (e) => {
82132
+ try {
82133
+ log9("APP before-quit \u2014 PREVENTED (daemon never self-quits via Electron)");
82134
+ snapshotProcs("before-quit");
82135
+ } catch {
82136
+ }
82137
+ try {
82138
+ e?.preventDefault?.();
82139
+ } catch {
82140
+ }
82141
+ });
82142
+ eapp.on("will-quit", (e) => {
82143
+ try {
82144
+ log9(`APP will-quit \u2014 PREVENTED (uptime ${Date.now() - daemonStartedAt}ms)`);
82145
+ e?.preventDefault?.();
82146
+ } catch {
82147
+ }
82148
+ });
82149
+ eapp.on("quit", (_e, code) => {
82150
+ try {
82151
+ log9(`APP quit event code=${JSON.stringify(code)} (uptime ${Date.now() - daemonStartedAt}ms)`);
82152
+ } catch {
82153
+ }
82154
+ });
82155
+ eapp.on("window-all-closed", () => {
82156
+ try {
82157
+ log9("APP window-all-closed (kept alive \u2014 no auto-quit)");
82158
+ } catch {
82159
+ }
82160
+ });
82161
+ eapp.on("render-process-gone", (_e, d) => {
82162
+ try {
82163
+ log9(`render-process-gone (kept alive) uptime=${Date.now() - daemonStartedAt}ms: ${JSON.stringify(d)}`);
82164
+ snapshotProcs("render-gone");
82165
+ } catch {
82166
+ }
82167
+ });
82168
+ eapp.on("child-process-gone", (_e, d) => {
82169
+ try {
82170
+ log9(`child-process-gone (kept alive) uptime=${Date.now() - daemonStartedAt}ms: ${JSON.stringify(d)}`);
82171
+ } catch {
82172
+ }
82173
+ });
82174
+ eapp.on("gpu-process-crashed", (_e, killed) => {
82175
+ try {
82176
+ log9(`gpu-process-crashed killed=${JSON.stringify(killed)} uptime=${Date.now() - daemonStartedAt}ms`);
82177
+ } catch {
82178
+ }
82179
+ });
82180
+ }
82181
+ } catch {
82182
+ }
80802
82183
  if (process.env.CCB_RDESK_ROLE !== "client")
80803
82184
  process.env.CCB_HOST_DAEMON = "1";
80804
82185
  const localToken = randomBytes8(24).toString("hex");
80805
82186
  try {
80806
- localServer = await startLocalServer({ hostRoot: resolveHostRoot(), token: localToken, log: log5, getRdesk: () => rdeskManager, loopbackRdeskSend });
82187
+ localServer = await startLocalServer({ hostRoot: resolveHostRoot(), token: localToken, log: log9, getRdesk: () => rdeskManager, loopbackRdeskSend });
80807
82188
  localInfo = { localPort: localServer.port, localToken };
80808
82189
  } catch (e) {
80809
- log5("local listener failed to start", e.message);
82190
+ log9("local listener failed to start", e.message);
80810
82191
  }
80811
82192
  writeStatus({ deviceId: deviceId2, connected: false });
80812
82193
  try {
80813
- registerNativeHost({ daemonDir: daemonDir2(), log: log5 });
82194
+ registerNativeHost({ daemonDir: daemonDir2(), log: log9 });
80814
82195
  } catch {
80815
82196
  }
80816
82197
  const syncActivity = () => {
@@ -80829,12 +82210,12 @@ async function runDaemon(opts = {}) {
80829
82210
  try {
80830
82211
  await opts.onOwned?.();
80831
82212
  } catch (e) {
80832
- log5("onOwned hook failed", e.message);
82213
+ log9("onOwned hook failed", e.message);
80833
82214
  }
80834
82215
  if (!creds3) {
80835
82216
  timer = setInterval(() => writeStatus({ deviceId: deviceId2, connected: false, localOnly: true }), 6e4);
80836
82217
  writeStatus({ deviceId: deviceId2, connected: false, localOnly: true });
80837
- log5(`started LOCAL-ONLY (not signed in) \u2014 local :${localInfo?.localPort ?? "off"}, version ${VERSION}`);
82218
+ log9(`started LOCAL-ONLY (not signed in) \u2014 local :${localInfo?.localPort ?? "off"}, version ${VERSION}`);
80838
82219
  return;
80839
82220
  }
80840
82221
  const c = creds3;
@@ -80843,10 +82224,20 @@ async function runDaemon(opts = {}) {
80843
82224
  void (async () => {
80844
82225
  try {
80845
82226
  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)");
82227
+ const sealing = await loadOrCreateSealingKey();
82228
+ const sealingPubSig = await attestSealingKey(id.privateKey, sealing.publicRaw);
82229
+ const ok = await registerDeviceKey({
82230
+ apiOrigin: c.apiOrigin,
82231
+ accessToken,
82232
+ deviceId: c.deviceId,
82233
+ publicRaw: id.publicRaw,
82234
+ label: hostname4(),
82235
+ sealingPub: b64u(sealing.publicRaw),
82236
+ sealingPubSig
82237
+ });
82238
+ log9(ok ? "e2e identity + sealing key registered" : "e2e identity registration not accepted (will retry on restart)");
80848
82239
  } catch (e) {
80849
- log5("e2e identity registration failed (non-fatal)", e.message);
82240
+ log9("e2e identity registration failed (non-fatal)", e.message);
80850
82241
  }
80851
82242
  })();
80852
82243
  async function refresh() {
@@ -80857,11 +82248,11 @@ async function runDaemon(opts = {}) {
80857
82248
  body: JSON.stringify({ dtid: c.dtid, refreshSecret: c.refreshSecret })
80858
82249
  });
80859
82250
  if (!r.ok) {
80860
- log5("token refresh rejected", r.status);
82251
+ log9("token refresh rejected", r.status);
80861
82252
  if (r.status === 401) {
80862
- log5("device token no longer valid \u2014 dropping to local-only on restart");
82253
+ log9("device token no longer valid \u2014 dropping to local-only on restart");
80863
82254
  try {
80864
- unlinkSync3(join50(daemonDir2(), "credentials.json"));
82255
+ unlinkSync4(join54(daemonDir2(), "credentials.json"));
80865
82256
  } catch {
80866
82257
  }
80867
82258
  shutdown(0, "token-revoked-401");
@@ -80873,15 +82264,53 @@ async function runDaemon(opts = {}) {
80873
82264
  accessToken = data.accessToken;
80874
82265
  writeCredentials({ ...c, accessToken: data.accessToken, expiresAt: data.expiresAt ?? c.expiresAt, publicId: data.publicId ?? c.publicId });
80875
82266
  refreshControlLink();
80876
- log5("token refreshed");
82267
+ log9("token refreshed");
80877
82268
  }
80878
82269
  } catch (e) {
80879
- log5("token refresh failed (will retry)", e.message);
82270
+ log9("token refresh failed (will retry)", e.message);
80880
82271
  }
80881
82272
  }
80882
82273
  registerTesseractEngine({ getIceServers: () => fetchIceServers() });
80883
82274
  registerVncEngine();
80884
82275
  registerRdpEngine();
82276
+ initDropReceiveUi({ hostLabel: (hostId) => listHosts().find((h) => h.hostId === hostId)?.name });
82277
+ const dropsBase = `${c.apiOrigin.replace(/\/$/, "")}/api/core/tesseract/drops`;
82278
+ const drops = createDropHandler({
82279
+ api: async (path6, init) => {
82280
+ const method = init?.method ?? "GET";
82281
+ const hasBody = method !== "GET" && method !== "HEAD";
82282
+ const r = await fetch(`${dropsBase}${path6}`, {
82283
+ method,
82284
+ headers: { authorization: `Bearer ${accessToken}`, ...hasBody ? { "content-type": "application/json" } : {} },
82285
+ ...hasBody ? { body: JSON.stringify(init?.body ?? {}) } : {},
82286
+ signal: AbortSignal.timeout(1e4)
82287
+ });
82288
+ if (!r.ok)
82289
+ throw new Error(`drops ${path6} \u2192 ${r.status}`);
82290
+ return await r.json().catch(() => ({}));
82291
+ },
82292
+ deviceId: () => c.deviceId,
82293
+ ownHostId: () => ownHost() || null,
82294
+ canPrompt: () => !!electronModule(),
82295
+ confirm: async (spec, signal) => (await confirmAction(spec, { signal })).confirmed,
82296
+ // Store-and-forward: we won the claim, so pull the bytes and (for cross-user
82297
+ // drops) open the one seal in the fan-out that was made for THIS device.
82298
+ fetchBlobDrop: async ({ dropId, destDir, claim, sender }) => {
82299
+ await fetchBlobDrop({
82300
+ dropId,
82301
+ destDir,
82302
+ apiOrigin: c.apiOrigin,
82303
+ items: claim.items ?? [],
82304
+ sealedKey: claim.sealedKey,
82305
+ sealingPrivate: async () => (await loadOrCreateSealingKey()).privateKey,
82306
+ // Present ⇒ cross-user ⇒ quarantine, so the OS treats the file as foreign.
82307
+ sender
82308
+ });
82309
+ }
82310
+ // No startLiveSend here: SENDING is driven by the app (a client tells the source
82311
+ // host to begin, exactly like the existing copy-to-machine flow). The daemon is
82312
+ // the receiving half, and the source host it addresses may well be this daemon.
82313
+ });
80885
82314
  let rdeskRef = null;
80886
82315
  const rdesk = new RdeskSessionManager({
80887
82316
  createEngine: createRemoteDesktopEngine,
@@ -80912,8 +82341,64 @@ async function runDaemon(opts = {}) {
80912
82341
  onSessionsChanged: (sessions3) => {
80913
82342
  writeStatus({ deviceId: c.deviceId, connected: true, rdeskSessions: sessions3.length });
80914
82343
  setRdeskTraySessions(sessions3.length, () => void rdeskRef?.disconnectAll("host"));
82344
+ if (sessions3.length === 0)
82345
+ closeRdeskCallWindow();
80915
82346
  },
80916
- log: (m) => log5(m)
82347
+ // A viewer joined/left the in-session voice/video call → open/close the operator call window,
82348
+ // joined to the SAME LiveKit room (= the rdesk sessionId) the viewer is in, so the person at the
82349
+ // host can talk. Reuses the team-chat/huddle conferencing; the daemon mints its own join token
82350
+ // with the device token (identity `${sub}#host`, distinct from the viewer's).
82351
+ onCall: ({ sessionId, on }) => {
82352
+ if (!on) {
82353
+ closeRdeskCallWindow();
82354
+ return;
82355
+ }
82356
+ void (async () => {
82357
+ try {
82358
+ const r = await fetch(`${c.apiOrigin.replace(/\/$/, "")}/api/core/tesseract/conferencing/token`, {
82359
+ method: "POST",
82360
+ headers: { "content-type": "application/json", authorization: `Bearer ${accessToken}` },
82361
+ body: JSON.stringify({ room: sessionId, identity: "host", name: hostname4() }),
82362
+ signal: AbortSignal.timeout(1e4)
82363
+ });
82364
+ if (!r.ok) {
82365
+ log9(`[rdesk call] token mint ${r.status} (conferencing configured?)`);
82366
+ return;
82367
+ }
82368
+ const j = await r.json().catch(() => ({}));
82369
+ if (!j.token || !j.url) {
82370
+ log9("[rdesk call] token response missing token/url");
82371
+ return;
82372
+ }
82373
+ showRdeskCallWindow({ url: j.url, token: j.token, room: sessionId });
82374
+ } catch (e) {
82375
+ log9("[rdesk call] token mint failed", e instanceof Error ? e.message : String(e));
82376
+ }
82377
+ })();
82378
+ },
82379
+ // In-session chat from the viewer → surface it to the operator as a native notification.
82380
+ // On macOS the notification carries an inline REPLY box (hasReply) whose text is sent
82381
+ // straight back via sendChat — a full operator chat loop with no extra window. Off-mac (no
82382
+ // hasReply) it's receive-only for now; the daemon log always records it as a fallback.
82383
+ onChat: ({ sessionId, viewerName, text: text8 }) => {
82384
+ const NotifCtor = electronModule()?.Notification;
82385
+ if (!NotifCtor) {
82386
+ log9(`[rdesk chat] ${viewerName ?? "viewer"}: ${text8}`);
82387
+ return;
82388
+ }
82389
+ try {
82390
+ const n = new NotifCtor({ title: `${viewerName ?? "Remote viewer"} \u2014 remote desktop`, body: text8, hasReply: true, replyPlaceholder: "Reply\u2026" });
82391
+ n.on("reply", (_e, reply3) => {
82392
+ const r = typeof reply3 === "string" ? reply3.trim() : "";
82393
+ if (r)
82394
+ rdeskRef?.sendChat(sessionId, r);
82395
+ });
82396
+ n.show();
82397
+ } catch (e) {
82398
+ log9("[rdesk chat] notify failed", e instanceof Error ? e.message : String(e));
82399
+ }
82400
+ },
82401
+ log: (m) => log9(m)
80917
82402
  });
80918
82403
  rdeskRef = rdesk;
80919
82404
  rdeskManager = rdesk;
@@ -80922,7 +82407,7 @@ async function runDaemon(opts = {}) {
80922
82407
  token: async () => accessToken,
80923
82408
  deviceId: c.deviceId,
80924
82409
  projectDir: daemonWorkspaceDir(),
80925
- log: (...args) => log5("chat-agent", ...args.map((a) => String(a)))
82410
+ log: (...args) => log9("chat-agent", ...args.map((a) => String(a)))
80926
82411
  });
80927
82412
  const hooks2 = {
80928
82413
  deliverToRenderer: () => {
@@ -80939,7 +82424,7 @@ async function runDaemon(opts = {}) {
80939
82424
  getRdeskClientWebContents()?.send("hostlink:own", own2);
80940
82425
  },
80941
82426
  notify: (n) => {
80942
- log5("notify", JSON.stringify(n));
82427
+ log9("notify", JSON.stringify(n));
80943
82428
  getRdeskClientWebContents()?.send("ccb:notify", n);
80944
82429
  },
80945
82430
  // No renderer, but DO forward remote change feeds to the macOS File Provider extension
@@ -80952,6 +82437,7 @@ async function runDaemon(opts = {}) {
80952
82437
  onHostsChanged: () => broadcastFileProviderHostsChanged(),
80953
82438
  pushTransferEvent: () => {
80954
82439
  },
82440
+ onDrop: (msg) => drops.onFrame(msg),
80955
82441
  pushTerm: () => {
80956
82442
  },
80957
82443
  // Remote-desktop host: a viewer drives an rdesk session on this machine.
@@ -81003,13 +82489,13 @@ async function runDaemon(opts = {}) {
81003
82489
  getPeerIdentityPub: (d) => getPeerIdentityPub({ apiOrigin: c.apiOrigin, accessToken, deviceId: d })
81004
82490
  };
81005
82491
  } catch (err2) {
81006
- log5("e2e responder init failed (staying plaintext)", err2.message);
82492
+ log9("e2e responder init failed (staying plaintext)", err2.message);
81007
82493
  }
81008
82494
  }
81009
82495
  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}`);
82496
+ startFileProviderBridge(log9);
82497
+ startAutofillBridge(log9);
82498
+ log9(`started \u2014 device ${c.deviceId}, user ${c.userId}, relay ${c.backendOrigin}, local :${localInfo?.localPort ?? "off"}, version ${VERSION}`);
81013
82499
  await refresh();
81014
82500
  timer = setInterval(() => {
81015
82501
  void refresh();
@@ -81034,14 +82520,14 @@ var CHUNK_BYTES = 256 * 1024;
81034
82520
  import { platform as platform3 } from "node:os";
81035
82521
 
81036
82522
  // ../../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";
82523
+ import { writeFileSync as writeFileSync26, existsSync as existsSync29, mkdirSync as mkdirSync27, unlinkSync as unlinkSync5 } from "node:fs";
82524
+ import { dirname as dirname24, join as join55 } from "node:path";
81039
82525
  import { homedir as homedir8, userInfo } from "node:os";
81040
82526
 
81041
82527
  // ../../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);
82528
+ import { execFile as execFile7, spawn as spawn10 } from "node:child_process";
82529
+ import { promisify as promisify6 } from "node:util";
82530
+ var run3 = promisify6(execFile7);
81045
82531
  function runInherit(cmd, args) {
81046
82532
  return new Promise((resolve12) => {
81047
82533
  const p = spawn10(cmd, args, { stdio: "inherit" });
@@ -81059,10 +82545,10 @@ function xml(s) {
81059
82545
  }
81060
82546
  function makeLaunchd(instanceId) {
81061
82547
  const label = SERVICE_LABEL_BASE + (instanceId ? `.${instanceId}` : "");
81062
- const plistPath = () => join51(homedir8(), "Library", "LaunchAgents", `${label}.plist`);
82548
+ const plistPath = () => join55(homedir8(), "Library", "LaunchAgents", `${label}.plist`);
81063
82549
  const domainTarget = () => `gui/${userInfo().uid}`;
81064
82550
  const serviceTarget = () => `${domainTarget()}/${label}`;
81065
- const logFile = () => join51(daemonDir2(), "daemon.log");
82551
+ const logFile = () => join55(daemonDir2(), "daemon.log");
81066
82552
  const workDirOf = (spec) => spec.workingDirectory ?? daemonDir2();
81067
82553
  function buildPlist(spec) {
81068
82554
  const args = [spec.exec, ...spec.args].map((a) => ` <string>${xml(a)}</string>`).join("\n");
@@ -81108,8 +82594,8 @@ ${env}
81108
82594
  return {
81109
82595
  async install(spec) {
81110
82596
  mkdirSync27(workDirOf(spec), { recursive: true });
81111
- mkdirSync27(dirname22(plistPath()), { recursive: true });
81112
- mkdirSync27(dirname22(spec.logPath), { recursive: true });
82597
+ mkdirSync27(dirname24(plistPath()), { recursive: true });
82598
+ mkdirSync27(dirname24(spec.logPath), { recursive: true });
81113
82599
  writeFileSync26(plistPath(), buildPlist(spec));
81114
82600
  await run3("launchctl", ["bootout", serviceTarget()]).catch(() => {
81115
82601
  });
@@ -81124,7 +82610,7 @@ ${env}
81124
82610
  });
81125
82611
  if (existsSync29(plistPath())) {
81126
82612
  try {
81127
- unlinkSync4(plistPath());
82613
+ unlinkSync5(plistPath());
81128
82614
  } catch {
81129
82615
  }
81130
82616
  }
@@ -81160,8 +82646,8 @@ ${env}
81160
82646
  }
81161
82647
 
81162
82648
  // ../../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";
82649
+ import { writeFileSync as writeFileSync27, existsSync as existsSync30, mkdirSync as mkdirSync28, unlinkSync as unlinkSync6 } from "node:fs";
82650
+ import { dirname as dirname25, join as join56 } from "node:path";
81165
82651
  import { homedir as homedir9, userInfo as userInfo2 } from "node:os";
81166
82652
  function unitVal(v) {
81167
82653
  return v.replace(/%/g, "%%");
@@ -81172,8 +82658,8 @@ function shq(s) {
81172
82658
  function makeSystemd(instanceId) {
81173
82659
  const unit = `tesseract-host${instanceId ? `-${instanceId}` : ""}.service`;
81174
82660
  const unitPath = () => {
81175
- const base = process.env.XDG_CONFIG_HOME || join52(homedir9(), ".config");
81176
- return join52(base, "systemd", "user", unit);
82661
+ const base = process.env.XDG_CONFIG_HOME || join56(homedir9(), ".config");
82662
+ return join56(base, "systemd", "user", unit);
81177
82663
  };
81178
82664
  const workDirOf = (spec) => spec.workingDirectory ?? daemonDir2();
81179
82665
  function buildUnit(spec) {
@@ -81201,8 +82687,8 @@ WantedBy=default.target
81201
82687
  return {
81202
82688
  async install(spec) {
81203
82689
  mkdirSync28(workDirOf(spec), { recursive: true });
81204
- mkdirSync28(dirname23(unitPath()), { recursive: true });
81205
- mkdirSync28(dirname23(spec.logPath), { recursive: true });
82690
+ mkdirSync28(dirname25(unitPath()), { recursive: true });
82691
+ mkdirSync28(dirname25(spec.logPath), { recursive: true });
81206
82692
  writeFileSync27(unitPath(), buildUnit(spec));
81207
82693
  await run3("loginctl", ["enable-linger", userInfo2().username]).catch(() => {
81208
82694
  });
@@ -81217,7 +82703,7 @@ WantedBy=default.target
81217
82703
  });
81218
82704
  if (existsSync30(unitPath())) {
81219
82705
  try {
81220
- unlinkSync5(unitPath());
82706
+ unlinkSync6(unitPath());
81221
82707
  } catch {
81222
82708
  }
81223
82709
  }
@@ -81246,12 +82732,12 @@ WantedBy=default.target
81246
82732
  }
81247
82733
 
81248
82734
  // ../../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";
82735
+ import { writeFileSync as writeFileSync28, existsSync as existsSync31, mkdirSync as mkdirSync29, unlinkSync as unlinkSync7 } from "node:fs";
82736
+ import { dirname as dirname26, join as join57 } from "node:path";
81251
82737
  function makeWindowsTask(instanceId) {
81252
82738
  const task = `Fineguide\\TesseractHost${instanceId ? `-${instanceId}` : ""}`;
81253
- const wrapperPath = () => join53(daemonDir2(), `run-host${instanceId ? `-${instanceId}` : ""}.cmd`);
81254
- const logFile = () => join53(daemonDir2(), "daemon.log");
82739
+ const wrapperPath = () => join57(daemonDir2(), `run-host${instanceId ? `-${instanceId}` : ""}.cmd`);
82740
+ const logFile = () => join57(daemonDir2(), "daemon.log");
81255
82741
  const oneLine2 = (s) => s.replace(/[\r\n]+/g, " ");
81256
82742
  const envVal = (v) => oneLine2(v).replace(/%/g, "%%").replace(/"/g, "");
81257
82743
  const cmdArg = (a) => `"${oneLine2(a).replace(/"/g, "")}"`;
@@ -81268,8 +82754,8 @@ start "" /b ${cmd} >> "${oneLine2(spec.logPath).replace(/"/g, "")}" 2>&1\r
81268
82754
  return {
81269
82755
  async install(spec) {
81270
82756
  mkdirSync29(spec.workingDirectory ?? daemonDir2(), { recursive: true });
81271
- mkdirSync29(dirname24(spec.logPath), { recursive: true });
81272
- mkdirSync29(dirname24(wrapperPath()), { recursive: true });
82757
+ mkdirSync29(dirname26(spec.logPath), { recursive: true });
82758
+ mkdirSync29(dirname26(wrapperPath()), { recursive: true });
81273
82759
  writeFileSync28(wrapperPath(), buildWrapper(spec));
81274
82760
  await run3("schtasks", ["/Create", "/TN", task, "/SC", "ONLOGON", "/RL", "LIMITED", "/F", "/TR", `"${wrapperPath()}"`]);
81275
82761
  await run3("schtasks", ["/Run", "/TN", task]).catch(() => {
@@ -81280,7 +82766,7 @@ start "" /b ${cmd} >> "${oneLine2(spec.logPath).replace(/"/g, "")}" 2>&1\r
81280
82766
  });
81281
82767
  if (existsSync31(wrapperPath())) {
81282
82768
  try {
81283
- unlinkSync6(wrapperPath());
82769
+ unlinkSync7(wrapperPath());
81284
82770
  } catch {
81285
82771
  }
81286
82772
  }
@@ -81334,9 +82820,9 @@ function getServiceController(instanceId) {
81334
82820
 
81335
82821
  // src/auth.ts
81336
82822
  var import_qrcode_terminal = __toESM(require_main(), 1);
81337
- import { randomUUID as randomUUID17 } from "node:crypto";
82823
+ import { randomUUID as randomUUID18 } from "node:crypto";
81338
82824
  import { existsSync as existsSync32, rmSync as rmSync8 } from "node:fs";
81339
- import { join as join54 } from "node:path";
82825
+ import { join as join58 } from "node:path";
81340
82826
 
81341
82827
  // src/config.ts
81342
82828
  import { hostname as hostname5 } from "node:os";
@@ -81476,7 +82962,7 @@ async function login(opts) {
81476
82962
  process.exit(1);
81477
82963
  }
81478
82964
  }
81479
- const deviceId2 = readCredentials()?.deviceId ?? process.env.CCB_DEVICE_ID ?? randomUUID17().slice(0, 8);
82965
+ const deviceId2 = readCredentials()?.deviceId ?? process.env.CCB_DEVICE_ID ?? randomUUID18().slice(0, 8);
81480
82966
  const label = opts.name ?? defaultLabel();
81481
82967
  if (opts.token) {
81482
82968
  const dt = await fetchJson(`${api}/auth/device-token`, {
@@ -81537,7 +83023,7 @@ async function logout() {
81537
83023
  }
81538
83024
  for (const f of ["credentials.json", "status.json", "daemon.lock"]) {
81539
83025
  try {
81540
- const p = join54(daemonDir2(), f);
83026
+ const p = join58(daemonDir2(), f);
81541
83027
  if (existsSync32(p)) rmSync8(p);
81542
83028
  } catch {
81543
83029
  }
@@ -81584,7 +83070,7 @@ function isNewer(latest, current) {
81584
83070
  for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
81585
83071
  return false;
81586
83072
  }
81587
- function log4(...args) {
83073
+ function log8(...args) {
81588
83074
  console.log(`[self-update ${(/* @__PURE__ */ new Date()).toISOString()}]`, ...args);
81589
83075
  }
81590
83076
  async function fetchLatest() {
@@ -81630,25 +83116,25 @@ function verifyNpmSignature(version2, integrity, sig) {
81630
83116
  function installVersion(target) {
81631
83117
  return new Promise((resolve12) => {
81632
83118
  const npm = process.platform === "win32" ? "npm.cmd" : "npm";
81633
- log4(`installing tesseract-agent@${target} \u2026`);
83119
+ log8(`installing tesseract-agent@${target} \u2026`);
81634
83120
  let child;
81635
83121
  try {
81636
83122
  child = spawn11(npm, ["install", "-g", `tesseract-agent@${target}`], { stdio: ["ignore", "inherit", "inherit"] });
81637
83123
  } catch (e) {
81638
- log4("npm spawn threw:", e instanceof Error ? e.message : String(e));
83124
+ log8("npm spawn threw:", e instanceof Error ? e.message : String(e));
81639
83125
  resolve12(false);
81640
83126
  return;
81641
83127
  }
81642
83128
  child.on("error", (e) => {
81643
- log4("npm not runnable:", e.message);
83129
+ log8("npm not runnable:", e.message);
81644
83130
  resolve12(false);
81645
83131
  });
81646
83132
  child.on("close", (code) => {
81647
83133
  if (code === 0) {
81648
- log4(`installed ${target}`);
83134
+ log8(`installed ${target}`);
81649
83135
  resolve12(true);
81650
83136
  } else {
81651
- log4(`npm install exited ${code} \u2014 staying on the current version`);
83137
+ log8(`npm install exited ${code} \u2014 staying on the current version`);
81652
83138
  resolve12(false);
81653
83139
  }
81654
83140
  });
@@ -81661,15 +83147,15 @@ async function checkOnce(current) {
81661
83147
  try {
81662
83148
  const latest = await fetchLatest();
81663
83149
  if (!latest || !isNewer(latest, current)) return;
81664
- log4(`update available: ${current} \u2192 ${latest}`);
83150
+ log8(`update available: ${current} \u2192 ${latest}`);
81665
83151
  const dist = await fetchVersionDist(latest);
81666
83152
  if (!verifyNpmSignature(latest, dist?.integrity, dist?.sig)) {
81667
- log4(`REFUSING update to ${latest}: registry signature did not verify \u2014 staying on ${current}`);
83153
+ log8(`REFUSING update to ${latest}: registry signature did not verify \u2014 staying on ${current}`);
81668
83154
  return;
81669
83155
  }
81670
- log4(`registry signature verified for ${latest} (keyid ${dist?.keyid ?? "?"})`);
83156
+ log8(`registry signature verified for ${latest} (keyid ${dist?.keyid ?? "?"})`);
81671
83157
  if (await installVersion(latest)) {
81672
- log4("restarting on the new version (supervisor re-spawns) \u2026");
83158
+ log8("restarting on the new version (supervisor re-spawns) \u2026");
81673
83159
  process.exit(RESTART_EXIT_CODE);
81674
83160
  }
81675
83161
  } finally {
@@ -81678,11 +83164,11 @@ async function checkOnce(current) {
81678
83164
  }
81679
83165
  function startSelfUpdater(current) {
81680
83166
  if (disabled()) {
81681
- log4("disabled via TESSERACT_AUTO_UPDATE/CCB_AUTO_UPDATE");
83167
+ log8("disabled via TESSERACT_AUTO_UPDATE/CCB_AUTO_UPDATE");
81682
83168
  return;
81683
83169
  }
81684
83170
  if (!isReleaseVersion(current)) {
81685
- log4(`skipped \u2014 non-release version "${current}"`);
83171
+ log8(`skipped \u2014 non-release version "${current}"`);
81686
83172
  return;
81687
83173
  }
81688
83174
  const first = setTimeout(() => {
@@ -81696,7 +83182,7 @@ function startSelfUpdater(current) {
81696
83182
  }
81697
83183
 
81698
83184
  // src/service.ts
81699
- import { join as join55 } from "node:path";
83185
+ import { join as join59 } from "node:path";
81700
83186
  import { platform as platform4 } from "node:os";
81701
83187
  import { existsSync as existsSync33, readFileSync as readFileSync31 } from "node:fs";
81702
83188
  function controller(instanceId) {
@@ -81711,7 +83197,7 @@ function buildSpec(cliEntry) {
81711
83197
  exec: process.execPath,
81712
83198
  args: [cliEntry, "run"],
81713
83199
  env: { CCB_DAEMON_DIR: daemonDir2() },
81714
- logPath: join55(daemonDir2(), "daemon.log")
83200
+ logPath: join59(daemonDir2(), "daemon.log")
81715
83201
  };
81716
83202
  }
81717
83203
  var isAlive = (pid) => {
@@ -81723,7 +83209,7 @@ var isAlive = (pid) => {
81723
83209
  }
81724
83210
  };
81725
83211
  function runningDaemonInfo() {
81726
- const lock = join55(daemonDir2(), "daemon.lock");
83212
+ const lock = join59(daemonDir2(), "daemon.lock");
81727
83213
  try {
81728
83214
  if (!existsSync33(lock)) return null;
81729
83215
  const pid = Number(readFileSync31(lock, "utf-8").trim());
@@ -81762,7 +83248,7 @@ async function logs(instanceId, follow) {
81762
83248
  // src/cli.ts
81763
83249
  function pkgVersion() {
81764
83250
  try {
81765
- const p = join56(dirname25(fileURLToPath4(import.meta.url)), "..", "package.json");
83251
+ const p = join60(dirname27(fileURLToPath4(import.meta.url)), "..", "package.json");
81766
83252
  return JSON.parse(readFileSync32(p, "utf8")).version ?? "0.0.0";
81767
83253
  } catch {
81768
83254
  return "0.0.0";
@@ -81955,7 +83441,7 @@ async function linkCommand(rest, flags) {
81955
83441
  process.exit(1);
81956
83442
  }
81957
83443
  async function captureClaudeSubscription(flags) {
81958
- const home = mkdtempSync2(join56(tmpdir4(), "tesseract-claude-login-"));
83444
+ const home = mkdtempSync2(join60(tmpdir4(), "tesseract-claude-login-"));
81959
83445
  const binary = typeof flags["claude-path"] === "string" ? flags["claude-path"] : process.env.CCB_ENGINE_PATH_CLAUDE_CODE || "claude";
81960
83446
  const args = ["auth", "login", "--claudeai"];
81961
83447
  if (typeof flags.email === "string") args.push("--email", flags.email);
@@ -81972,7 +83458,7 @@ async function captureClaudeSubscription(flags) {
81972
83458
  child.once("close", (exitCode, signal) => resolveCode(signal ? 1 : exitCode ?? 1));
81973
83459
  });
81974
83460
  if (code !== 0) throw new Error(`Claude sign-in did not complete (exit ${code}).`);
81975
- const file2 = join56(home, ".credentials.json");
83461
+ const file2 = join60(home, ".credentials.json");
81976
83462
  if (existsSync34(file2)) return readFileSync32(file2, "utf8");
81977
83463
  if (process.platform === "darwin") {
81978
83464
  const value = execFileSync5("security", [