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.
- package/dist-bundle/cli.js +1917 -431
- package/package.json +1 -1
package/dist-bundle/cli.js
CHANGED
|
@@ -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:
|
|
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
|
|
22268
|
-
if (!
|
|
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:
|
|
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
|
|
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
|
|
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,
|
|
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:
|
|
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
|
|
26474
|
+
const bs4 = [...b].sort();
|
|
26467
26475
|
for (let i = 0; i < as.length; i++)
|
|
26468
|
-
if (as[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(
|
|
36732
|
-
var value =
|
|
36733
|
-
var posval =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
62602
|
-
import { randomUUID as
|
|
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
|
|
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
|
|
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
|
-
|
|
62638
|
-
|
|
62639
|
-
const reqId =
|
|
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
|
|
62807
|
+
void win3.loadFile(uiPath("consent.html")).then(() => {
|
|
62642
62808
|
try {
|
|
62643
|
-
|
|
62644
|
-
|
|
62645
|
-
|
|
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 (!
|
|
62661
|
-
|
|
62826
|
+
if (!win3.isDestroyed())
|
|
62827
|
+
win3.close();
|
|
62662
62828
|
} catch {
|
|
62663
62829
|
}
|
|
62664
|
-
if (consentWin ===
|
|
62830
|
+
if (consentWin === win3)
|
|
62665
62831
|
consentWin = null;
|
|
62666
62832
|
resolve12(r.ok);
|
|
62667
62833
|
};
|
|
62668
62834
|
pendingConsent.set(reqId, finish);
|
|
62669
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
64556
|
-
return new Uint8Array(Buffer.from(
|
|
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((
|
|
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
|
|
64727
|
-
if (!
|
|
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(
|
|
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
|
|
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 ??
|
|
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 =
|
|
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(
|
|
64912
|
-
return new Uint8Array(Buffer.from(
|
|
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
|
|
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,
|
|
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(`${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
66274
|
-
import { randomUUID as
|
|
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
|
|
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 =
|
|
66295
|
-
const outDir = meta4.outDir ??
|
|
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:
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
|
66650
|
-
import { randomUUID as
|
|
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
|
|
66657
|
-
import { randomUUID as
|
|
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("~\\") ?
|
|
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. ${
|
|
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 =
|
|
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 =
|
|
66996
|
+
const outPath = join25(dir2, `edit.${format}`);
|
|
66830
66997
|
let assFile;
|
|
66831
66998
|
if (edl.captions?.cues.length) {
|
|
66832
|
-
assFile =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 ?
|
|
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() ||
|
|
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(
|
|
67136
|
-
return existsSync12(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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",
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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(`${
|
|
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
|
|
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
|
|
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(`${
|
|
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:
|
|
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
|
|
67654
|
-
|
|
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 =
|
|
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
|
|
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 ??
|
|
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 =
|
|
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
|
|
67829
|
-
if (!
|
|
67830
|
-
return { ok: false, error:
|
|
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(
|
|
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
|
|
67839
|
-
if (!
|
|
67840
|
-
return { ok: false, error:
|
|
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 },
|
|
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,
|
|
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 ??
|
|
68037
|
+
const dir2 = outDir ?? join28(musicDir(), randomUUID14());
|
|
67861
68038
|
await mkdir6(dir2, { recursive: true });
|
|
67862
|
-
const path6 =
|
|
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 ??
|
|
68057
|
+
const dir2 = outDir ?? join28(musicDir(), randomUUID14());
|
|
67881
68058
|
await mkdir6(dir2, { recursive: true });
|
|
67882
|
-
const path6 =
|
|
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
|
|
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(
|
|
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
|
|
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:${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
|
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
|
|
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(
|
|
69320
|
+
stack.push(join31(dir2, e.name));
|
|
69144
69321
|
} else if (e.isFile()) {
|
|
69145
69322
|
fileCount++;
|
|
69146
69323
|
try {
|
|
69147
|
-
totalBytes += (await lstat(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
69401
|
-
return isAbsolute5(raw) ? 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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
69715
|
+
const meta4 = JSON.parse(readFileSync21(join32(trashDir, e.name, ".fp-meta.json"), "utf-8"));
|
|
69539
69716
|
if ((meta4.deletedAt ?? 0) < cutoff)
|
|
69540
|
-
rmSync5(
|
|
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(
|
|
69743
|
+
id = statId(join32(real, e.name));
|
|
69567
69744
|
} catch {
|
|
69568
69745
|
}
|
|
69569
69746
|
return {
|
|
69570
69747
|
name: e.name,
|
|
69571
|
-
path:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
69989
|
+
const itemDir = join32(root, TRASH_DIR, id);
|
|
69813
69990
|
await mkdir7(itemDir, { recursive: true });
|
|
69814
69991
|
const name = basename5(p);
|
|
69815
|
-
const trashedPath =
|
|
69992
|
+
const trashedPath = join32(itemDir, name);
|
|
69816
69993
|
await moveItem(p, trashedPath);
|
|
69817
|
-
await writeFile8(
|
|
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 =
|
|
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(
|
|
69834
|
-
out.push({ trashId: e.name, name: meta4.name, path:
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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: (
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
72207
|
+
const basename12 = this._isDirent ? dirent.name : dirent;
|
|
71945
72208
|
try {
|
|
71946
|
-
const fullPath = presolve(pjoin(path6,
|
|
71947
|
-
entry = { path: prelative(this._root, fullPath), fullPath, basename:
|
|
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
|
|
72749
|
+
const basename12 = sysPath.basename(path6);
|
|
72487
72750
|
const parent = this.fsw._getWatchedDir(directory);
|
|
72488
|
-
parent.add(
|
|
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(
|
|
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
|
|
72521
|
-
const
|
|
72522
|
-
const parent = this.fsw._getWatchedDir(
|
|
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(
|
|
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(
|
|
72812
|
+
this.fsw._remove(dirname28, basename12);
|
|
72550
72813
|
}
|
|
72551
|
-
} else if (parent.has(
|
|
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(
|
|
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
|
|
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
|
|
74516
|
+
import { join as join41 } from "node:path";
|
|
74254
74517
|
function configPath2() {
|
|
74255
|
-
return
|
|
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
|
|
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 ??
|
|
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 =
|
|
75387
|
+
localRoot = join42(remoteParent, safeFolderName(remoteSubfolder));
|
|
75119
75388
|
} else {
|
|
75120
|
-
localRoot =
|
|
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
|
|
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
|
|
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 =
|
|
76714
|
+
this.win = win3;
|
|
76420
76715
|
windows.set(this.sessionId, this);
|
|
76421
76716
|
try {
|
|
76422
|
-
|
|
76717
|
+
win3.setContentProtection(true);
|
|
76423
76718
|
} catch {
|
|
76424
76719
|
}
|
|
76425
|
-
await
|
|
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
|
|
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 =
|
|
76756
|
+
this.win = win3;
|
|
76462
76757
|
windows.set(this.sessionId, this);
|
|
76463
76758
|
try {
|
|
76464
|
-
|
|
76759
|
+
win3.setContentProtection(true);
|
|
76465
76760
|
} catch {
|
|
76466
76761
|
}
|
|
76467
|
-
await
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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 ??
|
|
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:
|
|
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.
|
|
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 (!
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-${
|
|
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
|
-
|
|
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
|
-
|
|
80899
|
+
log9("chat-agent: answered", event.jobId);
|
|
79690
80900
|
} catch (err2) {
|
|
79691
80901
|
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
79692
|
-
|
|
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
|
|
81092
|
+
import { join as join48 } from "node:path";
|
|
79883
81093
|
function rdeskAuditPath() {
|
|
79884
|
-
return
|
|
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
|
|
79896
|
-
import { join as
|
|
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
|
|
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(
|
|
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,
|
|
81211
|
+
bindBridge(0, log9);
|
|
80002
81212
|
}
|
|
80003
|
-
function bindBridge(i,
|
|
81213
|
+
function bindBridge(i, log9) {
|
|
80004
81214
|
if (stopping || wss)
|
|
80005
81215
|
return;
|
|
80006
81216
|
if (i >= BRIDGE_PORTS.length) {
|
|
80007
|
-
|
|
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
|
-
|
|
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,
|
|
81250
|
+
bindBridge(i + 1, log9);
|
|
80041
81251
|
} else
|
|
80042
|
-
|
|
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
|
|
81260
|
+
import { join as join50 } from "node:path";
|
|
80051
81261
|
|
|
80052
81262
|
// ../../packages/host-core/dist/vault/peer-verify.js
|
|
80053
|
-
import { execFile as
|
|
80054
|
-
import { promisify as
|
|
80055
|
-
var run2 =
|
|
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
|
|
81322
|
+
return join50(homedir6(), "Library", "Containers", APPEX_BUNDLE_ID, "Data");
|
|
80113
81323
|
}
|
|
80114
81324
|
function bridgeInfoPath() {
|
|
80115
|
-
return
|
|
81325
|
+
return join50(appexContainerData(), "Library", "Application Support", "tesseract-autofill-bridge.json");
|
|
80116
81326
|
}
|
|
80117
81327
|
var infoLoggedOnce = false;
|
|
80118
|
-
function writeBridgeInfo(port, token3,
|
|
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
|
-
|
|
81336
|
+
log9("autofill bridge-info SKIP: no appex container yet at", containerData);
|
|
80127
81337
|
}
|
|
80128
81338
|
return;
|
|
80129
81339
|
}
|
|
80130
|
-
const dir2 =
|
|
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
|
-
|
|
81345
|
+
log9("autofill bridge-info WROTE", port, "\u2192", dir2);
|
|
80136
81346
|
}
|
|
80137
81347
|
} catch (e) {
|
|
80138
81348
|
if (!infoLoggedOnce) {
|
|
80139
81349
|
infoLoggedOnce = true;
|
|
80140
|
-
|
|
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,
|
|
81442
|
+
function bind(i, log9) {
|
|
80233
81443
|
if (stopping2 || wss2)
|
|
80234
81444
|
return;
|
|
80235
81445
|
if (i >= BRIDGE_PORTS2.length) {
|
|
80236
|
-
|
|
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
|
-
|
|
80244
|
-
writeBridgeInfo(port, token2,
|
|
80245
|
-
infoTimer = setInterval(() => writeBridgeInfo(port, token2,
|
|
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,
|
|
81483
|
+
bind(i + 1, log9);
|
|
80274
81484
|
} else
|
|
80275
|
-
|
|
81485
|
+
log9("autofill bridge error", e?.message);
|
|
80276
81486
|
});
|
|
80277
81487
|
}
|
|
80278
|
-
function startAutofillBridge(
|
|
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,
|
|
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
|
|
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 =
|
|
80358
|
-
return ["Google/Chrome", "Google/Chrome Beta", "Google/Chrome Dev", "Google/Chrome Canary", "Chromium", "Microsoft Edge", "BraveSoftware/Brave-Browser", "Vivaldi", "Arc"].map((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 =
|
|
80363
|
-
return ["google-chrome", "google-chrome-beta", "chromium", "microsoft-edge", "BraveSoftware/Brave-Browser", "vivaldi"].map((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(
|
|
81575
|
+
function resolveBridge(log9) {
|
|
80366
81576
|
const candidates = [
|
|
80367
81577
|
process.env.CCB_COMPANION_HOST_JS,
|
|
80368
|
-
process.resourcesPath ?
|
|
81578
|
+
process.resourcesPath ? join51(process.resourcesPath, "companion-host", BRIDGE_BASENAME) : void 0
|
|
80369
81579
|
];
|
|
80370
81580
|
try {
|
|
80371
|
-
const here =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
81610
|
+
const log9 = opts.log ?? (() => void 0);
|
|
80401
81611
|
try {
|
|
80402
|
-
const bridge = resolveBridge(
|
|
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,
|
|
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 =
|
|
81632
|
+
const dir2 = join51(profile, "NativeMessagingHosts");
|
|
80423
81633
|
mkdirSync24(dir2, { recursive: true });
|
|
80424
|
-
writeFileSync23(
|
|
81634
|
+
writeFileSync23(join51(dir2, `${HOST_NAME}.json`), manifestJson);
|
|
80425
81635
|
wrote++;
|
|
80426
81636
|
} catch {
|
|
80427
81637
|
}
|
|
80428
81638
|
}
|
|
80429
|
-
|
|
81639
|
+
log9(`companion native-host: registered for ${wrote} browser(s) \u2192 ${launcher}`);
|
|
80430
81640
|
} catch (e) {
|
|
80431
|
-
|
|
81641
|
+
log9("companion native-host: registration failed", e.message);
|
|
80432
81642
|
}
|
|
80433
81643
|
}
|
|
80434
|
-
function registerWindows(daemonDir3, manifestJson,
|
|
81644
|
+
function registerWindows(daemonDir3, manifestJson, log9) {
|
|
80435
81645
|
try {
|
|
80436
|
-
const manifestPath =
|
|
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
|
-
|
|
81654
|
+
log9("companion native-host: registered (Windows registry)");
|
|
80445
81655
|
} catch (e) {
|
|
80446
|
-
|
|
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
|
|
81662
|
+
import { join as join52 } from "node:path";
|
|
80453
81663
|
function activityProfilePath() {
|
|
80454
|
-
return
|
|
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
|
|
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
|
-
|
|
81725
|
+
log9(`activity flushed (${Object.keys(profile.apps).length} apps tracked)`);
|
|
80516
81726
|
dirty = 0;
|
|
80517
81727
|
} catch (e) {
|
|
80518
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81780
|
+
log9("activity final flush failed", e instanceof Error ? e.message : String(e));
|
|
80571
81781
|
}
|
|
80572
|
-
|
|
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
|
|
80606
|
-
|
|
80607
|
-
const pidfile = () =>
|
|
80608
|
-
const statusFile = () =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80691
|
-
if (
|
|
80692
|
-
rivals.add(
|
|
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
|
-
|
|
80697
|
-
if (
|
|
80698
|
-
rivals.add(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
80771
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
80847
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82251
|
+
log9("token refresh rejected", r.status);
|
|
80861
82252
|
if (r.status === 401) {
|
|
80862
|
-
|
|
82253
|
+
log9("device token no longer valid \u2014 dropping to local-only on restart");
|
|
80863
82254
|
try {
|
|
80864
|
-
|
|
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
|
-
|
|
82267
|
+
log9("token refreshed");
|
|
80877
82268
|
}
|
|
80878
82269
|
} catch (e) {
|
|
80879
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
81011
|
-
startAutofillBridge(
|
|
81012
|
-
|
|
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
|
|
81038
|
-
import { dirname as
|
|
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
|
|
81043
|
-
import { promisify as
|
|
81044
|
-
var run3 =
|
|
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 = () =>
|
|
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 = () =>
|
|
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(
|
|
81112
|
-
mkdirSync27(
|
|
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
|
-
|
|
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
|
|
81164
|
-
import { dirname as
|
|
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 ||
|
|
81176
|
-
return
|
|
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(
|
|
81205
|
-
mkdirSync28(
|
|
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
|
-
|
|
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
|
|
81250
|
-
import { dirname as
|
|
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 = () =>
|
|
81254
|
-
const logFile = () =>
|
|
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(
|
|
81272
|
-
mkdirSync29(
|
|
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
|
-
|
|
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
|
|
82823
|
+
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
81338
82824
|
import { existsSync as existsSync32, rmSync as rmSync8 } from "node:fs";
|
|
81339
|
-
import { join as
|
|
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 ??
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83134
|
+
log8(`installed ${target}`);
|
|
81649
83135
|
resolve12(true);
|
|
81650
83136
|
} else {
|
|
81651
|
-
|
|
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
|
-
|
|
83150
|
+
log8(`update available: ${current} \u2192 ${latest}`);
|
|
81665
83151
|
const dist = await fetchVersionDist(latest);
|
|
81666
83152
|
if (!verifyNpmSignature(latest, dist?.integrity, dist?.sig)) {
|
|
81667
|
-
|
|
83153
|
+
log8(`REFUSING update to ${latest}: registry signature did not verify \u2014 staying on ${current}`);
|
|
81668
83154
|
return;
|
|
81669
83155
|
}
|
|
81670
|
-
|
|
83156
|
+
log8(`registry signature verified for ${latest} (keyid ${dist?.keyid ?? "?"})`);
|
|
81671
83157
|
if (await installVersion(latest)) {
|
|
81672
|
-
|
|
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
|
-
|
|
83167
|
+
log8("disabled via TESSERACT_AUTO_UPDATE/CCB_AUTO_UPDATE");
|
|
81682
83168
|
return;
|
|
81683
83169
|
}
|
|
81684
83170
|
if (!isReleaseVersion(current)) {
|
|
81685
|
-
|
|
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
|
|
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:
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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", [
|