replicas-computer 0.3.29 → 0.3.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +125 -64
  2. 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 createConnection3 } from "net";
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 createConnection2, createServer as createServer2 } from "net";
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) {
@@ -1128,6 +1130,10 @@ async function computerRecordStopCommand() {
1128
1130
  }
1129
1131
 
1130
1132
  // src/lib/chrome-session.ts
1133
+ function filterChromePages(pages, filter) {
1134
+ const has = (value, needle) => (value ?? "").toLowerCase().includes(needle.toLowerCase());
1135
+ return pages.filter((page) => (!filter.targetId || page.id === filter.targetId) && (!filter.title || has(page.title, filter.title)) && (!filter.url || has(page.url, filter.url)));
1136
+ }
1131
1137
  async function openChromeSession(webSocketDebuggerUrl) {
1132
1138
  const socket = new WebSocket(webSocketDebuggerUrl);
1133
1139
  const pending = /* @__PURE__ */ new Map();
@@ -1137,9 +1143,9 @@ async function openChromeSession(webSocketDebuggerUrl) {
1137
1143
  const fail2 = (error) => {
1138
1144
  if (closed) return;
1139
1145
  closed = true;
1140
- for (const request of pending.values()) {
1141
- clearTimeout(request.timeout);
1142
- request.reject(error);
1146
+ for (const request2 of pending.values()) {
1147
+ clearTimeout(request2.timeout);
1148
+ request2.reject(error);
1143
1149
  }
1144
1150
  pending.clear();
1145
1151
  socket.close();
@@ -1156,12 +1162,12 @@ async function openChromeSession(webSocketDebuggerUrl) {
1156
1162
  if (!isRecord(message)) return;
1157
1163
  if (typeof message.method === "string" && isRecord(message.params)) listeners.get(message.method)?.(message.params);
1158
1164
  if (typeof message.id !== "number") return;
1159
- const request = pending.get(message.id);
1160
- if (!request) return;
1161
- clearTimeout(request.timeout);
1165
+ const request2 = pending.get(message.id);
1166
+ if (!request2) return;
1167
+ clearTimeout(request2.timeout);
1162
1168
  pending.delete(message.id);
1163
1169
  const detail = isRecord(message.error) && typeof message.error.message === "string" ? message.error.message : void 0;
1164
- request.resolve(message.error ? { error: { message: detail } } : { result: isRecord(message.result) ? message.result : {} });
1170
+ request2.resolve(message.error ? { error: { message: detail } } : { result: isRecord(message.result) ? message.result : {} });
1165
1171
  });
1166
1172
  socket.addEventListener("error", () => fail2(new Error("Chrome DevTools WebSocket failed")));
1167
1173
  socket.addEventListener("close", () => fail2(new Error("Chrome DevTools WebSocket closed unexpectedly")));
@@ -1231,18 +1237,7 @@ async function getChromePages() {
1231
1237
  }
1232
1238
  async function selectChromePage(options = {}) {
1233
1239
  const pages = await getChromePages();
1234
- let matches = pages;
1235
- if (options.targetId) {
1236
- matches = matches.filter((page2) => page2.id === options.targetId);
1237
- }
1238
- if (options.title) {
1239
- const needle = options.title.toLowerCase();
1240
- matches = matches.filter((page2) => (page2.title ?? "").toLowerCase().includes(needle));
1241
- }
1242
- if (options.url) {
1243
- const needle = options.url.toLowerCase();
1244
- matches = matches.filter((page2) => (page2.url ?? "").toLowerCase().includes(needle));
1245
- }
1240
+ const matches = filterChromePages(pages, options);
1246
1241
  const index = options.page ? parseCoord(options.page, "--page") : 0;
1247
1242
  if (index < 0) fail("--page must be >= 0");
1248
1243
  const page = matches[index];
@@ -3007,10 +3002,68 @@ async function computerDragCommand(fx, fy, tx, ty) {
3007
3002
  console.log(`dragged (${fromX},${fromY}) -> (${toX},${toY})`);
3008
3003
  }
3009
3004
 
3010
- // src/commands/screen.ts
3005
+ // src/commands/mac.ts
3011
3006
  import { spawn as spawn3 } from "child_process";
3012
- import { closeSync, existsSync as existsSync5, mkdirSync as mkdirSync5, openSync, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync4, symlinkSync, writeFileSync as writeFileSync5 } from "fs";
3013
- import { createConnection, createServer } from "net";
3007
+ import { randomUUID } from "crypto";
3008
+ import { writeFileSync as writeFileSync5 } from "fs";
3009
+ import { createConnection } from "net";
3010
+ var REQUEST_TIMEOUT_MS = 12e4;
3011
+ function request(payload) {
3012
+ return new Promise((resolve5, reject) => {
3013
+ const socket = createConnection(DESKTOP_COMPUTER_SOCKET);
3014
+ let reply = "";
3015
+ 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."));
3016
+ socket.once("error", disconnected);
3017
+ socket.once("close", disconnected);
3018
+ socket.setTimeout(REQUEST_TIMEOUT_MS, () => {
3019
+ socket.destroy();
3020
+ reject(new Error(`The Mac's driver didn't answer within ${REQUEST_TIMEOUT_MS / 1e3} seconds.`));
3021
+ });
3022
+ socket.on("data", (chunk) => {
3023
+ reply += chunk.toString("utf8");
3024
+ const end = reply.indexOf("\n");
3025
+ if (end === -1) return;
3026
+ socket.destroy();
3027
+ try {
3028
+ resolve5(JSON.parse(reply.slice(0, end)));
3029
+ } catch (error) {
3030
+ reject(error);
3031
+ }
3032
+ });
3033
+ socket.write(`${JSON.stringify(payload)}
3034
+ `);
3035
+ });
3036
+ }
3037
+ async function showView() {
3038
+ const origin = `http://127.0.0.1:${DESKTOP_COMPUTER_VIEW_PORT}/`;
3039
+ if ((await getChromePages().catch(() => [])).some((page) => page.url?.startsWith(origin))) return;
3040
+ ensureServicesRunning();
3041
+ spawn3(CHROME_WRAPPER, ["--new-window", "--start-maximized", `--app=${origin}`], { env: withDisplay(), detached: true, stdio: "ignore" }).unref();
3042
+ }
3043
+ async function computerMacCommand(tool, json, options) {
3044
+ let args;
3045
+ try {
3046
+ args = JSON.parse(json ?? "{}");
3047
+ } catch {
3048
+ fail(`Arguments must be a JSON object, got: ${json}`);
3049
+ }
3050
+ if (!isRecord(args)) fail(`Arguments must be a JSON object, got: ${json}`);
3051
+ const session = options.session ?? `replicas-${randomUUID()}`;
3052
+ 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)));
3053
+ if (!options.session) await request({ method: "session_end", session_id: session, client_kind: "cli" }).catch(() => void 0);
3054
+ await showView().catch(() => void 0);
3055
+ if (!isRecord(reply) || reply.ok !== true || !isRecord(reply.result)) fail(`The Mac's driver refused: ${JSON.stringify(isRecord(reply) ? reply.error ?? reply : reply)}`);
3056
+ const { content, structuredContent } = reply.result;
3057
+ const blocks = Array.isArray(content) ? content.filter(isRecord) : [];
3058
+ const image = blocks.find((block) => block.type === "image" && typeof block.data === "string");
3059
+ if (image && options.screenshot) writeFileSync5(options.screenshot, Buffer.from(String(image.data), "base64"));
3060
+ console.log(structuredContent === void 0 ? blocks.filter((block) => block.type === "text").map((block) => block.text).join("\n") : JSON.stringify(structuredContent, null, 2));
3061
+ }
3062
+
3063
+ // src/commands/screen.ts
3064
+ import { spawn as spawn4 } from "child_process";
3065
+ import { closeSync, existsSync as existsSync5, mkdirSync as mkdirSync5, openSync, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync4, symlinkSync, writeFileSync as writeFileSync6 } from "fs";
3066
+ import { createConnection as createConnection2, createServer } from "net";
3014
3067
  import { homedir } from "os";
3015
3068
  import { join } from "path";
3016
3069
  import { setTimeout as sleep2 } from "timers/promises";
@@ -3026,7 +3079,7 @@ function ownsProcess(session, pid) {
3026
3079
  }
3027
3080
  function isListening(port2) {
3028
3081
  return new Promise((resolve5) => {
3029
- const socket = createConnection({ host: "127.0.0.1", port: port2 });
3082
+ const socket = createConnection2({ host: "127.0.0.1", port: port2 });
3030
3083
  const done = (listening) => {
3031
3084
  socket.destroy();
3032
3085
  resolve5(listening);
@@ -3050,7 +3103,7 @@ async function freePorts(count) {
3050
3103
  }
3051
3104
  function spawnLogged(bin, args, log) {
3052
3105
  const fd = openSync(log, "a");
3053
- const child = spawn3(bin, args, { detached: true, stdio: ["ignore", fd, fd] });
3106
+ const child = spawn4(bin, args, { detached: true, stdio: ["ignore", fd, fd] });
3054
3107
  closeSync(fd);
3055
3108
  child.unref();
3056
3109
  let exited = false;
@@ -3069,7 +3122,7 @@ async function waitForPort(port2, child) {
3069
3122
  }
3070
3123
  function readBanner(port2) {
3071
3124
  return new Promise((resolve5) => {
3072
- const socket = createConnection({ host: "127.0.0.1", port: port2 });
3125
+ const socket = createConnection2({ host: "127.0.0.1", port: port2 });
3073
3126
  let banner = "";
3074
3127
  const done = () => {
3075
3128
  socket.destroy();
@@ -3138,7 +3191,7 @@ async function openSession(device, dir, previous) {
3138
3191
  pids.push(bridge.pid);
3139
3192
  if (!await waitForPort(webPort, bridge)) abort(`Could not start the screen viewer: ${readFileSync5(bridgeLog, "utf8").trim() || "timed out"}`);
3140
3193
  const session = { webPort, tunnelPort, pids };
3141
- writeFileSync5(join(dir, "session.json"), JSON.stringify(session));
3194
+ writeFileSync6(join(dir, "session.json"), JSON.stringify(session));
3142
3195
  return session;
3143
3196
  }
3144
3197
  async function computerScreenCommand(device, options) {
@@ -3168,7 +3221,7 @@ async function showScreen(device, session) {
3168
3221
  const findPage = async () => (await getChromePages().catch(() => [])).find((page2) => page2.webSocketDebuggerUrl && page2.url?.startsWith(origin));
3169
3222
  let page = await findPage();
3170
3223
  if (!page) {
3171
- spawn3(CHROME_WRAPPER, ["--new-window", `--app=${origin}?device=${device}&requestFps=0`], { env: withDisplay(), detached: true, stdio: "ignore" }).unref();
3224
+ spawn4(CHROME_WRAPPER, ["--new-window", `--app=${origin}?device=${device}&requestFps=0`], { env: withDisplay(), detached: true, stdio: "ignore" }).unref();
3172
3225
  const deadline2 = Date.now() + CONNECT_TIMEOUT_MS;
3173
3226
  while (!page && Date.now() < deadline2) {
3174
3227
  await sleep2(250);
@@ -3263,7 +3316,7 @@ async function computerStatusCommand() {
3263
3316
  }
3264
3317
 
3265
3318
  // src/version.ts
3266
- var COMPUTER_USE_VERSION = "0.3.29";
3319
+ var COMPUTER_USE_VERSION = "0.3.31";
3267
3320
  var COMPUTER_USE_PROTOCOL_VERSION = 1;
3268
3321
 
3269
3322
  // src/program.ts
@@ -3301,6 +3354,7 @@ function createProgram() {
3301
3354
  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
3355
  program.command("drag <fromX> <fromY> <toX> <toY>").description("Drag between desktop coordinates").action((fromX, fromY, toX, toY) => computerDragCommand(fromX, fromY, toX, toY));
3303
3356
  program.command("launch <app> [args...]").description("Launch an application on the desktop").action((app, args) => computerLaunchCommand(app, args));
3357
+ 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
3358
  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
3359
  const record = program.command("record").description("Record the workspace display");
3306
3360
  record.command("start <path>").description("Start a recording").option("--fps <n>", "Frame rate. Default 60.").action((path, options) => computerRecordStartCommand(path, options));
@@ -3355,7 +3409,7 @@ function writeResult(socket, result) {
3355
3409
  async function hasSocketListener() {
3356
3410
  if (!existsSync6(COMPUTER_USE_SOCKET_PATH)) return false;
3357
3411
  return await new Promise((resolve5) => {
3358
- const socket = createConnection2(COMPUTER_USE_SOCKET_PATH);
3412
+ const socket = createConnection3(COMPUTER_USE_SOCKET_PATH);
3359
3413
  socket.setTimeout(500, () => socket.destroy());
3360
3414
  socket.once("connect", () => {
3361
3415
  socket.destroy();
@@ -3398,18 +3452,18 @@ async function runComputerDaemon() {
3398
3452
  if (rejected) return;
3399
3453
  socket.setTimeout(0);
3400
3454
  queue = queue.then(async () => {
3401
- let request;
3455
+ let request2;
3402
3456
  try {
3403
- request = JSON.parse(input);
3457
+ request2 = JSON.parse(input);
3404
3458
  } catch {
3405
3459
  writeResult(socket, { exitCode: 2, stdout: "", stderr: "Invalid computer-use request JSON\n" });
3406
3460
  return;
3407
3461
  }
3408
- if (!isDaemonRequest(request)) {
3462
+ if (!isDaemonRequest(request2)) {
3409
3463
  writeResult(socket, { exitCode: 2, stdout: "", stderr: "Invalid or incompatible computer-use request\n" });
3410
3464
  return;
3411
3465
  }
3412
- writeResult(socket, await runDirectCommand(request.argv, request.cwd));
3466
+ writeResult(socket, await runDirectCommand(request2.argv, request2.cwd));
3413
3467
  }).catch((error) => {
3414
3468
  writeResult(socket, { exitCode: 1, stdout: "", stderr: `${error instanceof Error ? error.message : String(error)}
3415
3469
  ` });
@@ -3454,7 +3508,7 @@ var ComputerDaemonUnavailableError = class extends Error {
3454
3508
  };
3455
3509
  function sendComputerRequest(argv) {
3456
3510
  return new Promise((resolve5, reject) => {
3457
- const socket = createConnection3(COMPUTER_USE_SOCKET_PATH);
3511
+ const socket = createConnection4(COMPUTER_USE_SOCKET_PATH);
3458
3512
  let response = "";
3459
3513
  let settled = false;
3460
3514
  const finish = (error, result) => {
@@ -3505,7 +3559,7 @@ async function runComputerClient(argv) {
3505
3559
  }
3506
3560
 
3507
3561
  // src/media-server.ts
3508
- import { spawn as spawn6 } from "child_process";
3562
+ import { spawn as spawn7 } from "child_process";
3509
3563
  import { createHash as createHash3 } from "crypto";
3510
3564
  import { createReadStream, existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync2 } from "fs";
3511
3565
  import { createServer as createServer3 } from "http";
@@ -3515,7 +3569,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
3515
3569
  import WebSocket2, { WebSocketServer } from "ws";
3516
3570
 
3517
3571
  // src/encoder.ts
3518
- import { spawn as spawn4 } from "child_process";
3572
+ import { spawn as spawn5 } from "child_process";
3519
3573
  import { createSocket } from "dgram";
3520
3574
  import { createInterface } from "readline";
3521
3575
 
@@ -3750,7 +3804,7 @@ function superviseEncoder(options) {
3750
3804
  const warmupFrames = active ? options.warmupFrames ?? 0 : 0;
3751
3805
  const command = options.command?.() ?? "ffmpeg";
3752
3806
  const args = options.args(socket.address().port, warmupFrames);
3753
- const child = spawn4(command, args, { env: options.env, stdio: [options.controllable?.(command) ? "pipe" : "ignore", options.onOutput ? "pipe" : "ignore", "pipe"] });
3807
+ const child = spawn5(command, args, { env: options.env, stdio: [options.controllable?.(command) ? "pipe" : "ignore", options.onOutput ? "pipe" : "ignore", "pipe"] });
3754
3808
  child.stdin?.on("error", () => {
3755
3809
  });
3756
3810
  child.stderr?.on("data", (chunk) => process.stderr.write(`[desktop-stream] ${chunk}`));
@@ -3932,7 +3986,11 @@ function readReplicasRuntimeEnv(readEnv, [namespacedKey, legacyKey]) {
3932
3986
  return readEnv(namespacedKey) ?? readEnv(legacyKey);
3933
3987
  }
3934
3988
  function parsePosixEnvFile(content) {
3989
+ return parsePosixEnv(content).values;
3990
+ }
3991
+ function parsePosixEnv(content) {
3935
3992
  const result = {};
3993
+ const unsetKeys = [];
3936
3994
  let pos = 0;
3937
3995
  while (pos < content.length) {
3938
3996
  let lineStart;
@@ -3945,6 +4003,9 @@ function parsePosixEnvFile(content) {
3945
4003
  }
3946
4004
  if (!content.startsWith("export ", lineStart)) {
3947
4005
  const nl2 = content.indexOf("\n", lineStart);
4006
+ if (content.startsWith("unset ", lineStart)) {
4007
+ unsetKeys.push(content.slice(lineStart + "unset ".length, nl2 === -1 ? void 0 : nl2).trim());
4008
+ }
3948
4009
  if (nl2 === -1) break;
3949
4010
  pos = nl2 + 1;
3950
4011
  continue;
@@ -3983,7 +4044,7 @@ function parsePosixEnvFile(content) {
3983
4044
  const nl = content.indexOf("\n", cursor);
3984
4045
  pos = nl === -1 ? content.length : nl + 1;
3985
4046
  }
3986
- return result;
4047
+ return { values: result, unsetKeys };
3987
4048
  }
3988
4049
 
3989
4050
  // src/ice-servers.ts
@@ -4674,7 +4735,7 @@ function superviseVideoEncoder(options) {
4674
4735
  }
4675
4736
 
4676
4737
  // src/input-bridge.ts
4677
- import { spawn as spawn5 } from "child_process";
4738
+ import { spawn as spawn6 } from "child_process";
4678
4739
  var scrollPixelsPerStep = 80;
4679
4740
  var namedKeys = {
4680
4741
  AltLeft: "Alt_L",
@@ -4855,7 +4916,7 @@ var InputBridge = class {
4855
4916
  }
4856
4917
  write(command) {
4857
4918
  if (!this.process || this.process.exitCode !== null || this.process.stdin.destroyed) {
4858
- const child = spawn5("xte", [], {
4919
+ const child = spawn6("xte", [], {
4859
4920
  env: { ...process.env, DISPLAY: this.display },
4860
4921
  stdio: ["pipe", "pipe", "pipe"]
4861
4922
  });
@@ -4956,7 +5017,7 @@ function drainDesktopTheme() {
4956
5017
  if (applied) appliedTheme = theme;
4957
5018
  drainDesktopTheme();
4958
5019
  };
4959
- const child = spawn6("bash", [SERVICES_SCRIPT, "--theme", theme], { detached: true, stdio: "ignore" });
5020
+ const child = spawn7("bash", [SERVICES_SCRIPT, "--theme", theme], { detached: true, stdio: "ignore" });
4960
5021
  child.unref();
4961
5022
  child.once("exit", (code) => finish(code === 0));
4962
5023
  child.once("error", () => finish(false));
@@ -5263,8 +5324,8 @@ async function runMediaServer() {
5263
5324
  for (const viewer of viewers.values()) viewer.rtc?.setBudget(budgetBps());
5264
5325
  syncEncoder();
5265
5326
  };
5266
- const resetLadder = (request) => {
5267
- rung = request.transport === "webrtc" && request.adaptive ? webrtcStartupRung : 0;
5327
+ const resetLadder = (request2) => {
5328
+ rung = request2.transport === "webrtc" && request2.adaptive ? webrtcStartupRung : 0;
5268
5329
  cleanTransportSeconds = 0;
5269
5330
  descents = [];
5270
5331
  failedRung = -1;
@@ -5692,8 +5753,8 @@ async function runMediaServer() {
5692
5753
  audioSampleRate: 48e3,
5693
5754
  audioChannels: 2
5694
5755
  };
5695
- const httpServer = createServer3((request, response) => {
5696
- const url = new URL(request.url || "/", "http://localhost");
5756
+ const httpServer = createServer3((request2, response) => {
5757
+ const url = new URL(request2.url || "/", "http://localhost");
5697
5758
  if (url.pathname === "/health" || url.pathname === "/telemetry") {
5698
5759
  const all = [...viewers.values()];
5699
5760
  const memory = process.memoryUsage();
@@ -5746,7 +5807,7 @@ async function runMediaServer() {
5746
5807
  return;
5747
5808
  }
5748
5809
  if (url.pathname === "/snapshot") {
5749
- const capture = spawn6("ffmpeg", [
5810
+ const capture = spawn7("ffmpeg", [
5750
5811
  "-hide_banner",
5751
5812
  "-loglevel",
5752
5813
  "error",
@@ -5803,20 +5864,20 @@ async function runMediaServer() {
5803
5864
  createReadStream(path).pipe(response);
5804
5865
  });
5805
5866
  const webSocketServer = new WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });
5806
- httpServer.on("upgrade", (request, socket, head) => {
5807
- const url = new URL(request.url || "/", "http://localhost");
5867
+ httpServer.on("upgrade", (request2, socket, head) => {
5868
+ const url = new URL(request2.url || "/", "http://localhost");
5808
5869
  if (url.pathname !== "/stream") {
5809
5870
  socket.destroy();
5810
5871
  return;
5811
5872
  }
5812
5873
  const streamRequest = parseStreamRequest(url.searchParams, defaultRequest);
5813
- void (webrtc && streamRequest.transport === "webrtc" ? currentIceServers() : Promise.resolve(void 0)).then((ice) => webSocketServer.handleUpgrade(request, socket, head, (client) => webSocketServer.emit("connection", client, streamRequest, url.searchParams.get("standby") === "1", ice)));
5874
+ 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
5875
  });
5815
- webSocketServer.on("connection", (socket, request, standby, ice) => {
5876
+ webSocketServer.on("connection", (socket, request2, standby, ice) => {
5816
5877
  const viewer = {
5817
5878
  socket,
5818
5879
  input: new InputBridge(display, width, height),
5819
- request,
5880
+ request: request2,
5820
5881
  standby,
5821
5882
  fallback: false,
5822
5883
  waitingForKeyframe: true,
@@ -5834,23 +5895,23 @@ async function runMediaServer() {
5834
5895
  rttFloorMs: 0,
5835
5896
  rttSamples: []
5836
5897
  };
5837
- if (viewers.size === 0) resetLadder(request);
5838
- if (request.profile !== activeProfile || request.adaptive !== adaptive) {
5839
- record("viewer request", `${request.profile} profile, ${request.adaptive ? "adaptive" : "pinned"} quality`);
5840
- activeProfile = request.profile;
5841
- adaptive = request.adaptive;
5898
+ if (viewers.size === 0) resetLadder(request2);
5899
+ if (request2.profile !== activeProfile || request2.adaptive !== adaptive) {
5900
+ record("viewer request", `${request2.profile} profile, ${request2.adaptive ? "adaptive" : "pinned"} quality`);
5901
+ activeProfile = request2.profile;
5902
+ adaptive = request2.adaptive;
5842
5903
  if (!adaptive) rung = 0;
5843
5904
  }
5844
5905
  viewers.set(socket, viewer);
5845
- record("viewer connected", `${request.transport} requested${standby ? " in standby" : ""}, ${viewers.size} connected`);
5906
+ record("viewer connected", `${request2.transport} requested${standby ? " in standby" : ""}, ${viewers.size} connected`);
5846
5907
  const reply = (message) => {
5847
5908
  if (socket.readyState === WebSocket2.OPEN) socket.send(JSON.stringify(message));
5848
5909
  };
5849
5910
  const fallbackProfile = profile() === "high444" ? "main" : profile();
5850
- const configMessage = { ...config, videoCodec: H264_PROFILES[fallbackProfile].codec, iceServers: ice?.browser, request };
5911
+ const configMessage = { ...config, videoCodec: H264_PROFILES[fallbackProfile].codec, iceServers: ice?.browser, request: request2 };
5851
5912
  reply(configMessage);
5852
5913
  if (ice) openWebRtc(viewer, reply, ice.native);
5853
- if (!viewer.rtc) enterFallback(viewer, request.transport === "webrtc" ? "webrtc unavailable" : "websocket requested");
5914
+ if (!viewer.rtc) enterFallback(viewer, request2.transport === "webrtc" ? "webrtc unavailable" : "websocket requested");
5854
5915
  ensureEncoders();
5855
5916
  socket.on("message", (raw, binary) => {
5856
5917
  if (binary) return;
@@ -5908,7 +5969,7 @@ async function runMediaServer() {
5908
5969
 
5909
5970
  // src/setup.ts
5910
5971
  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 writeFileSync6 } from "fs";
5972
+ import { accessSync, chmodSync as chmodSync2, constants, cpSync, existsSync as existsSync9, mkdirSync as mkdirSync7, readdirSync as readdirSync4, writeFileSync as writeFileSync7 } from "fs";
5912
5973
  import { dirname as dirname8, resolve as resolve4 } from "path";
5913
5974
  import { fileURLToPath as fileURLToPath3 } from "url";
5914
5975
  var dependencies = [
@@ -6053,13 +6114,13 @@ async function setupComputer(options = {}) {
6053
6114
  chmodSync2("/usr/local/bin/replicas-start-desktop-services", 493);
6054
6115
  chmodSync2("/usr/local/bin/replicas-chrome", 493);
6055
6116
  chmodSync2("/usr/local/bin/replicas-terminal", 493);
6056
- writeFileSync6("/etc/systemd/system/replicas-computer.service", serviceUnit, { mode: 420 });
6117
+ writeFileSync7("/etc/systemd/system/replicas-computer.service", serviceUnit, { mode: 420 });
6057
6118
  if (existsSync9("/run/systemd/system")) {
6058
6119
  const reload = spawnSync6("systemctl", ["daemon-reload"], { stdio: "pipe" });
6059
6120
  if (reload.status !== 0) throw new Error(`Failed to reload systemd: ${reload.stderr?.toString().trim() || `exit ${reload.status}`}`);
6060
6121
  }
6061
6122
  mkdirSync7("/usr/local/share/replicas-computer", { recursive: true });
6062
- writeFileSync6("/usr/local/share/replicas-computer/install.json", `${JSON.stringify({
6123
+ writeFileSync7("/usr/local/share/replicas-computer/install.json", `${JSON.stringify({
6063
6124
  version: COMPUTER_USE_VERSION,
6064
6125
  protocolVersion: COMPUTER_USE_PROTOCOL_VERSION,
6065
6126
  dependencies: found
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-computer",
3
- "version": "0.3.29",
3
+ "version": "0.3.31",
4
4
  "description": "Replicas workspace computer-use runtime",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",