replicas-computer 0.3.29 → 0.3.30
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/index.js +120 -52
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/client.ts
|
|
4
|
-
import { createConnection as
|
|
4
|
+
import { createConnection as createConnection4 } from "net";
|
|
5
5
|
|
|
6
6
|
// src/daemon.ts
|
|
7
7
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
8
8
|
import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, rmSync as rmSync5 } from "fs";
|
|
9
|
-
import { createConnection as
|
|
9
|
+
import { createConnection as createConnection3, createServer as createServer2 } from "net";
|
|
10
10
|
import { dirname as dirname5 } from "path";
|
|
11
11
|
|
|
12
12
|
// src/commands/desktop.ts
|
|
@@ -21,6 +21,8 @@ function isRecord(value) {
|
|
|
21
21
|
|
|
22
22
|
// ../shared/src/engine/environment.ts
|
|
23
23
|
var DESKTOP_STREAM_PORT = 6080;
|
|
24
|
+
var DESKTOP_COMPUTER_SOCKET = "/tmp/replicas-desktop-computer.sock";
|
|
25
|
+
var DESKTOP_COMPUTER_VIEW_PORT = 6091;
|
|
24
26
|
var DEVICES_DIR = ".ssh/replicas-devices";
|
|
25
27
|
var DEVICE_SCREEN_FILE_SUFFIX = ".screen.json";
|
|
26
28
|
function parseDeviceScreenState(value) {
|
|
@@ -1137,9 +1139,9 @@ async function openChromeSession(webSocketDebuggerUrl) {
|
|
|
1137
1139
|
const fail2 = (error) => {
|
|
1138
1140
|
if (closed) return;
|
|
1139
1141
|
closed = true;
|
|
1140
|
-
for (const
|
|
1141
|
-
clearTimeout(
|
|
1142
|
-
|
|
1142
|
+
for (const request2 of pending.values()) {
|
|
1143
|
+
clearTimeout(request2.timeout);
|
|
1144
|
+
request2.reject(error);
|
|
1143
1145
|
}
|
|
1144
1146
|
pending.clear();
|
|
1145
1147
|
socket.close();
|
|
@@ -1156,12 +1158,12 @@ async function openChromeSession(webSocketDebuggerUrl) {
|
|
|
1156
1158
|
if (!isRecord(message)) return;
|
|
1157
1159
|
if (typeof message.method === "string" && isRecord(message.params)) listeners.get(message.method)?.(message.params);
|
|
1158
1160
|
if (typeof message.id !== "number") return;
|
|
1159
|
-
const
|
|
1160
|
-
if (!
|
|
1161
|
-
clearTimeout(
|
|
1161
|
+
const request2 = pending.get(message.id);
|
|
1162
|
+
if (!request2) return;
|
|
1163
|
+
clearTimeout(request2.timeout);
|
|
1162
1164
|
pending.delete(message.id);
|
|
1163
1165
|
const detail = isRecord(message.error) && typeof message.error.message === "string" ? message.error.message : void 0;
|
|
1164
|
-
|
|
1166
|
+
request2.resolve(message.error ? { error: { message: detail } } : { result: isRecord(message.result) ? message.result : {} });
|
|
1165
1167
|
});
|
|
1166
1168
|
socket.addEventListener("error", () => fail2(new Error("Chrome DevTools WebSocket failed")));
|
|
1167
1169
|
socket.addEventListener("close", () => fail2(new Error("Chrome DevTools WebSocket closed unexpectedly")));
|
|
@@ -3007,10 +3009,68 @@ async function computerDragCommand(fx, fy, tx, ty) {
|
|
|
3007
3009
|
console.log(`dragged (${fromX},${fromY}) -> (${toX},${toY})`);
|
|
3008
3010
|
}
|
|
3009
3011
|
|
|
3010
|
-
// src/commands/
|
|
3012
|
+
// src/commands/mac.ts
|
|
3011
3013
|
import { spawn as spawn3 } from "child_process";
|
|
3012
|
-
import {
|
|
3013
|
-
import {
|
|
3014
|
+
import { randomUUID } from "crypto";
|
|
3015
|
+
import { writeFileSync as writeFileSync5 } from "fs";
|
|
3016
|
+
import { createConnection } from "net";
|
|
3017
|
+
var REQUEST_TIMEOUT_MS = 12e4;
|
|
3018
|
+
function request(payload) {
|
|
3019
|
+
return new Promise((resolve5, reject) => {
|
|
3020
|
+
const socket = createConnection(DESKTOP_COMPUTER_SOCKET);
|
|
3021
|
+
let reply = "";
|
|
3022
|
+
const disconnected = () => reject(new Error("No Mac is connected. Open this workspace in the Replicas desktop app on the Mac, with computer use turned on."));
|
|
3023
|
+
socket.once("error", disconnected);
|
|
3024
|
+
socket.once("close", disconnected);
|
|
3025
|
+
socket.setTimeout(REQUEST_TIMEOUT_MS, () => {
|
|
3026
|
+
socket.destroy();
|
|
3027
|
+
reject(new Error(`The Mac's driver didn't answer within ${REQUEST_TIMEOUT_MS / 1e3} seconds.`));
|
|
3028
|
+
});
|
|
3029
|
+
socket.on("data", (chunk) => {
|
|
3030
|
+
reply += chunk.toString("utf8");
|
|
3031
|
+
const end = reply.indexOf("\n");
|
|
3032
|
+
if (end === -1) return;
|
|
3033
|
+
socket.destroy();
|
|
3034
|
+
try {
|
|
3035
|
+
resolve5(JSON.parse(reply.slice(0, end)));
|
|
3036
|
+
} catch (error) {
|
|
3037
|
+
reject(error);
|
|
3038
|
+
}
|
|
3039
|
+
});
|
|
3040
|
+
socket.write(`${JSON.stringify(payload)}
|
|
3041
|
+
`);
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
async function showView() {
|
|
3045
|
+
const origin = `http://127.0.0.1:${DESKTOP_COMPUTER_VIEW_PORT}/`;
|
|
3046
|
+
if ((await getChromePages().catch(() => [])).some((page) => page.url?.startsWith(origin))) return;
|
|
3047
|
+
ensureServicesRunning();
|
|
3048
|
+
spawn3(CHROME_WRAPPER, ["--new-window", "--start-maximized", `--app=${origin}`], { env: withDisplay(), detached: true, stdio: "ignore" }).unref();
|
|
3049
|
+
}
|
|
3050
|
+
async function computerMacCommand(tool, json, options) {
|
|
3051
|
+
let args;
|
|
3052
|
+
try {
|
|
3053
|
+
args = JSON.parse(json ?? "{}");
|
|
3054
|
+
} catch {
|
|
3055
|
+
fail(`Arguments must be a JSON object, got: ${json}`);
|
|
3056
|
+
}
|
|
3057
|
+
if (!isRecord(args)) fail(`Arguments must be a JSON object, got: ${json}`);
|
|
3058
|
+
const session = options.session ?? `replicas-${randomUUID()}`;
|
|
3059
|
+
const reply = await request({ method: "call", name: tool, args: { ...args, session }, session_id: session, client_kind: "cli", observation_origin: "direct" }).catch((error) => fail(error instanceof Error ? error.message : String(error)));
|
|
3060
|
+
if (!options.session) await request({ method: "session_end", session_id: session, client_kind: "cli" }).catch(() => void 0);
|
|
3061
|
+
await showView().catch(() => void 0);
|
|
3062
|
+
if (!isRecord(reply) || reply.ok !== true || !isRecord(reply.result)) fail(`The Mac's driver refused: ${JSON.stringify(isRecord(reply) ? reply.error ?? reply : reply)}`);
|
|
3063
|
+
const { content, structuredContent } = reply.result;
|
|
3064
|
+
const blocks = Array.isArray(content) ? content.filter(isRecord) : [];
|
|
3065
|
+
const image = blocks.find((block) => block.type === "image" && typeof block.data === "string");
|
|
3066
|
+
if (image && options.screenshot) writeFileSync5(options.screenshot, Buffer.from(String(image.data), "base64"));
|
|
3067
|
+
console.log(structuredContent === void 0 ? blocks.filter((block) => block.type === "text").map((block) => block.text).join("\n") : JSON.stringify(structuredContent, null, 2));
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
// src/commands/screen.ts
|
|
3071
|
+
import { spawn as spawn4 } from "child_process";
|
|
3072
|
+
import { closeSync, existsSync as existsSync5, mkdirSync as mkdirSync5, openSync, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync4, symlinkSync, writeFileSync as writeFileSync6 } from "fs";
|
|
3073
|
+
import { createConnection as createConnection2, createServer } from "net";
|
|
3014
3074
|
import { homedir } from "os";
|
|
3015
3075
|
import { join } from "path";
|
|
3016
3076
|
import { setTimeout as sleep2 } from "timers/promises";
|
|
@@ -3026,7 +3086,7 @@ function ownsProcess(session, pid) {
|
|
|
3026
3086
|
}
|
|
3027
3087
|
function isListening(port2) {
|
|
3028
3088
|
return new Promise((resolve5) => {
|
|
3029
|
-
const socket =
|
|
3089
|
+
const socket = createConnection2({ host: "127.0.0.1", port: port2 });
|
|
3030
3090
|
const done = (listening) => {
|
|
3031
3091
|
socket.destroy();
|
|
3032
3092
|
resolve5(listening);
|
|
@@ -3050,7 +3110,7 @@ async function freePorts(count) {
|
|
|
3050
3110
|
}
|
|
3051
3111
|
function spawnLogged(bin, args, log) {
|
|
3052
3112
|
const fd = openSync(log, "a");
|
|
3053
|
-
const child =
|
|
3113
|
+
const child = spawn4(bin, args, { detached: true, stdio: ["ignore", fd, fd] });
|
|
3054
3114
|
closeSync(fd);
|
|
3055
3115
|
child.unref();
|
|
3056
3116
|
let exited = false;
|
|
@@ -3069,7 +3129,7 @@ async function waitForPort(port2, child) {
|
|
|
3069
3129
|
}
|
|
3070
3130
|
function readBanner(port2) {
|
|
3071
3131
|
return new Promise((resolve5) => {
|
|
3072
|
-
const socket =
|
|
3132
|
+
const socket = createConnection2({ host: "127.0.0.1", port: port2 });
|
|
3073
3133
|
let banner = "";
|
|
3074
3134
|
const done = () => {
|
|
3075
3135
|
socket.destroy();
|
|
@@ -3138,7 +3198,7 @@ async function openSession(device, dir, previous) {
|
|
|
3138
3198
|
pids.push(bridge.pid);
|
|
3139
3199
|
if (!await waitForPort(webPort, bridge)) abort(`Could not start the screen viewer: ${readFileSync5(bridgeLog, "utf8").trim() || "timed out"}`);
|
|
3140
3200
|
const session = { webPort, tunnelPort, pids };
|
|
3141
|
-
|
|
3201
|
+
writeFileSync6(join(dir, "session.json"), JSON.stringify(session));
|
|
3142
3202
|
return session;
|
|
3143
3203
|
}
|
|
3144
3204
|
async function computerScreenCommand(device, options) {
|
|
@@ -3168,7 +3228,7 @@ async function showScreen(device, session) {
|
|
|
3168
3228
|
const findPage = async () => (await getChromePages().catch(() => [])).find((page2) => page2.webSocketDebuggerUrl && page2.url?.startsWith(origin));
|
|
3169
3229
|
let page = await findPage();
|
|
3170
3230
|
if (!page) {
|
|
3171
|
-
|
|
3231
|
+
spawn4(CHROME_WRAPPER, ["--new-window", `--app=${origin}?device=${device}&requestFps=0`], { env: withDisplay(), detached: true, stdio: "ignore" }).unref();
|
|
3172
3232
|
const deadline2 = Date.now() + CONNECT_TIMEOUT_MS;
|
|
3173
3233
|
while (!page && Date.now() < deadline2) {
|
|
3174
3234
|
await sleep2(250);
|
|
@@ -3263,7 +3323,7 @@ async function computerStatusCommand() {
|
|
|
3263
3323
|
}
|
|
3264
3324
|
|
|
3265
3325
|
// src/version.ts
|
|
3266
|
-
var COMPUTER_USE_VERSION = "0.3.
|
|
3326
|
+
var COMPUTER_USE_VERSION = "0.3.30";
|
|
3267
3327
|
var COMPUTER_USE_PROTOCOL_VERSION = 1;
|
|
3268
3328
|
|
|
3269
3329
|
// src/program.ts
|
|
@@ -3301,6 +3361,7 @@ function createProgram() {
|
|
|
3301
3361
|
program.command("scroll <direction>").description("Scroll the desktop").option("--amount <n>", "Wheel ticks").option("--x <x>", "Pointer x").option("--y <y>", "Pointer y").action((direction, options) => computerScrollCommand(direction, options));
|
|
3302
3362
|
program.command("drag <fromX> <fromY> <toX> <toY>").description("Drag between desktop coordinates").action((fromX, fromY, toX, toY) => computerDragCommand(fromX, fromY, toX, toY));
|
|
3303
3363
|
program.command("launch <app> [args...]").description("Launch an application on the desktop").action((app, args) => computerLaunchCommand(app, args));
|
|
3364
|
+
program.command("mac <tool> [json]", { hidden: true }).description("Call a Cua Driver tool on the user's Mac, in the background, through the Replicas desktop app").option("--screenshot <path>", "Write the screenshot in the reply to this PNG").option("--session <name>", "Keep element tokens across calls under this session name").action((tool, json, options) => computerMacCommand(tool, json, options));
|
|
3304
3365
|
program.command("screen <device>", { hidden: true }).description("Show a device's screen in a window on this desktop, where the other commands drive it").option("--close", "Close the window and its connection").action((device, options) => computerScreenCommand(device, options));
|
|
3305
3366
|
const record = program.command("record").description("Record the workspace display");
|
|
3306
3367
|
record.command("start <path>").description("Start a recording").option("--fps <n>", "Frame rate. Default 60.").action((path, options) => computerRecordStartCommand(path, options));
|
|
@@ -3355,7 +3416,7 @@ function writeResult(socket, result) {
|
|
|
3355
3416
|
async function hasSocketListener() {
|
|
3356
3417
|
if (!existsSync6(COMPUTER_USE_SOCKET_PATH)) return false;
|
|
3357
3418
|
return await new Promise((resolve5) => {
|
|
3358
|
-
const socket =
|
|
3419
|
+
const socket = createConnection3(COMPUTER_USE_SOCKET_PATH);
|
|
3359
3420
|
socket.setTimeout(500, () => socket.destroy());
|
|
3360
3421
|
socket.once("connect", () => {
|
|
3361
3422
|
socket.destroy();
|
|
@@ -3398,18 +3459,18 @@ async function runComputerDaemon() {
|
|
|
3398
3459
|
if (rejected) return;
|
|
3399
3460
|
socket.setTimeout(0);
|
|
3400
3461
|
queue = queue.then(async () => {
|
|
3401
|
-
let
|
|
3462
|
+
let request2;
|
|
3402
3463
|
try {
|
|
3403
|
-
|
|
3464
|
+
request2 = JSON.parse(input);
|
|
3404
3465
|
} catch {
|
|
3405
3466
|
writeResult(socket, { exitCode: 2, stdout: "", stderr: "Invalid computer-use request JSON\n" });
|
|
3406
3467
|
return;
|
|
3407
3468
|
}
|
|
3408
|
-
if (!isDaemonRequest(
|
|
3469
|
+
if (!isDaemonRequest(request2)) {
|
|
3409
3470
|
writeResult(socket, { exitCode: 2, stdout: "", stderr: "Invalid or incompatible computer-use request\n" });
|
|
3410
3471
|
return;
|
|
3411
3472
|
}
|
|
3412
|
-
writeResult(socket, await runDirectCommand(
|
|
3473
|
+
writeResult(socket, await runDirectCommand(request2.argv, request2.cwd));
|
|
3413
3474
|
}).catch((error) => {
|
|
3414
3475
|
writeResult(socket, { exitCode: 1, stdout: "", stderr: `${error instanceof Error ? error.message : String(error)}
|
|
3415
3476
|
` });
|
|
@@ -3454,7 +3515,7 @@ var ComputerDaemonUnavailableError = class extends Error {
|
|
|
3454
3515
|
};
|
|
3455
3516
|
function sendComputerRequest(argv) {
|
|
3456
3517
|
return new Promise((resolve5, reject) => {
|
|
3457
|
-
const socket =
|
|
3518
|
+
const socket = createConnection4(COMPUTER_USE_SOCKET_PATH);
|
|
3458
3519
|
let response = "";
|
|
3459
3520
|
let settled = false;
|
|
3460
3521
|
const finish = (error, result) => {
|
|
@@ -3505,7 +3566,7 @@ async function runComputerClient(argv) {
|
|
|
3505
3566
|
}
|
|
3506
3567
|
|
|
3507
3568
|
// src/media-server.ts
|
|
3508
|
-
import { spawn as
|
|
3569
|
+
import { spawn as spawn7 } from "child_process";
|
|
3509
3570
|
import { createHash as createHash3 } from "crypto";
|
|
3510
3571
|
import { createReadStream, existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync2 } from "fs";
|
|
3511
3572
|
import { createServer as createServer3 } from "http";
|
|
@@ -3515,7 +3576,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
3515
3576
|
import WebSocket2, { WebSocketServer } from "ws";
|
|
3516
3577
|
|
|
3517
3578
|
// src/encoder.ts
|
|
3518
|
-
import { spawn as
|
|
3579
|
+
import { spawn as spawn5 } from "child_process";
|
|
3519
3580
|
import { createSocket } from "dgram";
|
|
3520
3581
|
import { createInterface } from "readline";
|
|
3521
3582
|
|
|
@@ -3750,7 +3811,7 @@ function superviseEncoder(options) {
|
|
|
3750
3811
|
const warmupFrames = active ? options.warmupFrames ?? 0 : 0;
|
|
3751
3812
|
const command = options.command?.() ?? "ffmpeg";
|
|
3752
3813
|
const args = options.args(socket.address().port, warmupFrames);
|
|
3753
|
-
const child =
|
|
3814
|
+
const child = spawn5(command, args, { env: options.env, stdio: [options.controllable?.(command) ? "pipe" : "ignore", options.onOutput ? "pipe" : "ignore", "pipe"] });
|
|
3754
3815
|
child.stdin?.on("error", () => {
|
|
3755
3816
|
});
|
|
3756
3817
|
child.stderr?.on("data", (chunk) => process.stderr.write(`[desktop-stream] ${chunk}`));
|
|
@@ -3932,7 +3993,11 @@ function readReplicasRuntimeEnv(readEnv, [namespacedKey, legacyKey]) {
|
|
|
3932
3993
|
return readEnv(namespacedKey) ?? readEnv(legacyKey);
|
|
3933
3994
|
}
|
|
3934
3995
|
function parsePosixEnvFile(content) {
|
|
3996
|
+
return parsePosixEnv(content).values;
|
|
3997
|
+
}
|
|
3998
|
+
function parsePosixEnv(content) {
|
|
3935
3999
|
const result = {};
|
|
4000
|
+
const unsetKeys = [];
|
|
3936
4001
|
let pos = 0;
|
|
3937
4002
|
while (pos < content.length) {
|
|
3938
4003
|
let lineStart;
|
|
@@ -3945,6 +4010,9 @@ function parsePosixEnvFile(content) {
|
|
|
3945
4010
|
}
|
|
3946
4011
|
if (!content.startsWith("export ", lineStart)) {
|
|
3947
4012
|
const nl2 = content.indexOf("\n", lineStart);
|
|
4013
|
+
if (content.startsWith("unset ", lineStart)) {
|
|
4014
|
+
unsetKeys.push(content.slice(lineStart + "unset ".length, nl2 === -1 ? void 0 : nl2).trim());
|
|
4015
|
+
}
|
|
3948
4016
|
if (nl2 === -1) break;
|
|
3949
4017
|
pos = nl2 + 1;
|
|
3950
4018
|
continue;
|
|
@@ -3983,7 +4051,7 @@ function parsePosixEnvFile(content) {
|
|
|
3983
4051
|
const nl = content.indexOf("\n", cursor);
|
|
3984
4052
|
pos = nl === -1 ? content.length : nl + 1;
|
|
3985
4053
|
}
|
|
3986
|
-
return result;
|
|
4054
|
+
return { values: result, unsetKeys };
|
|
3987
4055
|
}
|
|
3988
4056
|
|
|
3989
4057
|
// src/ice-servers.ts
|
|
@@ -4674,7 +4742,7 @@ function superviseVideoEncoder(options) {
|
|
|
4674
4742
|
}
|
|
4675
4743
|
|
|
4676
4744
|
// src/input-bridge.ts
|
|
4677
|
-
import { spawn as
|
|
4745
|
+
import { spawn as spawn6 } from "child_process";
|
|
4678
4746
|
var scrollPixelsPerStep = 80;
|
|
4679
4747
|
var namedKeys = {
|
|
4680
4748
|
AltLeft: "Alt_L",
|
|
@@ -4855,7 +4923,7 @@ var InputBridge = class {
|
|
|
4855
4923
|
}
|
|
4856
4924
|
write(command) {
|
|
4857
4925
|
if (!this.process || this.process.exitCode !== null || this.process.stdin.destroyed) {
|
|
4858
|
-
const child =
|
|
4926
|
+
const child = spawn6("xte", [], {
|
|
4859
4927
|
env: { ...process.env, DISPLAY: this.display },
|
|
4860
4928
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4861
4929
|
});
|
|
@@ -4956,7 +5024,7 @@ function drainDesktopTheme() {
|
|
|
4956
5024
|
if (applied) appliedTheme = theme;
|
|
4957
5025
|
drainDesktopTheme();
|
|
4958
5026
|
};
|
|
4959
|
-
const child =
|
|
5027
|
+
const child = spawn7("bash", [SERVICES_SCRIPT, "--theme", theme], { detached: true, stdio: "ignore" });
|
|
4960
5028
|
child.unref();
|
|
4961
5029
|
child.once("exit", (code) => finish(code === 0));
|
|
4962
5030
|
child.once("error", () => finish(false));
|
|
@@ -5263,8 +5331,8 @@ async function runMediaServer() {
|
|
|
5263
5331
|
for (const viewer of viewers.values()) viewer.rtc?.setBudget(budgetBps());
|
|
5264
5332
|
syncEncoder();
|
|
5265
5333
|
};
|
|
5266
|
-
const resetLadder = (
|
|
5267
|
-
rung =
|
|
5334
|
+
const resetLadder = (request2) => {
|
|
5335
|
+
rung = request2.transport === "webrtc" && request2.adaptive ? webrtcStartupRung : 0;
|
|
5268
5336
|
cleanTransportSeconds = 0;
|
|
5269
5337
|
descents = [];
|
|
5270
5338
|
failedRung = -1;
|
|
@@ -5692,8 +5760,8 @@ async function runMediaServer() {
|
|
|
5692
5760
|
audioSampleRate: 48e3,
|
|
5693
5761
|
audioChannels: 2
|
|
5694
5762
|
};
|
|
5695
|
-
const httpServer = createServer3((
|
|
5696
|
-
const url = new URL(
|
|
5763
|
+
const httpServer = createServer3((request2, response) => {
|
|
5764
|
+
const url = new URL(request2.url || "/", "http://localhost");
|
|
5697
5765
|
if (url.pathname === "/health" || url.pathname === "/telemetry") {
|
|
5698
5766
|
const all = [...viewers.values()];
|
|
5699
5767
|
const memory = process.memoryUsage();
|
|
@@ -5746,7 +5814,7 @@ async function runMediaServer() {
|
|
|
5746
5814
|
return;
|
|
5747
5815
|
}
|
|
5748
5816
|
if (url.pathname === "/snapshot") {
|
|
5749
|
-
const capture =
|
|
5817
|
+
const capture = spawn7("ffmpeg", [
|
|
5750
5818
|
"-hide_banner",
|
|
5751
5819
|
"-loglevel",
|
|
5752
5820
|
"error",
|
|
@@ -5803,20 +5871,20 @@ async function runMediaServer() {
|
|
|
5803
5871
|
createReadStream(path).pipe(response);
|
|
5804
5872
|
});
|
|
5805
5873
|
const webSocketServer = new WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });
|
|
5806
|
-
httpServer.on("upgrade", (
|
|
5807
|
-
const url = new URL(
|
|
5874
|
+
httpServer.on("upgrade", (request2, socket, head) => {
|
|
5875
|
+
const url = new URL(request2.url || "/", "http://localhost");
|
|
5808
5876
|
if (url.pathname !== "/stream") {
|
|
5809
5877
|
socket.destroy();
|
|
5810
5878
|
return;
|
|
5811
5879
|
}
|
|
5812
5880
|
const streamRequest = parseStreamRequest(url.searchParams, defaultRequest);
|
|
5813
|
-
void (webrtc && streamRequest.transport === "webrtc" ? currentIceServers() : Promise.resolve(void 0)).then((ice) => webSocketServer.handleUpgrade(
|
|
5881
|
+
void (webrtc && streamRequest.transport === "webrtc" ? currentIceServers() : Promise.resolve(void 0)).then((ice) => webSocketServer.handleUpgrade(request2, socket, head, (client) => webSocketServer.emit("connection", client, streamRequest, url.searchParams.get("standby") === "1", ice)));
|
|
5814
5882
|
});
|
|
5815
|
-
webSocketServer.on("connection", (socket,
|
|
5883
|
+
webSocketServer.on("connection", (socket, request2, standby, ice) => {
|
|
5816
5884
|
const viewer = {
|
|
5817
5885
|
socket,
|
|
5818
5886
|
input: new InputBridge(display, width, height),
|
|
5819
|
-
request,
|
|
5887
|
+
request: request2,
|
|
5820
5888
|
standby,
|
|
5821
5889
|
fallback: false,
|
|
5822
5890
|
waitingForKeyframe: true,
|
|
@@ -5834,23 +5902,23 @@ async function runMediaServer() {
|
|
|
5834
5902
|
rttFloorMs: 0,
|
|
5835
5903
|
rttSamples: []
|
|
5836
5904
|
};
|
|
5837
|
-
if (viewers.size === 0) resetLadder(
|
|
5838
|
-
if (
|
|
5839
|
-
record("viewer request", `${
|
|
5840
|
-
activeProfile =
|
|
5841
|
-
adaptive =
|
|
5905
|
+
if (viewers.size === 0) resetLadder(request2);
|
|
5906
|
+
if (request2.profile !== activeProfile || request2.adaptive !== adaptive) {
|
|
5907
|
+
record("viewer request", `${request2.profile} profile, ${request2.adaptive ? "adaptive" : "pinned"} quality`);
|
|
5908
|
+
activeProfile = request2.profile;
|
|
5909
|
+
adaptive = request2.adaptive;
|
|
5842
5910
|
if (!adaptive) rung = 0;
|
|
5843
5911
|
}
|
|
5844
5912
|
viewers.set(socket, viewer);
|
|
5845
|
-
record("viewer connected", `${
|
|
5913
|
+
record("viewer connected", `${request2.transport} requested${standby ? " in standby" : ""}, ${viewers.size} connected`);
|
|
5846
5914
|
const reply = (message) => {
|
|
5847
5915
|
if (socket.readyState === WebSocket2.OPEN) socket.send(JSON.stringify(message));
|
|
5848
5916
|
};
|
|
5849
5917
|
const fallbackProfile = profile() === "high444" ? "main" : profile();
|
|
5850
|
-
const configMessage = { ...config, videoCodec: H264_PROFILES[fallbackProfile].codec, iceServers: ice?.browser, request };
|
|
5918
|
+
const configMessage = { ...config, videoCodec: H264_PROFILES[fallbackProfile].codec, iceServers: ice?.browser, request: request2 };
|
|
5851
5919
|
reply(configMessage);
|
|
5852
5920
|
if (ice) openWebRtc(viewer, reply, ice.native);
|
|
5853
|
-
if (!viewer.rtc) enterFallback(viewer,
|
|
5921
|
+
if (!viewer.rtc) enterFallback(viewer, request2.transport === "webrtc" ? "webrtc unavailable" : "websocket requested");
|
|
5854
5922
|
ensureEncoders();
|
|
5855
5923
|
socket.on("message", (raw, binary) => {
|
|
5856
5924
|
if (binary) return;
|
|
@@ -5908,7 +5976,7 @@ async function runMediaServer() {
|
|
|
5908
5976
|
|
|
5909
5977
|
// src/setup.ts
|
|
5910
5978
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
5911
|
-
import { accessSync, chmodSync as chmodSync2, constants, cpSync, existsSync as existsSync9, mkdirSync as mkdirSync7, readdirSync as readdirSync4, writeFileSync as
|
|
5979
|
+
import { accessSync, chmodSync as chmodSync2, constants, cpSync, existsSync as existsSync9, mkdirSync as mkdirSync7, readdirSync as readdirSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
5912
5980
|
import { dirname as dirname8, resolve as resolve4 } from "path";
|
|
5913
5981
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5914
5982
|
var dependencies = [
|
|
@@ -6053,13 +6121,13 @@ async function setupComputer(options = {}) {
|
|
|
6053
6121
|
chmodSync2("/usr/local/bin/replicas-start-desktop-services", 493);
|
|
6054
6122
|
chmodSync2("/usr/local/bin/replicas-chrome", 493);
|
|
6055
6123
|
chmodSync2("/usr/local/bin/replicas-terminal", 493);
|
|
6056
|
-
|
|
6124
|
+
writeFileSync7("/etc/systemd/system/replicas-computer.service", serviceUnit, { mode: 420 });
|
|
6057
6125
|
if (existsSync9("/run/systemd/system")) {
|
|
6058
6126
|
const reload = spawnSync6("systemctl", ["daemon-reload"], { stdio: "pipe" });
|
|
6059
6127
|
if (reload.status !== 0) throw new Error(`Failed to reload systemd: ${reload.stderr?.toString().trim() || `exit ${reload.status}`}`);
|
|
6060
6128
|
}
|
|
6061
6129
|
mkdirSync7("/usr/local/share/replicas-computer", { recursive: true });
|
|
6062
|
-
|
|
6130
|
+
writeFileSync7("/usr/local/share/replicas-computer/install.json", `${JSON.stringify({
|
|
6063
6131
|
version: COMPUTER_USE_VERSION,
|
|
6064
6132
|
protocolVersion: COMPUTER_USE_PROTOCOL_VERSION,
|
|
6065
6133
|
dependencies: found
|