replicas-computer 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,6 +39,8 @@ JSON
39
39
  touch "$MARKER"
40
40
  fi
41
41
 
42
+ # password-store skips a keyring D-Bus probe that stalls first launch for seconds
43
+ # on a headless image; the network flags keep first-run downloads off the critical path.
42
44
  exec /usr/bin/google-chrome-stable \
43
45
  --user-data-dir="$PROFILE_DIR" \
44
46
  --profile-directory=Default \
@@ -46,6 +48,10 @@ exec /usr/bin/google-chrome-stable \
46
48
  --no-default-browser-check \
47
49
  --disable-search-engine-choice-screen \
48
50
  --disable-sync \
51
+ --password-store=basic \
52
+ --disable-component-update \
53
+ --disable-background-networking \
54
+ --no-pings \
49
55
  --remote-debugging-address=127.0.0.1 \
50
56
  --remote-debugging-port="$DEBUG_PORT" \
51
57
  --start-maximized \
@@ -384,8 +384,17 @@ fi
384
384
 
385
385
  kill_pids "$(legacy_stream_websockify_pids)"
386
386
  clear_bridge_pid_if_unhealthy stream has_stream stream_listener_pids stream_pids
387
+ # Forwarded only when set, so the stream server keeps its own default.
387
388
  start_bg stream \
388
389
  "has_stream" \
389
- "exec env REPLICAS_DESKTOP_DISPLAY=:$DISPLAY_NUM REPLICAS_DESKTOP_WIDTH=$WIDTH REPLICAS_DESKTOP_HEIGHT=$HEIGHT REPLICAS_DESKTOP_STREAM_FPS=${REPLICAS_DESKTOP_STREAM_FPS:-60} REPLICAS_DESKTOP_STREAM_PORT=$STREAM_PORT REPLICAS_DESKTOP_STREAM_WEBROOT=${REPLICAS_DESKTOP_STREAM_WEBROOT:-$REPLICAS_DESKTOP_TEMPLATES/stream} replicas-computer stream"
390
+ "exec env REPLICAS_DESKTOP_DISPLAY=:$DISPLAY_NUM REPLICAS_DESKTOP_WIDTH=$WIDTH REPLICAS_DESKTOP_HEIGHT=$HEIGHT ${REPLICAS_DESKTOP_STREAM_FPS:+REPLICAS_DESKTOP_STREAM_FPS=$REPLICAS_DESKTOP_STREAM_FPS }REPLICAS_DESKTOP_STREAM_PORT=$STREAM_PORT REPLICAS_DESKTOP_STREAM_WEBROOT=${REPLICAS_DESKTOP_STREAM_WEBROOT:-$REPLICAS_DESKTOP_TEMPLATES/stream} replicas-computer stream"
391
+
392
+ # Takes Chrome's cold-start cost at boot instead of on the user's first click;
393
+ # later launches join this instance through the profile singleton.
394
+ if [ "${REPLICAS_DESKTOP_CHROME_PREWARM:-1}" = "1" ] && command -v replicas-chrome >/dev/null 2>&1; then
395
+ start_bg chrome-prewarm \
396
+ "curl -fsS --max-time 1 http://127.0.0.1:${REPLICAS_DESKTOP_CHROME_DEBUG_PORT:-9222}/json/version" \
397
+ "DISPLAY=:$DISPLAY_NUM REPLICAS_DESKTOP_CHROME_PROFILE_DIR=\"$CHROME_PROFILE_DIR\" exec replicas-chrome --no-startup-window"
398
+ fi
390
399
 
391
400
  exit 0
package/dist/index.js CHANGED
@@ -2694,11 +2694,13 @@ async function computerLaunchCommand(app, args) {
2694
2694
  const fullArgs = [...baseArgs.slice(1), ...args];
2695
2695
  const expectsNewChromePage = args.some((arg) => !arg.startsWith("-"));
2696
2696
  const existingChromePageIds = /* @__PURE__ */ new Set();
2697
+ let chromeAlreadyRunning = false;
2697
2698
  if (app === "chrome") {
2698
2699
  try {
2699
2700
  for (const page of await getChromePages()) {
2700
2701
  if (page.id) existingChromePageIds.add(page.id);
2701
2702
  }
2703
+ chromeAlreadyRunning = true;
2702
2704
  } catch {
2703
2705
  }
2704
2706
  }
@@ -2729,10 +2731,12 @@ async function computerLaunchCommand(app, args) {
2729
2731
  }
2730
2732
  }
2731
2733
  if (readyPage) break;
2732
- try {
2733
- if (child.pid) process.kill(child.pid, 0);
2734
- } catch {
2735
- fail(`Chrome exited before its desktop control channel became ready.`);
2734
+ if (!chromeAlreadyRunning) {
2735
+ try {
2736
+ if (child.pid) process.kill(child.pid, 0);
2737
+ } catch {
2738
+ fail(`Chrome exited before its desktop control channel became ready.`);
2739
+ }
2736
2740
  }
2737
2741
  await sleep(100);
2738
2742
  }
@@ -2921,7 +2925,7 @@ async function computerStatusCommand() {
2921
2925
  }
2922
2926
 
2923
2927
  // src/version.ts
2924
- var COMPUTER_USE_VERSION = "0.2.4";
2928
+ var COMPUTER_USE_VERSION = "0.2.6";
2925
2929
  var COMPUTER_USE_PROTOCOL_VERSION = 1;
2926
2930
 
2927
2931
  // src/program.ts
@@ -3165,6 +3169,7 @@ import { spawn as spawn4 } from "child_process";
3165
3169
  import { createSocket } from "dgram";
3166
3170
  import { createReadStream, existsSync as existsSync6, statSync as statSync2 } from "fs";
3167
3171
  import { createServer as createServer2 } from "http";
3172
+ import { availableParallelism } from "os";
3168
3173
  import { dirname as dirname6, extname, resolve as resolve2 } from "path";
3169
3174
  import { fileURLToPath } from "url";
3170
3175
 
@@ -3471,20 +3476,28 @@ var H264RtpAssembler = class {
3471
3476
  import WebSocket2, { WebSocketServer } from "ws";
3472
3477
  var width = Number.parseInt(process.env.REPLICAS_DESKTOP_WIDTH || String(DESKTOP_VIEWER_WIDTH), 10);
3473
3478
  var height = Number.parseInt(process.env.REPLICAS_DESKTOP_HEIGHT || String(DESKTOP_VIEWER_HEIGHT), 10);
3474
- var fps = Number.parseInt(process.env.REPLICAS_DESKTOP_STREAM_FPS || "60", 10);
3479
+ var cores = availableParallelism();
3480
+ var fps = Number.parseInt(process.env.REPLICAS_DESKTOP_STREAM_FPS || "", 10) || 60;
3481
+ var x264Preset = cores >= 4 ? "superfast" : "ultrafast";
3475
3482
  var port = Number.parseInt(process.env.REPLICAS_DESKTOP_STREAM_PORT || String(DESKTOP_STREAM_PORT), 10);
3483
+ var configuredTopKbps = (() => {
3484
+ const raw = process.env.REPLICAS_DESKTOP_STREAM_VIDEO_BITRATE?.trim();
3485
+ const match = raw?.match(/^(\d+(?:\.\d+)?)([MmKk]?)$/);
3486
+ if (!match) return 2e4;
3487
+ const value = Number.parseFloat(match[1]);
3488
+ return Math.max(1, Math.round(match[2].toLowerCase() === "m" ? value * 1e3 : value));
3489
+ })();
3476
3490
  var qualityLadder = [
3477
- { fps, bitrate: process.env.REPLICAS_DESKTOP_STREAM_VIDEO_BITRATE || "12M", crf: 18 },
3478
- { fps: Math.max(1, Math.round(fps / 2)), bitrate: "6M", crf: 20 },
3479
- { fps: Math.max(1, Math.round(fps / 2)), bitrate: "3M", crf: 23 },
3480
- { fps: Math.max(1, Math.round(fps / 4)), bitrate: "1200k", crf: 26 }
3491
+ { fps, kbps: configuredTopKbps, crf: 18 },
3492
+ { fps: Math.max(1, Math.round(fps / 2)), kbps: 6e3, crf: 20 },
3493
+ { fps: Math.max(1, Math.round(fps / 2)), kbps: 3e3, crf: 23 },
3494
+ { fps: Math.max(1, Math.round(fps / 4)), kbps: 1200, crf: 26 }
3481
3495
  ];
3482
3496
  var keyframeMs = 250;
3483
3497
  var probeIntervalMs = 250;
3484
3498
  var targetBacklogMs = 120;
3485
3499
  var congestedBacklogMs = 250;
3486
3500
  var congestedDecodeQueue = 3;
3487
- var probeTimeoutMs = 2e3;
3488
3501
  var fallbackBufferedBytes = 512 * 1024;
3489
3502
  var maximumBufferedBytes = 4 * 1024 * 1024;
3490
3503
  var display = process.env.REPLICAS_DESKTOP_DISPLAY?.replace(/^([^:])/, ":$1") || ":99";
@@ -3559,9 +3572,9 @@ async function runMediaServer() {
3559
3572
  let videoBytes = 0;
3560
3573
  let audioBytes = 0;
3561
3574
  const queueMs = (state) => {
3575
+ if (!state.probeAnswered) return 0;
3562
3576
  const outstanding = state.probeSentAt ? Date.now() - state.probeSentAt : 0;
3563
- if (!state.probeAnswered && outstanding > probeTimeoutMs) return 0;
3564
- return Math.max(state.probeAckedMs, outstanding);
3577
+ return Math.max(0, Math.max(state.probeAckedMs, outstanding) - state.baselineMs);
3565
3578
  };
3566
3579
  const broadcast = (packet, keyframe = false) => {
3567
3580
  for (const [client, state] of clients) {
@@ -3597,7 +3610,7 @@ async function runMediaServer() {
3597
3610
  audioBytes += rtp.payload.byteLength;
3598
3611
  broadcast(mediaPacket(3 /* Audio */, audioClock.microseconds(rtp.timestamp), rtp.payload));
3599
3612
  });
3600
- const superviseEncoder = (label, prepare, buildArgs) => {
3613
+ const superviseEncoder = (label, prepare, buildArgs, niceness = 0) => {
3601
3614
  let child;
3602
3615
  let retiring;
3603
3616
  let restart;
@@ -3609,7 +3622,8 @@ async function runMediaServer() {
3609
3622
  if (retiring) return;
3610
3623
  clearTimeout(restart);
3611
3624
  prepare();
3612
- const spawned = spawn4("ffmpeg", buildArgs(), { env: { ...process.env, DISPLAY: display }, stdio: ["ignore", "ignore", "pipe"] });
3625
+ const argv = niceness > 0 ? ["nice", "-n", String(niceness), "ffmpeg", ...buildArgs()] : ["ffmpeg", ...buildArgs()];
3626
+ const spawned = spawn4(argv[0], argv.slice(1), { env: { ...process.env, DISPLAY: display }, stdio: ["ignore", "ignore", "pipe"] });
3613
3627
  spawned.stderr?.on("data", (chunk) => process.stderr.write(`[desktop-stream] ${chunk}`));
3614
3628
  child = spawned;
3615
3629
  spawned.once("exit", (code, signal) => {
@@ -3697,7 +3711,7 @@ async function runMediaServer() {
3697
3711
  "-c:v",
3698
3712
  "libx264",
3699
3713
  "-preset",
3700
- "ultrafast",
3714
+ x264Preset,
3701
3715
  "-tune",
3702
3716
  "zerolatency",
3703
3717
  "-profile:v",
@@ -3717,9 +3731,9 @@ async function runMediaServer() {
3717
3731
  "-crf",
3718
3732
  String(quality.crf),
3719
3733
  "-maxrate",
3720
- quality.bitrate,
3734
+ `${quality.kbps}k`,
3721
3735
  "-bufsize",
3722
- "1M",
3736
+ `${Math.round(quality.kbps * keyframeMs / 1e3)}k`,
3723
3737
  "-x264-params",
3724
3738
  `repeat-headers=1:sliced-threads=1:slices=4:keyint=${keyframeInterval}:min-keyint=${keyframeInterval}:scenecut=0`,
3725
3739
  "-fps_mode",
@@ -3732,7 +3746,7 @@ async function runMediaServer() {
3732
3746
  "96",
3733
3747
  `rtp://127.0.0.1:${videoPort}?pkt_size=1200&connect=1&rtcpport=${videoPort}`
3734
3748
  ];
3735
- });
3749
+ }, 10);
3736
3750
  const audioEncoder = superviseEncoder("audio", () => audioClock.reset(), () => [
3737
3751
  "-hide_banner",
3738
3752
  "-loglevel",
@@ -3788,7 +3802,7 @@ async function runMediaServer() {
3788
3802
  rung = next;
3789
3803
  congestedSeconds = 0;
3790
3804
  comfortableSeconds = 0;
3791
- process.stderr.write(`[desktop-stream] quality rung ${rung} (${qualityLadder[rung].fps} fps, ${qualityLadder[rung].bitrate})
3805
+ process.stderr.write(`[desktop-stream] quality rung ${rung} (${qualityLadder[rung].fps} fps, ${qualityLadder[rung].kbps}k)
3792
3806
  `);
3793
3807
  videoEncoder.replace();
3794
3808
  };
@@ -3843,6 +3857,7 @@ async function runMediaServer() {
3843
3857
  audioBytes,
3844
3858
  clientStats: [...clients.values()].flatMap((client) => client.stats ? [client.stats] : []),
3845
3859
  clientQueueMs: [...clients.values()].map((client) => Math.round(queueMs(client))),
3860
+ clientBaselineMs: [...clients.values()].map((client) => Number.isFinite(client.baselineMs) ? Math.round(client.baselineMs) : null),
3846
3861
  qualityRung: rung,
3847
3862
  width,
3848
3863
  height,
@@ -3857,7 +3872,11 @@ async function runMediaServer() {
3857
3872
  response.end("Not found");
3858
3873
  return;
3859
3874
  }
3860
- response.setHeader("cache-control", "no-store");
3875
+ if (extname(path) === ".html") {
3876
+ startEncoders();
3877
+ if (clients.size === 0) stopEncodersSoon();
3878
+ }
3879
+ response.setHeader("cache-control", extname(path) === ".html" ? "no-store" : "private, max-age=3600");
3861
3880
  response.setHeader("content-type", contentTypes[extname(path)] || "application/octet-stream");
3862
3881
  createReadStream(path).pipe(response);
3863
3882
  });
@@ -3875,7 +3894,8 @@ async function runMediaServer() {
3875
3894
  input: new InputBridge(display, width, height),
3876
3895
  probeSentAt: 0,
3877
3896
  probeAckedMs: 0,
3878
- probeAnswered: false
3897
+ probeAnswered: false,
3898
+ baselineMs: Number.POSITIVE_INFINITY
3879
3899
  });
3880
3900
  client.send(JSON.stringify(config));
3881
3901
  startEncoders();
@@ -3895,6 +3915,7 @@ async function runMediaServer() {
3895
3915
  state.probeAckedMs = Date.now() - state.probeSentAt;
3896
3916
  state.probeSentAt = 0;
3897
3917
  state.probeAnswered = true;
3918
+ state.baselineMs = Math.min(state.baselineMs, state.probeAckedMs);
3898
3919
  } else if (message.type === "stats") {
3899
3920
  const state = clients.get(client);
3900
3921
  if (!state) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-computer",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Replicas workspace computer-use runtime",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",