replicas-computer 0.3.17 → 0.3.19

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/README.md CHANGED
@@ -15,7 +15,7 @@ The versioned computer-use runtime for Replicas workspace images.
15
15
  - `src/viewer.ts` owns the optional noVNC fallback.
16
16
  - `assets/desktop/start-services.sh` owns the X11 and stream process lifecycle.
17
17
  - `xsettingsd` broadcasts theme changes to running GTK apps and Chrome. Chrome follows the dashboard theme without reopening tabs; launch it without a `GTK_THEME` override.
18
- - `assets/desktop/` owns the desktop theme: `brand-wallpaper.svg` (regenerate with `bun scripts/render-wallpaper.ts`), `tint2rc` dock, `openbox-theme/`, `gtk-settings.ini`, the `replicas-terminal` wrapper, `themes/` palettes that `start-services.sh` renders into those templates (`--theme light|dark` switches a running desktop), and the bundled fonts (static TTF instances of General Sans and Geist Mono; variable and WOFF2 builds render with wrong metrics in Pango and VTE).
18
+ - `assets/desktop/` owns the desktop theme: `wallpapers/` (a light and dark JPEG per scene; each new desktop picks a different scene), `tint2rc` dock, `openbox-theme/`, `gtk-settings.ini`, the `replicas-terminal` wrapper, `themes/` palettes that `start-services.sh` renders into those templates (`--theme light|dark` switches a running desktop), and the bundled fonts (static TTF instances of General Sans and Geist Mono; variable and WOFF2 builds render with wrong metrics in Pango and VTE).
19
19
 
20
20
  The viewer and agent command paths are independent. `start-services.sh` runs Xvfb, the compositor, PulseAudio and the stream server (and so the encoders it spawns) at nice −10 when `sudo` is available, so CPU load from the apps on the desktop slows those apps instead of stalling the picture of them. One on-demand encoder pair is shared by all viewers, stops when the last viewer disconnects, and restarts without reloading the page. A viewer out of sight (the dashboard keeps one connected while the Desktop tab is hidden) reports `standby`, detected with an `IntersectionObserver` because a hidden iframe keeps its last layout size; while every viewer is in standby the encoder runs at 5 fps on the 1.5 Mbps rung, and the first viewer to become visible returns it to full rate in place.
21
21
 
@@ -23,15 +23,15 @@ Chrome disables GPU compositing when no `/dev/dri/renderD*` device is available.
23
23
 
24
24
  ## How the stream works
25
25
 
26
- The Go capture process listens for XDamage updates, copies the current screen with XShm, converts RGB to YUV, and encodes H.264 through libx264 directly into RTP on loopback. The libyuv path copies and converts the changed bounding rectangle, aligned to preserve 4:2:0 chroma samples, while reusing unchanged pixels. It captures up to 60fps when the desktop changes and sends 5fps keepalives while idle. It reads control lines on stdin between frames: `keyframe` encodes the next frame as an IDR at once, `quality <crf> <kbps>` changes the rate control through `x264_encoder_reconfig`, and `fps <n>` changes the capture rate, so none of them restarts the process. FFmpeg supplies Opus audio and remains the video fallback when the native binary is missing or fails. The stream uses the desktop resolution, 1920×1080 by default. RGB capture is converted with the BT.709 matrix in limited range and the same matrix is signalled in the bitstream, so browsers reconstruct the colours the desktop drew (`REPLICAS_DESKTOP_STREAM_COLOR=legacy` restores the untagged BT.601 conversion). Frames are forwarded unchanged apart from a sequence/timestamp splice and a playout-delay header extension.
26
+ The Go capture process listens for XDamage updates, copies the current screen with XShm, converts RGB to YUV, and encodes H.264 through libx264 directly into RTP on loopback. The libyuv path copies and converts the changed bounding rectangle, aligned to preserve 4:2:0 chroma samples, while reusing unchanged pixels. It captures up to 60fps when the desktop changes and sends 5fps keepalives while idle. It reads control lines on stdin between frames: `keyframe` encodes the next frame as an IDR at once, `quality <crf> <kbps>` changes the rate control through `x264_encoder_reconfig`, and `fps <n>` changes the capture rate, so none of them restarts the process. x264 keeps the frame rate it opened with and sizes its rate cap by it, so the encoder always opens at the stream's full rate and `--capture-fps` sets the rate capture starts at. FFmpeg supplies Opus audio and remains the video fallback when the native binary is missing or fails. The stream uses the desktop resolution, 1920×1080 by default. RGB capture is converted with the BT.709 matrix in limited range and the same matrix is signalled in the bitstream, so browsers reconstruct the colours the desktop drew (`REPLICAS_DESKTOP_STREAM_COLOR=legacy` restores the untagged BT.601 conversion). Frames are forwarded unchanged apart from a sequence/timestamp splice and a playout-delay header extension.
27
27
 
28
28
  WebRTC is the default transport, with automatic WebSocket fallback. A viewer can choose `?transport=websocket`; `REPLICAS_DESKTOP_STREAM_TRANSPORT=websocket` changes the server default for viewers that do not choose. Dashboard URLs carry the selected transport explicitly, and saved transport preferences are preserved. The viewer's WebSocket to `/stream` is the control plane either way: it carries the server's SDP offer, the viewer's answer, trickled ICE candidates, keepalives and telemetry. Pointer moves ride an unreliable data channel. When the server advertises `inputRace`, clicks, keys, wheel events and held-input release race a direct copy against ordered WebSocket delivery. The input bridge accepts only the next action ID, ignoring duplicates and out-of-order direct copies; the socket fills gaps without SCTP retransmission delays or a reorder queue. Older peers keep their existing input path. The watchdog tracks decoded frames so CSS-hidden viewers do not fall back merely because the browser stops presenting video. An explicit server-side WebRTC failure, a network that cannot pass UDP, or six seconds without decoded video (at least three after the connection completes, so a slow network is not abandoned just as it connects) switches the viewer to the socket, where the server reassembles frames and WebCodecs decodes them in the browser. A viewer that fell back after a WebRTC failure keeps trying WebRTC in the background, 15 seconds after the fallback and then at doubling intervals up to five minutes: the server opens a new peer while the socket keeps carrying media, the new peer plays into an unseen element without offering 4:4:4, and once it decodes video the viewer moves its picture, audio and input to it and tells the server to stop the socket media (`retry-webrtc`, then `webrtc-ready`). The dashboard records each return as `desktop_stream_webrtc_recovered` with the time spent on the socket.
29
29
 
30
30
  The server makes the offer, so payload types and SSRCs are fixed and ffmpeg emits them directly. Four H.264 payload types are offered (High 4:4:4 Predictive, High, Main, Constrained Baseline); the encoder runs the best profile every connected viewer accepts, so one viewer without 4:4:4 support moves everyone to 4:2:0, and socket viewers or caps below 8 Mbps do the same. Two profiles select the encoder tuning: `balanced` (CRF 14, default chroma quantisation) and `text` (CRF 10, chroma QP offset −6); `?profile=` picks one and the most recent viewer's choice applies to the shared encoder. Quality adapts on a bitrate ladder down to 750 kbps that keeps resolution and frame rate and adds two CRF per rung under the default 20 Mbps cap; `?adapt=0` pins the top rung.
31
31
 
32
- Every video byte for a WebRTC viewer passes through `PacedSender`. libdatachannel's own pacer and NACK responder are not used because repairs bypassed the pacer and neither accounted for duplicates, so recovery traffic could exceed the link while fresh frames queued for seconds. The budget is 1.25× the rung's encoder cap and covers media, repairs and protective copies together: repairs take at most 30% of it over a rolling second, copies are sent only from idle budget while loss is observed and never exceed the original media bytes, and a queue that grows past 150 ms is cut back to whole frames a decoder can still follow (unsent delta frames go, the in-flight frame completes, a keyframe is requested and further deltas are dropped until it arrives). Sequence numbers are assigned as packets leave, so dropped frames never read as loss. Receiver PLI, NACK and REMB are parsed from RTCP. RTT growth (the lowest probe of the last second against the session's baseline, so a single late probe is not a queue), loss, jitter-buffer growth and stale-queue cuts drive the ladder down, and clean seconds drive it up. Loss, RTT growth and jitter count only while the encoder has sent over 500 kbps for three seconds: on a quiet screen they belong to the network, and no rung fixes them. Climbing back to the rung below the one a descent left takes a few clean seconds; retrying that rung waits 15, 30 and then 60 seconds as episodes repeat, and an episode is forgotten after three minutes. REMB is not a congestion vote: the receiver's estimate is bounded by what recently arrived and climbs by tens of kbps per second, so it trails every burst of motion on a quiet screen for a minute or more, and its delay detector reads the sender's pacing bursts on a quiet screen as overuse. It and the browser's available-bitrate estimate only bound how far a severe descent (heavy loss or a starved decoder) steps, and only while the stream has been sending more than they report. `scripts/probe-viewer.ts` drives the viewer page in a Chrome with remote debugging and prints what it measures and what the server decides each second; `scripts/probe-rtt-jitter.ts` feeds the controller a synthetic WebRTC viewer with jittery RTT probes.
32
+ Every video byte for a WebRTC viewer passes through `PacedSender`. libdatachannel's own pacer and NACK responder are not used because repairs bypassed the pacer and neither accounted for duplicates, so recovery traffic could exceed the link while fresh frames queued for seconds. The budget is 1.25× the rung's encoder cap and covers media, repairs and protective copies together: repairs take at most 30% of it over a rolling second, copies are sent only from idle budget while loss is observed and never exceed the original media bytes, and a queue that grows past 150 ms is cut back to whole frames a decoder can still follow (unsent delta frames go, the in-flight frame completes, a keyframe is requested and further deltas are dropped until it arrives). Sequence numbers are assigned as packets leave, so dropped frames never read as loss. Receiver PLI, NACK and REMB are parsed from RTCP. RTT growth (the lowest probe of the last second against the session's baseline, so a single late probe is not a queue), loss, jitter-buffer growth and stale-queue cuts drive the ladder down, and clean seconds drive it up. Loss, RTT growth and jitter count only while the encoder has sent over 500 kbps for three seconds: on a quiet screen they belong to the network, and no rung fixes them. Climbing back to the rung below the one a descent left takes a few clean seconds; retrying that rung waits 15, 30 and then 60 seconds as episodes repeat, and an episode is forgotten after three minutes. REMB is not a congestion vote: the receiver's estimate is bounded by what recently arrived and climbs by tens of kbps per second, so it trails every burst of motion on a quiet screen for a minute or more, and its delay detector reads the sender's pacing bursts on a quiet screen as overuse. It only bounds how far a severe descent (heavy loss, a starved decoder or a cut send queue) steps, and only once it has stopped climbing below what the stream sends. The browser's available-bitrate stat is the same estimate and is reported but never used. `scripts/probe-viewer.ts` drives the viewer page in a Chrome with remote debugging and prints what it measures and what the server decides each second; `scripts/probe-rtt-jitter.ts` feeds the controller a synthetic WebRTC viewer with jittery RTT probes.
33
33
 
34
- The superfast preset uses integer-pixel motion search to reduce encoder CPU while retaining CABAC compression; ultrafast is unchanged. Encoding uses at most four threads and one queued capture frame and skips rapid catch-up captures after CPU stalls. Native capture sends no periodic keyframes or refresh wave: an IDR follows within a frame when a viewer joins, a receiver sends PLI, a send queue is cut, the viewer's watchdog sees no video, or a socket viewer whose frames were withheld has drained its backlog or reset its decoder. Requests within a round trip of the last keyframe share it. Browsers join only at an IDR, so the SPS that intra refresh puts before a recovery point is not treated as a keyframe. Rung and standby changes are applied in place; a change of H.264 profile, chroma or preset starts a replacement, which discards two initial frames, emits a forced keyframe and then takes over. The FFmpeg fallback cannot be told to make a keyframe, so it keeps periodic intra refresh for WebRTC viewers, switches to 250 ms IDR intervals for socket viewers, and makes requested keyframes by replacement. Every new encoder has four seconds to produce joinable media. Native startup or replacement failures switch subsequent launches to FFmpeg; the active encoder continues until its replacement is ready.
34
+ The superfast preset uses integer-pixel motion search to reduce encoder CPU while retaining CABAC compression; ultrafast is unchanged. Encoding uses at most four threads and one queued capture frame and skips rapid catch-up captures after CPU stalls. Native capture sends no periodic keyframes or refresh wave: an IDR follows within a frame when a viewer joins, a receiver sends PLI, a send queue is cut, the viewer's watchdog sees no video, or a socket viewer whose frames were withheld has drained its backlog or reset its decoder. Requests within a round trip of the last keyframe share it. Browsers join only at an IDR, so the SPS that intra refresh puts before a recovery point is not treated as a keyframe. Rung and standby changes are applied in place; a change of H.264 profile, chroma or preset starts a replacement, which discards two initial frames, emits a forced keyframe and then takes over. Until then the outgoing encoder takes the new rate, and keyframe requests wait for the replacement's. The FFmpeg fallback cannot be told to make a keyframe, so it keeps periodic intra refresh for WebRTC viewers, switches to 250 ms IDR intervals for socket viewers, and makes requested keyframes by replacement. Every new encoder has four seconds to produce joinable media. Native startup or replacement failures switch subsequent launches to FFmpeg; the active encoder continues until its replacement is ready.
35
35
 
36
36
  The viewer exposes `?view=fit|native|pixel` (letterboxed, one source pixel per CSS pixel, one source pixel per device pixel) and `?diagnostics=1`, which shows transport and ICE route, negotiated codec profile, source/decoded/CSS/device dimensions, displayed and decoded frame rates, bitrate, RTT, loss, jitter buffer, the server's encoder settings, keyframe interval, CPU, pacer budget, queue, stale cuts, repairs and copies, the socket fallback's unsent bytes, withheld delta frames and decoder resets, and the latest server and viewer events. `globalThis.__replicasDesktop` carries the same state for tooling.
37
37
 
@@ -276,8 +276,9 @@ clear_bridge_pid_if_unhealthy() {
276
276
  }
277
277
 
278
278
  paint_wallpaper() {
279
- local wallpaper="${REPLICAS_DESKTOP_WALLPAPER:-$REPLICAS_DESKTOP_TEMPLATES/$WALLPAPER}"
280
- local rendered_wallpaper="$STATE_DIR/wallpaper-$THEME-${WIDTH}x${HEIGHT}.png"
279
+ local wallpaper="${REPLICAS_DESKTOP_WALLPAPER:-$REPLICAS_DESKTOP_TEMPLATES/wallpapers/$WALLPAPER-$THEME.jpg}"
280
+ local rendered_wallpaper="$STATE_DIR/wallpaper-$(basename "${wallpaper%.*}")-${WIDTH}x${HEIGHT}.png"
281
+ echo "$wallpaper" > "$STATE_DIR/wallpaper-path"
281
282
  if command -v ffmpeg >/dev/null 2>&1 && [ -f "$wallpaper" ] && { [ ! -f "$rendered_wallpaper" ] || [ "$wallpaper" -nt "$rendered_wallpaper" ]; }; then
282
283
  ffmpeg -y -loglevel error \
283
284
  -i "$wallpaper" \
@@ -306,6 +307,13 @@ THEME="${REQUESTED_THEME:-${PREVIOUS_THEME:-${REPLICAS_DESKTOP_THEME:-dark}}}"
306
307
  . "$REPLICAS_DESKTOP_TEMPLATES/themes/$THEME.env"
307
308
  echo "$THEME" > "$STATE_DIR/theme"
308
309
 
310
+ WALLPAPER="$(cat "$STATE_DIR/wallpaper" 2>/dev/null)"
311
+ if ! pgrep -f "Xvfb :$DISPLAY_NUM " >/dev/null 2>&1 || [ ! -f "$REPLICAS_DESKTOP_TEMPLATES/wallpapers/$WALLPAPER-$THEME.jpg" ]; then
312
+ mapfile -t scenes < <(ls "$REPLICAS_DESKTOP_TEMPLATES/wallpapers" 2>/dev/null | sed -n 's/-dark\.jpg$//p' | grep -vx "$WALLPAPER")
313
+ [ "${#scenes[@]}" -gt 0 ] && WALLPAPER="${scenes[RANDOM % ${#scenes[@]}]}"
314
+ echo "$WALLPAPER" > "$STATE_DIR/wallpaper"
315
+ fi
316
+
309
317
  render_template() {
310
318
  local expr="s|/usr/local/share/replicas/desktop|$REPLICAS_DESKTOP_TEMPLATES|g" key
311
319
  for key in $(grep -o '^[A-Z_]*' "$REPLICAS_DESKTOP_TEMPLATES/themes/$THEME.env"); do
@@ -1,4 +1,3 @@
1
- WALLPAPER='brand-wallpaper.svg'
2
1
  GTK_THEME_NAME='Yaru-viridian-dark'
3
2
  SURFACE='#171717'
4
3
  SURFACE_ALT='#121212'
@@ -1,4 +1,3 @@
1
- WALLPAPER='brand-wallpaper-light.svg'
2
1
  GTK_THEME_NAME='Yaru-viridian'
3
2
  SURFACE='#ffffff'
4
3
  SURFACE_ALT='#f2f3f1'
package/dist/index.js CHANGED
@@ -191,7 +191,7 @@ import { dirname as dirname4 } from "path";
191
191
  // src/commands/capture.ts
192
192
  import { spawnSync as spawnSync2 } from "child_process";
193
193
  import { createHash } from "crypto";
194
- import { closeSync, copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, readSync, rmSync, writeFileSync } from "fs";
194
+ import { closeSync, copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, openSync, readdirSync, readFileSync as readFileSync2, readSync, rmSync, writeFileSync } from "fs";
195
195
  import { dirname as dirname2 } from "path";
196
196
  var PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
197
197
  function readPngDimensions(filePath) {
@@ -207,18 +207,16 @@ function readPngDimensions(filePath) {
207
207
  closeSync(fd);
208
208
  }
209
209
  }
210
- function brandSvgPath() {
211
- const dir = process.env.REPLICAS_DESKTOP_TEMPLATES || "/usr/local/share/replicas/desktop";
212
- return `${dir}/brand-wallpaper.svg`;
213
- }
214
- function loadBrandSvg(canvasW, canvasH) {
215
- const path = brandSvgPath();
216
- if (!existsSync2(path)) {
217
- fail(
218
- `Brand wallpaper SVG missing at ${path}. The workspace image is out of date \u2014 \`replicas-computer setup\` must install brand-wallpaper.svg at $REPLICAS_DESKTOP_TEMPLATES.`
219
- );
210
+ function currentWallpaper() {
211
+ const recorded = `${STATE_DIR}/wallpaper-path`;
212
+ const path = existsSync2(recorded) ? readFileSync2(recorded, "utf8").trim() : "";
213
+ if (path && existsSync2(path)) return path;
214
+ const dir = `${process.env.REPLICAS_DESKTOP_TEMPLATES || "/usr/local/share/replicas/desktop"}/wallpapers`;
215
+ const fallback = existsSync2(dir) ? readdirSync(dir).find((name) => name.endsWith("-dark.jpg")) : void 0;
216
+ if (!fallback) {
217
+ fail(`Desktop wallpapers missing at ${dir}. The workspace image is out of date \u2014 \`replicas-computer setup\` must install them.`);
220
218
  }
221
- return readFileSync2(path, "utf8").replace(/(<svg[^>]*\s)width="\d+"/, `$1width="${canvasW}"`).replace(/(<svg[^>]*\s)height="\d+"/, `$1height="${canvasH}"`);
219
+ return `${dir}/${fallback}`;
222
220
  }
223
221
  var BRAND_PAD_FRACTION = 0.06;
224
222
  var SCREENSHOT_CORNER_FRACTION = 0.022;
@@ -284,7 +282,6 @@ async function computerScreenshotCommand(path, options = {}) {
284
282
  mkdirSync2(dirname2(target), { recursive: true });
285
283
  const stamp = `${process.pid}-${Date.now()}`;
286
284
  const rawPath = `/tmp/replicas-screenshot-${stamp}.raw.png`;
287
- const svgPath = `/tmp/replicas-screenshot-${stamp}.brand.svg`;
288
285
  const maskPath = `/tmp/replicas-screenshot-${stamp}.mask.svg`;
289
286
  const shadowPath = `/tmp/replicas-screenshot-${stamp}.shadow.svg`;
290
287
  const gridPath = `/tmp/replicas-screenshot-${stamp}.grid.svg`;
@@ -313,7 +310,6 @@ async function computerScreenshotCommand(path, options = {}) {
313
310
  const shadowMargin = shadowSigma * 3;
314
311
  const shadowW = width2 + shadowMargin * 2;
315
312
  const shadowH = height2 + shadowMargin * 2;
316
- writeFileSync(svgPath, loadBrandSvg(canvasW, canvasH));
317
313
  writeFileSync(
318
314
  maskPath,
319
315
  SCREENSHOT_MASK_TEMPLATE.replace(/__W__/g, String(width2)).replace(/__H__/g, String(height2)).replace(/__R__/g, String(cornerR))
@@ -330,7 +326,7 @@ async function computerScreenshotCommand(path, options = {}) {
330
326
  "-loglevel",
331
327
  "error",
332
328
  "-i",
333
- svgPath,
329
+ currentWallpaper(),
334
330
  "-i",
335
331
  rawPath,
336
332
  "-i",
@@ -338,7 +334,7 @@ async function computerScreenshotCommand(path, options = {}) {
338
334
  "-i",
339
335
  shadowPath,
340
336
  "-filter_complex",
341
- `[2:v]format=rgba,alphaextract[mask];[3:v]format=rgba,colorchannelmixer=rr=0:gg=0:bb=0:aa=${SHADOW_ALPHA},gblur=sigma=${shadowSigma}[shadow];[1:v]format=rgba[scr];[scr][mask]alphamerge[rounded];[0:v]format=rgba[bg];[bg][shadow]overlay=${padX - shadowMargin}:${padY - shadowMargin + shadowOffsetY}:format=auto[bg_shadow];[bg_shadow][rounded]overlay=${padX}:${padY}:format=auto`,
337
+ `[2:v]format=rgba,alphaextract[mask];[3:v]format=rgba,colorchannelmixer=rr=0:gg=0:bb=0:aa=${SHADOW_ALPHA},gblur=sigma=${shadowSigma}[shadow];[1:v]format=rgba[scr];[scr][mask]alphamerge[rounded];[0:v]scale=${canvasW}:${canvasH}:force_original_aspect_ratio=increase,crop=${canvasW}:${canvasH},gblur=sigma=${shadowSigma * 2},eq=brightness=-0.12,format=rgba[bg];[bg][shadow]overlay=${padX - shadowMargin}:${padY - shadowMargin + shadowOffsetY}:format=auto[bg_shadow];[bg_shadow][rounded]overlay=${padX}:${padY}:format=auto`,
342
338
  "-frames:v",
343
339
  "1",
344
340
  "-update",
@@ -352,7 +348,6 @@ async function computerScreenshotCommand(path, options = {}) {
352
348
  }
353
349
  } finally {
354
350
  rmSync(rawPath, { force: true });
355
- rmSync(svgPath, { force: true });
356
351
  rmSync(maskPath, { force: true });
357
352
  rmSync(shadowPath, { force: true });
358
353
  rmSync(gridPath, { force: true });
@@ -2937,7 +2932,7 @@ async function computerStatusCommand() {
2937
2932
  }
2938
2933
 
2939
2934
  // src/version.ts
2940
- var COMPUTER_USE_VERSION = "0.3.17";
2935
+ var COMPUTER_USE_VERSION = "0.3.19";
2941
2936
  var COMPUTER_USE_PROTOCOL_VERSION = 1;
2942
2937
 
2943
2938
  // src/program.ts
@@ -3179,7 +3174,7 @@ async function runComputerClient(argv) {
3179
3174
  // src/media-server.ts
3180
3175
  import { spawn as spawn5 } from "child_process";
3181
3176
  import { createHash as createHash3 } from "crypto";
3182
- import { createReadStream, existsSync as existsSync7, readdirSync, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
3177
+ import { createReadStream, existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
3183
3178
  import { createServer as createServer2 } from "http";
3184
3179
  import { availableParallelism as availableParallelism2, loadavg } from "os";
3185
3180
  import { dirname as dirname7, extname, resolve as resolve3 } from "path";
@@ -3478,13 +3473,21 @@ function superviseEncoder(options) {
3478
3473
  if (active || candidate) return;
3479
3474
  await spawnCandidate();
3480
3475
  });
3481
- const replace = () => enqueue(async () => {
3482
- if (!wanted) return;
3483
- clearTimeout(restart);
3484
- clearTimeout(candidateDeadline);
3485
- retire(candidate);
3486
- await spawnCandidate();
3487
- });
3476
+ let queuedReplacements = 0;
3477
+ const replace = () => {
3478
+ queuedReplacements += 1;
3479
+ return enqueue(async () => {
3480
+ try {
3481
+ if (!wanted) return;
3482
+ clearTimeout(restart);
3483
+ clearTimeout(candidateDeadline);
3484
+ retire(candidate);
3485
+ await spawnCandidate();
3486
+ } finally {
3487
+ queuedReplacements -= 1;
3488
+ }
3489
+ });
3490
+ };
3488
3491
  const stop = () => enqueue(async () => {
3489
3492
  wanted = false;
3490
3493
  clearTimeout(restart);
@@ -3507,7 +3510,7 @@ function superviseEncoder(options) {
3507
3510
  stop,
3508
3511
  control,
3509
3512
  running: () => Boolean(active && alive(active.child)),
3510
- replacing: () => Boolean(candidate),
3513
+ replacing: () => Boolean(candidate) || queuedReplacements > 0,
3511
3514
  generation: () => active?.id ?? 0,
3512
3515
  pid: () => active?.child.pid ?? 0,
3513
3516
  command: () => active?.child.spawnfile,
@@ -4200,6 +4203,8 @@ function superviseVideoEncoder(options) {
4200
4203
  "--height",
4201
4204
  String(height2),
4202
4205
  "--fps",
4206
+ String(options.maxFps),
4207
+ "--capture-fps",
4203
4208
  String(fps2),
4204
4209
  "--threads",
4205
4210
  String(Math.min(4, availableParallelism())),
@@ -4807,6 +4812,7 @@ async function runMediaServer() {
4807
4812
  width,
4808
4813
  height,
4809
4814
  colorMode,
4815
+ maxFps: fps,
4810
4816
  params: encoderParams,
4811
4817
  onPacket: onVideoPacket,
4812
4818
  record
@@ -4882,17 +4888,16 @@ async function runMediaServer() {
4882
4888
  }
4883
4889
  if (description !== encoderDescription) record("encoder", description);
4884
4890
  encoderDescription = description;
4885
- if (signature !== encoderSignature) {
4891
+ const replacing = signature !== encoderSignature;
4892
+ if (replacing) {
4886
4893
  encoderSignature = signature;
4887
- encoderLive = live;
4888
4894
  lastSwapAt = Date.now();
4889
4895
  void videoEncoder.replace();
4890
- return true;
4891
4896
  }
4892
4897
  if (live.crf !== encoderLive.crf || live.kbps !== encoderLive.kbps) videoEncoder.control(`quality ${live.crf} ${live.kbps}`);
4893
4898
  if (live.fps !== encoderLive.fps) videoEncoder.control(`fps ${live.fps}`);
4894
4899
  encoderLive = live;
4895
- return false;
4900
+ return replacing;
4896
4901
  };
4897
4902
  const syncEncoder = () => videoWanted && viewers.size > 0 && ensureEncoders();
4898
4903
  const stopEncodersSoon = () => {
@@ -4974,12 +4979,9 @@ async function runMediaServer() {
4974
4979
  congestedSeconds = 0;
4975
4980
  if (!adaptive || now - lastSevereDescentAt < 1e3) return;
4976
4981
  lastSevereDescentAt = now;
4977
- const sent = Math.min(...recentVideoKbps);
4978
4982
  const estimates = [...viewers.values()].flatMap((viewer) => {
4979
- const available = viewer.stats && now - viewer.stats.at < 750 ? viewer.stats.availableKbps ?? 0 : 0;
4980
- const reported = sent > 500 && available < sent * 0.9 ? available : 0;
4981
- const remb = rembBelowSent(viewer, now) ? freshRemb(viewer, now) ?? 0 : 0;
4982
- return [...reported > 0 ? [reported] : [], ...remb > 0 ? [remb] : []];
4983
+ const remb = rembBelowSent(viewer, now) ? freshRemb(viewer, now) : void 0;
4984
+ return remb ? [remb] : [];
4983
4985
  });
4984
4986
  const rungs = ladder();
4985
4987
  const estimatedRung = estimates.length ? rungs.findIndex((quality) => quality.kbps <= Math.min(...estimates) * 0.8) : rung + 2;
@@ -5424,7 +5426,7 @@ async function runMediaServer() {
5424
5426
  if (webrtc) void currentIceServers();
5425
5427
  response.setHeader("cache-control", "no-store");
5426
5428
  const digest = createHash3("sha1");
5427
- for (const name of readdirSync(webroot).filter((file) => /\.(js|css)$/.test(file)).sort()) digest.update(readFileSync7(resolve3(webroot, name)));
5429
+ for (const name of readdirSync2(webroot).filter((file) => /\.(js|css)$/.test(file)).sort()) digest.update(readFileSync7(resolve3(webroot, name)));
5428
5430
  response.end(readFileSync7(path, "utf8").replaceAll("__VERSION__", `${COMPUTER_USE_VERSION}-${digest.digest("hex").slice(0, 8)}`));
5429
5431
  return;
5430
5432
  }
@@ -5537,7 +5539,7 @@ async function runMediaServer() {
5537
5539
 
5538
5540
  // src/setup.ts
5539
5541
  import { spawnSync as spawnSync6 } from "child_process";
5540
- import { accessSync, chmodSync as chmodSync2, constants, cpSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
5542
+ import { accessSync, chmodSync as chmodSync2, constants, cpSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readdirSync as readdirSync3, writeFileSync as writeFileSync5 } from "fs";
5541
5543
  import { dirname as dirname8, resolve as resolve4 } from "path";
5542
5544
  import { fileURLToPath as fileURLToPath3 } from "url";
5543
5545
  var dependencies = [
@@ -5660,7 +5662,7 @@ async function setupComputer(options = {}) {
5660
5662
  cpSync(assets, "/usr/local/share/replicas/desktop", { recursive: true, force: true });
5661
5663
  const fontRoot = "/usr/local/share/fonts/replicas";
5662
5664
  mkdirSync6(fontRoot, { recursive: true });
5663
- for (const file of readdirSync2(assets).filter((name) => name.endsWith(".ttf"))) cpSync(resolve4(assets, file), resolve4(fontRoot, file), { force: true });
5665
+ for (const file of readdirSync3(assets).filter((name) => name.endsWith(".ttf"))) cpSync(resolve4(assets, file), resolve4(fontRoot, file), { force: true });
5664
5666
  spawnSync6("fc-cache", ["-f", fontRoot], { stdio: "ignore" });
5665
5667
  const viewerRoot = "/usr/local/share/replicas/desktop/novnc";
5666
5668
  mkdirSync6(viewerRoot, { recursive: true });
@@ -1,7 +1,7 @@
1
1
  package capture
2
2
 
3
3
  type Options struct {
4
- Width, Height, FPS, Threads, CRF, Kbps int
4
+ Width, Height, FPS, CaptureFPS, Threads, CRF, Kbps int
5
5
  KeyframeMS, IntraRefresh, ChromaQP, Warmup, Payload, BT709 int
6
6
  Preset, Profile, URL, SSRC string
7
7
  }
package/native/main.go CHANGED
@@ -2,6 +2,7 @@ package main
2
2
 
3
3
  import (
4
4
  "bytes"
5
+ "cmp"
5
6
  "fmt"
6
7
  "os"
7
8
  "os/signal"
@@ -59,7 +60,10 @@ func run(o capture.Options) error {
59
60
  }
60
61
  started := capture.NowMicros()
61
62
  last, lastKeyframe, reportAt := started-1000000, started-1000000, started
62
- interval := int64((1000000 + o.FPS - 1) / o.FPS)
63
+ // x264 keeps the frame rate it opened with and sizes its rate cap by it, so
64
+ // --fps is the stream's full rate and --capture-fps the rate capture starts at.
65
+ captureFPS := cmp.Or(o.CaptureFPS, o.FPS)
66
+ interval := int64((1000000 + captureFPS - 1) / captureFPS)
63
67
  busyUS, captureUS, scaleUS, encodeUS, previousPTS := int64(0), int64(0), int64(0), int64(0), int64(-1)
64
68
  dirty, forceKeyframe := true, false
65
69
  number, changedFrames, sentFrames := 0, 0, 0
package/native/options.go CHANGED
@@ -18,7 +18,7 @@ func parseOptions() (capture.Options, error) {
18
18
  initial, low, high int
19
19
  }{
20
20
  {"width", &o.Width, 0, 2, 8192}, {"height", &o.Height, 0, 2, 8192},
21
- {"fps", &o.FPS, 60, 1, 240}, {"threads", &o.Threads, 4, 1, 16},
21
+ {"fps", &o.FPS, 60, 1, 240}, {"capture-fps", &o.CaptureFPS, 0, 1, 240}, {"threads", &o.Threads, 4, 1, 16},
22
22
  {"crf", &o.CRF, 14, 0, 51}, {"kbps", &o.Kbps, 20000, 1, 1000000},
23
23
  {"keyframe-ms", &o.KeyframeMS, 1000, 0, 10000}, {"intra-refresh", &o.IntraRefresh, 1, 0, 1},
24
24
  {"chroma-qp", &o.ChromaQP, 0, -12, 12}, {"warmup", &o.Warmup, 0, 0, 60},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-computer",
3
- "version": "0.3.17",
3
+ "version": "0.3.19",
4
4
  "description": "Replicas workspace computer-use runtime",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,12 +0,0 @@
1
- <svg width="1920" height="1080" viewBox="0 0 1920 1080" xmlns="http://www.w3.org/2000/svg">
2
- <defs>
3
- <linearGradient id="cell" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#2fc487" stop-opacity="0.3"/><stop offset="1" stop-color="#2fc487" stop-opacity="0.14"/></linearGradient>
4
- <pattern id="grid" width="64" height="64" y="28" patternUnits="userSpaceOnUse"><path d="M0.5 0V64M0 0.5H64" stroke="#162118" stroke-opacity="0.1"/></pattern>
5
- <filter id="cell-glow" x="-25%" y="-25%" width="150%" height="150%"><feDropShadow dx="0" dy="0" stdDeviation="10" flood-color="#2fc487" flood-opacity="0.18"/></filter>
6
- </defs>
7
- <rect width="1920" height="1080" fill="#f4f5f1"/>
8
- <rect width="1920" height="1080" fill="url(#grid)"/>
9
- <g fill="url(#cell)" stroke="#2fc487" filter="url(#cell-glow)"><rect x="768.5" y="220.5" width="64" height="64"/><rect x="768.5" y="284.5" width="64" height="64"/><rect x="832.5" y="220.5" width="64" height="64"/><rect x="832.5" y="284.5" width="64" height="64"/><rect x="896.5" y="220.5" width="64" height="64"/><rect x="896.5" y="284.5" width="64" height="64"/><rect x="960.5" y="220.5" width="64" height="64"/><rect x="960.5" y="284.5" width="64" height="64"/><rect x="1024.5" y="348.5" width="64" height="64"/><rect x="1024.5" y="412.5" width="64" height="64"/><rect x="1088.5" y="348.5" width="64" height="64"/><rect x="1088.5" y="412.5" width="64" height="64"/><rect x="768.5" y="476.5" width="64" height="64"/><rect x="768.5" y="540.5" width="64" height="64"/><rect x="832.5" y="476.5" width="64" height="64"/><rect x="832.5" y="540.5" width="64" height="64"/><rect x="896.5" y="476.5" width="64" height="64"/><rect x="896.5" y="540.5" width="64" height="64"/><rect x="960.5" y="476.5" width="64" height="64"/><rect x="960.5" y="540.5" width="64" height="64"/><rect x="768.5" y="604.5" width="64" height="64"/><rect x="768.5" y="668.5" width="64" height="64"/><rect x="832.5" y="604.5" width="64" height="64"/><rect x="832.5" y="668.5" width="64" height="64"/><rect x="1024.5" y="604.5" width="64" height="64"/><rect x="1024.5" y="668.5" width="64" height="64"/><rect x="1088.5" y="604.5" width="64" height="64"/><rect x="1088.5" y="668.5" width="64" height="64"/><rect x="320.5" y="156.5" width="64" height="64"/><rect x="384.5" y="220.5" width="64" height="64"/><rect x="256.5" y="284.5" width="64" height="64"/><rect x="320.5" y="284.5" width="64" height="64"/><rect x="384.5" y="284.5" width="64" height="64"/><rect x="1536.5" y="220.5" width="64" height="64"/><rect x="1472.5" y="284.5" width="64" height="64"/><rect x="1600.5" y="348.5" width="64" height="64"/><rect x="1536.5" y="348.5" width="64" height="64"/><rect x="1472.5" y="348.5" width="64" height="64"/><rect x="448.5" y="860.5" width="64" height="64"/><rect x="512.5" y="796.5" width="64" height="64"/><rect x="384.5" y="732.5" width="64" height="64"/><rect x="448.5" y="732.5" width="64" height="64"/><rect x="512.5" y="732.5" width="64" height="64"/><rect x="1344.5" y="796.5" width="64" height="64"/><rect x="1408.5" y="796.5" width="64" height="64"/><rect x="1472.5" y="796.5" width="64" height="64"/><rect x="1664.5" y="540.5" width="64" height="64"/><rect x="1728.5" y="540.5" width="64" height="64"/><rect x="1664.5" y="604.5" width="64" height="64"/><rect x="1728.5" y="604.5" width="64" height="64"/></g>
10
- <g font-family="Geist Mono, monospace" font-size="5" font-weight="600" fill="#2f9d57"><text x="774" y="233">AGENT-73</text><text x="774" y="276">CODING</text><text x="774" y="297">AGENT-45</text><text x="774" y="340">DEPLOYING</text><text x="838" y="233">AGENT-10</text><text x="838" y="276">LINTING</text><text x="838" y="297">AGENT-82</text><text x="838" y="340">MERGING</text><text x="902" y="233">AGENT-47</text><text x="902" y="276">REFACTORING</text><text x="902" y="297">AGENT-19</text><text x="902" y="340">CODING</text><text x="966" y="233">AGENT-84</text><text x="966" y="276">RUNNING TESTS</text><text x="966" y="297">AGENT-56</text><text x="966" y="340">LINTING</text><text x="1030" y="361">AGENT-65</text><text x="1030" y="404">CODING</text><text x="1030" y="425">AGENT-37</text><text x="1030" y="468">DEPLOYING</text><text x="1094" y="361">AGENT-02</text><text x="1094" y="404">LINTING</text><text x="1094" y="425">AGENT-74</text><text x="1094" y="468">MERGING</text><text x="774" y="489">AGENT-61</text><text x="774" y="532">DEPLOYING</text><text x="774" y="553">AGENT-33</text><text x="774" y="596">REFACTORING</text><text x="838" y="489">AGENT-98</text><text x="838" y="532">MERGING</text><text x="838" y="553">AGENT-70</text><text x="838" y="596">RUNNING TESTS</text><text x="902" y="489">AGENT-35</text><text x="902" y="532">CODING</text><text x="902" y="553">AGENT-07</text><text x="902" y="596">DEPLOYING</text><text x="966" y="489">AGENT-72</text><text x="966" y="532">LINTING</text><text x="966" y="553">AGENT-44</text><text x="966" y="596">MERGING</text><text x="774" y="617">AGENT-05</text><text x="774" y="660">CODING</text><text x="774" y="681">AGENT-77</text><text x="774" y="724">DEPLOYING</text><text x="838" y="617">AGENT-42</text><text x="838" y="660">LINTING</text><text x="838" y="681">AGENT-14</text><text x="838" y="724">MERGING</text><text x="1030" y="617">AGENT-53</text><text x="1030" y="660">DEPLOYING</text><text x="1030" y="681">AGENT-25</text><text x="1030" y="724">REFACTORING</text><text x="1094" y="617">AGENT-90</text><text x="1094" y="660">MERGING</text><text x="1094" y="681">AGENT-62</text><text x="1094" y="724">RUNNING TESTS</text><text x="326" y="169">AGENT-42</text><text x="326" y="212">LINTING</text><text x="390" y="233">AGENT-51</text><text x="390" y="276">CODING</text><text x="262" y="297">AGENT-49</text><text x="262" y="340">REFACTORING</text><text x="326" y="297">AGENT-86</text><text x="326" y="340">RUNNING TESTS</text><text x="390" y="297">AGENT-23</text><text x="390" y="340">DEPLOYING</text><text x="1542" y="233">AGENT-17</text><text x="1542" y="276">CODING</text><text x="1478" y="297">AGENT-52</text><text x="1478" y="340">RUNNING TESTS</text><text x="1606" y="361">AGENT-98</text><text x="1606" y="404">RUNNING TESTS</text><text x="1542" y="361">AGENT-61</text><text x="1542" y="404">REFACTORING</text><text x="1478" y="361">AGENT-24</text><text x="1478" y="404">LINTING</text><text x="454" y="873">AGENT-08</text><text x="454" y="916">MERGING</text><text x="518" y="809">AGENT-73</text><text x="518" y="852">REFACTORING</text><text x="390" y="745">AGENT-27</text><text x="390" y="788">REFACTORING</text><text x="454" y="745">AGENT-64</text><text x="454" y="788">RUNNING TESTS</text><text x="518" y="745">AGENT-01</text><text x="518" y="788">DEPLOYING</text><text x="1350" y="809">AGENT-54</text><text x="1350" y="852">RUNNING TESTS</text><text x="1414" y="809">AGENT-91</text><text x="1414" y="852">DEPLOYING</text><text x="1478" y="809">AGENT-28</text><text x="1478" y="852">MERGING</text><text x="1670" y="553">AGENT-51</text><text x="1670" y="596">DEPLOYING</text><text x="1734" y="553">AGENT-88</text><text x="1734" y="596">MERGING</text><text x="1670" y="617">AGENT-23</text><text x="1670" y="660">REFACTORING</text><text x="1734" y="617">AGENT-60</text><text x="1734" y="660">RUNNING TESTS</text></g>
11
- <path transform="translate(893.05 842)" fill="#162118" fill-opacity="0.9" d="M5.3 0H3.3V-28.7H13.2Q17.5 -28.7 20 -26.5Q22.6 -24.3 22.6 -20.2Q22.6 -16.9 20.5 -14.6Q18.4 -12.4 14.9 -12L23.3 0H20.9L12.8 -11.8H5.3ZM5.3 -27.1V-13.4H13Q16.5 -13.4 18.5 -15.2Q20.6 -17 20.6 -20.2Q20.6 -23.5 18.5 -25.3Q16.5 -27.1 13 -27.1Z M33.8 0.4Q31 0.4 28.8 -0.9Q26.6 -2.3 25.4 -4.9Q24.2 -7.4 24.2 -10.7Q24.2 -13.7 25.5 -16.2Q26.7 -18.6 28.8 -20Q30.9 -21.4 33.6 -21.4Q35.8 -21.4 37.5 -20.6Q39.2 -19.7 40.5 -18.2Q41.7 -16.7 42.3 -14.7Q42.9 -12.6 42.8 -10.1H25.2V-11.5H40.9Q40.7 -15.4 38.8 -17.7Q36.8 -19.9 33.6 -19.9Q31.4 -19.9 29.7 -18.7Q28 -17.6 27 -15.5Q26.1 -13.5 26.1 -10.7Q26.1 -7.8 27 -5.6Q27.9 -3.4 29.7 -2.3Q31.4 -1.1 33.8 -1.1Q36.7 -1.1 38.6 -2.7Q40.6 -4.3 41.1 -7.1H42.9Q42.3 -3.6 39.8 -1.6Q37.4 0.4 33.8 0.4Z M64.2 -10.5Q64.2 -7.2 63.1 -4.8Q61.9 -2.3 59.9 -1Q57.8 0.4 55 0.4Q52.5 0.4 50.5 -1Q48.5 -2.4 47.4 -4.8V7.7H45.5V-21H47.3V-15.9Q48.3 -18.5 50.4 -19.9Q52.5 -21.4 55 -21.4Q57.8 -21.4 59.9 -20Q61.9 -18.7 63.1 -16.2Q64.2 -13.8 64.2 -10.5ZM62.3 -10.5Q62.3 -14.8 60.3 -17.3Q58.3 -19.8 54.8 -19.8Q52.7 -19.8 51 -18.7Q49.3 -17.6 48.3 -15.5Q47.3 -13.5 47.3 -10.5Q47.3 -7.7 48.3 -5.6Q49.2 -3.5 50.9 -2.3Q52.6 -1.2 54.8 -1.2Q58.3 -1.2 60.3 -3.6Q62.3 -6.1 62.3 -10.5Z M68.7 0H66.8V-28.7H68.7Z M74.8 0H72.9V-21H74.8ZM75.6 -25.8Q75.6 -25.1 75.2 -24.6Q74.7 -24.1 73.9 -24.1Q73.1 -24.1 72.6 -24.6Q72.1 -25.1 72.1 -25.8Q72.1 -26.6 72.6 -27Q73.1 -27.5 73.9 -27.5Q74.7 -27.5 75.2 -27Q75.6 -26.6 75.6 -25.8Z M86.5 0.4Q83.8 0.4 81.8 -0.9Q79.7 -2.3 78.6 -4.8Q77.4 -7.3 77.4 -10.5Q77.4 -13.7 78.6 -16.2Q79.7 -18.6 81.8 -20Q83.9 -21.4 86.5 -21.4Q88.9 -21.4 90.7 -20.4Q92.6 -19.4 93.8 -17.6Q95 -15.7 95.5 -13.2H93.6Q93 -16.3 91.2 -18.1Q89.3 -19.8 86.5 -19.8Q84.4 -19.8 82.8 -18.6Q81.1 -17.4 80.2 -15.4Q79.3 -13.3 79.3 -10.5Q79.3 -7.7 80.2 -5.6Q81.1 -3.5 82.8 -2.3Q84.4 -1.1 86.5 -1.1Q89.3 -1.1 91.2 -2.9Q93.1 -4.7 93.6 -7.8H95.5Q95.1 -5.2 93.8 -3.4Q92.6 -1.6 90.8 -0.6Q88.9 0.4 86.5 0.4Z M111.1 0V-4.2Q110.3 -2.1 108.3 -0.9Q106.4 0.4 103.9 0.4Q100.6 0.4 98.7 -1.3Q96.7 -2.9 96.7 -5.7Q96.7 -8.8 99.2 -10.4Q101.6 -11.9 106.4 -11.9Q107.4 -11.9 108.3 -11.9Q109.3 -11.8 111 -11.7V-13.6Q111 -16.6 109.5 -18.2Q108.1 -19.9 105.4 -19.9Q102.6 -19.9 100.9 -18.2Q99.3 -16.5 99.2 -13.7H97.5Q97.5 -16 98.5 -17.7Q99.5 -19.4 101.3 -20.4Q103.1 -21.4 105.4 -21.4Q108.9 -21.4 110.9 -19.4Q112.8 -17.4 112.8 -13.8V0ZM98.6 -5.8Q98.6 -3.7 100.1 -2.4Q101.6 -1 104 -1Q107.2 -1 109.1 -3.1Q111 -5.1 111 -8.4V-10.4Q109.5 -10.5 108.5 -10.5Q107.4 -10.6 106.5 -10.6Q102.5 -10.6 100.5 -9.4Q98.6 -8.2 98.6 -5.8Z M131.9 -5.5Q131.9 -2.8 129.8 -1.2Q127.7 0.4 123.9 0.4Q120.3 0.4 118 -1.4Q115.8 -3.2 115.5 -6.4H117.3Q117.4 -3.9 119.2 -2.5Q121 -1.1 124 -1.1Q126.9 -1.1 128.5 -2.2Q130 -3.3 130 -5.3Q130 -7.1 128.9 -8Q127.8 -8.9 125.2 -9.4L122.4 -10Q119.3 -10.6 117.8 -12Q116.3 -13.4 116.3 -15.6Q116.3 -17.3 117.2 -18.6Q118.2 -19.9 119.8 -20.6Q121.5 -21.4 123.7 -21.4Q126 -21.4 127.7 -20.6Q129.5 -19.8 130.5 -18.3Q131.5 -16.8 131.7 -14.8H129.9Q129.7 -17.2 128 -18.6Q126.4 -19.9 123.7 -19.9Q121.2 -19.9 119.6 -18.8Q118.1 -17.7 118.1 -15.8Q118.1 -14 119.2 -13.1Q120.4 -12.1 123 -11.6L125.9 -11Q129 -10.4 130.5 -9.1Q131.9 -7.8 131.9 -5.5Z"/>
12
- </svg>
@@ -1,12 +0,0 @@
1
- <svg width="1920" height="1080" viewBox="0 0 1920 1080" xmlns="http://www.w3.org/2000/svg">
2
- <defs>
3
- <linearGradient id="cell" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#3eeba3" stop-opacity="0.24"/><stop offset="1" stop-color="#3eeba3" stop-opacity="0.12"/></linearGradient>
4
- <pattern id="grid" width="64" height="64" y="28" patternUnits="userSpaceOnUse"><path d="M0.5 0V64M0 0.5H64" stroke="#ffffff" stroke-opacity="0.07"/></pattern>
5
- <filter id="cell-glow" x="-25%" y="-25%" width="150%" height="150%"><feDropShadow dx="0" dy="0" stdDeviation="10" flood-color="#2a8f65" flood-opacity="0.06"/></filter>
6
- </defs>
7
- <rect width="1920" height="1080" fill="#121212"/>
8
- <rect width="1920" height="1080" fill="url(#grid)"/>
9
- <g fill="url(#cell)" stroke="#2a8f65" filter="url(#cell-glow)"><rect x="768.5" y="220.5" width="64" height="64"/><rect x="768.5" y="284.5" width="64" height="64"/><rect x="832.5" y="220.5" width="64" height="64"/><rect x="832.5" y="284.5" width="64" height="64"/><rect x="896.5" y="220.5" width="64" height="64"/><rect x="896.5" y="284.5" width="64" height="64"/><rect x="960.5" y="220.5" width="64" height="64"/><rect x="960.5" y="284.5" width="64" height="64"/><rect x="1024.5" y="348.5" width="64" height="64"/><rect x="1024.5" y="412.5" width="64" height="64"/><rect x="1088.5" y="348.5" width="64" height="64"/><rect x="1088.5" y="412.5" width="64" height="64"/><rect x="768.5" y="476.5" width="64" height="64"/><rect x="768.5" y="540.5" width="64" height="64"/><rect x="832.5" y="476.5" width="64" height="64"/><rect x="832.5" y="540.5" width="64" height="64"/><rect x="896.5" y="476.5" width="64" height="64"/><rect x="896.5" y="540.5" width="64" height="64"/><rect x="960.5" y="476.5" width="64" height="64"/><rect x="960.5" y="540.5" width="64" height="64"/><rect x="768.5" y="604.5" width="64" height="64"/><rect x="768.5" y="668.5" width="64" height="64"/><rect x="832.5" y="604.5" width="64" height="64"/><rect x="832.5" y="668.5" width="64" height="64"/><rect x="1024.5" y="604.5" width="64" height="64"/><rect x="1024.5" y="668.5" width="64" height="64"/><rect x="1088.5" y="604.5" width="64" height="64"/><rect x="1088.5" y="668.5" width="64" height="64"/><rect x="320.5" y="156.5" width="64" height="64"/><rect x="384.5" y="220.5" width="64" height="64"/><rect x="256.5" y="284.5" width="64" height="64"/><rect x="320.5" y="284.5" width="64" height="64"/><rect x="384.5" y="284.5" width="64" height="64"/><rect x="1536.5" y="220.5" width="64" height="64"/><rect x="1472.5" y="284.5" width="64" height="64"/><rect x="1600.5" y="348.5" width="64" height="64"/><rect x="1536.5" y="348.5" width="64" height="64"/><rect x="1472.5" y="348.5" width="64" height="64"/><rect x="448.5" y="860.5" width="64" height="64"/><rect x="512.5" y="796.5" width="64" height="64"/><rect x="384.5" y="732.5" width="64" height="64"/><rect x="448.5" y="732.5" width="64" height="64"/><rect x="512.5" y="732.5" width="64" height="64"/><rect x="1344.5" y="796.5" width="64" height="64"/><rect x="1408.5" y="796.5" width="64" height="64"/><rect x="1472.5" y="796.5" width="64" height="64"/><rect x="1664.5" y="540.5" width="64" height="64"/><rect x="1728.5" y="540.5" width="64" height="64"/><rect x="1664.5" y="604.5" width="64" height="64"/><rect x="1728.5" y="604.5" width="64" height="64"/></g>
10
- <g font-family="Geist Mono, monospace" font-size="5" font-weight="600" fill="#48ae80"><text x="774" y="233">AGENT-73</text><text x="774" y="276">CODING</text><text x="774" y="297">AGENT-45</text><text x="774" y="340">DEPLOYING</text><text x="838" y="233">AGENT-10</text><text x="838" y="276">LINTING</text><text x="838" y="297">AGENT-82</text><text x="838" y="340">MERGING</text><text x="902" y="233">AGENT-47</text><text x="902" y="276">REFACTORING</text><text x="902" y="297">AGENT-19</text><text x="902" y="340">CODING</text><text x="966" y="233">AGENT-84</text><text x="966" y="276">RUNNING TESTS</text><text x="966" y="297">AGENT-56</text><text x="966" y="340">LINTING</text><text x="1030" y="361">AGENT-65</text><text x="1030" y="404">CODING</text><text x="1030" y="425">AGENT-37</text><text x="1030" y="468">DEPLOYING</text><text x="1094" y="361">AGENT-02</text><text x="1094" y="404">LINTING</text><text x="1094" y="425">AGENT-74</text><text x="1094" y="468">MERGING</text><text x="774" y="489">AGENT-61</text><text x="774" y="532">DEPLOYING</text><text x="774" y="553">AGENT-33</text><text x="774" y="596">REFACTORING</text><text x="838" y="489">AGENT-98</text><text x="838" y="532">MERGING</text><text x="838" y="553">AGENT-70</text><text x="838" y="596">RUNNING TESTS</text><text x="902" y="489">AGENT-35</text><text x="902" y="532">CODING</text><text x="902" y="553">AGENT-07</text><text x="902" y="596">DEPLOYING</text><text x="966" y="489">AGENT-72</text><text x="966" y="532">LINTING</text><text x="966" y="553">AGENT-44</text><text x="966" y="596">MERGING</text><text x="774" y="617">AGENT-05</text><text x="774" y="660">CODING</text><text x="774" y="681">AGENT-77</text><text x="774" y="724">DEPLOYING</text><text x="838" y="617">AGENT-42</text><text x="838" y="660">LINTING</text><text x="838" y="681">AGENT-14</text><text x="838" y="724">MERGING</text><text x="1030" y="617">AGENT-53</text><text x="1030" y="660">DEPLOYING</text><text x="1030" y="681">AGENT-25</text><text x="1030" y="724">REFACTORING</text><text x="1094" y="617">AGENT-90</text><text x="1094" y="660">MERGING</text><text x="1094" y="681">AGENT-62</text><text x="1094" y="724">RUNNING TESTS</text><text x="326" y="169">AGENT-42</text><text x="326" y="212">LINTING</text><text x="390" y="233">AGENT-51</text><text x="390" y="276">CODING</text><text x="262" y="297">AGENT-49</text><text x="262" y="340">REFACTORING</text><text x="326" y="297">AGENT-86</text><text x="326" y="340">RUNNING TESTS</text><text x="390" y="297">AGENT-23</text><text x="390" y="340">DEPLOYING</text><text x="1542" y="233">AGENT-17</text><text x="1542" y="276">CODING</text><text x="1478" y="297">AGENT-52</text><text x="1478" y="340">RUNNING TESTS</text><text x="1606" y="361">AGENT-98</text><text x="1606" y="404">RUNNING TESTS</text><text x="1542" y="361">AGENT-61</text><text x="1542" y="404">REFACTORING</text><text x="1478" y="361">AGENT-24</text><text x="1478" y="404">LINTING</text><text x="454" y="873">AGENT-08</text><text x="454" y="916">MERGING</text><text x="518" y="809">AGENT-73</text><text x="518" y="852">REFACTORING</text><text x="390" y="745">AGENT-27</text><text x="390" y="788">REFACTORING</text><text x="454" y="745">AGENT-64</text><text x="454" y="788">RUNNING TESTS</text><text x="518" y="745">AGENT-01</text><text x="518" y="788">DEPLOYING</text><text x="1350" y="809">AGENT-54</text><text x="1350" y="852">RUNNING TESTS</text><text x="1414" y="809">AGENT-91</text><text x="1414" y="852">DEPLOYING</text><text x="1478" y="809">AGENT-28</text><text x="1478" y="852">MERGING</text><text x="1670" y="553">AGENT-51</text><text x="1670" y="596">DEPLOYING</text><text x="1734" y="553">AGENT-88</text><text x="1734" y="596">MERGING</text><text x="1670" y="617">AGENT-23</text><text x="1670" y="660">REFACTORING</text><text x="1734" y="617">AGENT-60</text><text x="1734" y="660">RUNNING TESTS</text></g>
11
- <path transform="translate(893.05 842)" fill="#ffffff" fill-opacity="0.85" d="M5.3 0H3.3V-28.7H13.2Q17.5 -28.7 20 -26.5Q22.6 -24.3 22.6 -20.2Q22.6 -16.9 20.5 -14.6Q18.4 -12.4 14.9 -12L23.3 0H20.9L12.8 -11.8H5.3ZM5.3 -27.1V-13.4H13Q16.5 -13.4 18.5 -15.2Q20.6 -17 20.6 -20.2Q20.6 -23.5 18.5 -25.3Q16.5 -27.1 13 -27.1Z M33.8 0.4Q31 0.4 28.8 -0.9Q26.6 -2.3 25.4 -4.9Q24.2 -7.4 24.2 -10.7Q24.2 -13.7 25.5 -16.2Q26.7 -18.6 28.8 -20Q30.9 -21.4 33.6 -21.4Q35.8 -21.4 37.5 -20.6Q39.2 -19.7 40.5 -18.2Q41.7 -16.7 42.3 -14.7Q42.9 -12.6 42.8 -10.1H25.2V-11.5H40.9Q40.7 -15.4 38.8 -17.7Q36.8 -19.9 33.6 -19.9Q31.4 -19.9 29.7 -18.7Q28 -17.6 27 -15.5Q26.1 -13.5 26.1 -10.7Q26.1 -7.8 27 -5.6Q27.9 -3.4 29.7 -2.3Q31.4 -1.1 33.8 -1.1Q36.7 -1.1 38.6 -2.7Q40.6 -4.3 41.1 -7.1H42.9Q42.3 -3.6 39.8 -1.6Q37.4 0.4 33.8 0.4Z M64.2 -10.5Q64.2 -7.2 63.1 -4.8Q61.9 -2.3 59.9 -1Q57.8 0.4 55 0.4Q52.5 0.4 50.5 -1Q48.5 -2.4 47.4 -4.8V7.7H45.5V-21H47.3V-15.9Q48.3 -18.5 50.4 -19.9Q52.5 -21.4 55 -21.4Q57.8 -21.4 59.9 -20Q61.9 -18.7 63.1 -16.2Q64.2 -13.8 64.2 -10.5ZM62.3 -10.5Q62.3 -14.8 60.3 -17.3Q58.3 -19.8 54.8 -19.8Q52.7 -19.8 51 -18.7Q49.3 -17.6 48.3 -15.5Q47.3 -13.5 47.3 -10.5Q47.3 -7.7 48.3 -5.6Q49.2 -3.5 50.9 -2.3Q52.6 -1.2 54.8 -1.2Q58.3 -1.2 60.3 -3.6Q62.3 -6.1 62.3 -10.5Z M68.7 0H66.8V-28.7H68.7Z M74.8 0H72.9V-21H74.8ZM75.6 -25.8Q75.6 -25.1 75.2 -24.6Q74.7 -24.1 73.9 -24.1Q73.1 -24.1 72.6 -24.6Q72.1 -25.1 72.1 -25.8Q72.1 -26.6 72.6 -27Q73.1 -27.5 73.9 -27.5Q74.7 -27.5 75.2 -27Q75.6 -26.6 75.6 -25.8Z M86.5 0.4Q83.8 0.4 81.8 -0.9Q79.7 -2.3 78.6 -4.8Q77.4 -7.3 77.4 -10.5Q77.4 -13.7 78.6 -16.2Q79.7 -18.6 81.8 -20Q83.9 -21.4 86.5 -21.4Q88.9 -21.4 90.7 -20.4Q92.6 -19.4 93.8 -17.6Q95 -15.7 95.5 -13.2H93.6Q93 -16.3 91.2 -18.1Q89.3 -19.8 86.5 -19.8Q84.4 -19.8 82.8 -18.6Q81.1 -17.4 80.2 -15.4Q79.3 -13.3 79.3 -10.5Q79.3 -7.7 80.2 -5.6Q81.1 -3.5 82.8 -2.3Q84.4 -1.1 86.5 -1.1Q89.3 -1.1 91.2 -2.9Q93.1 -4.7 93.6 -7.8H95.5Q95.1 -5.2 93.8 -3.4Q92.6 -1.6 90.8 -0.6Q88.9 0.4 86.5 0.4Z M111.1 0V-4.2Q110.3 -2.1 108.3 -0.9Q106.4 0.4 103.9 0.4Q100.6 0.4 98.7 -1.3Q96.7 -2.9 96.7 -5.7Q96.7 -8.8 99.2 -10.4Q101.6 -11.9 106.4 -11.9Q107.4 -11.9 108.3 -11.9Q109.3 -11.8 111 -11.7V-13.6Q111 -16.6 109.5 -18.2Q108.1 -19.9 105.4 -19.9Q102.6 -19.9 100.9 -18.2Q99.3 -16.5 99.2 -13.7H97.5Q97.5 -16 98.5 -17.7Q99.5 -19.4 101.3 -20.4Q103.1 -21.4 105.4 -21.4Q108.9 -21.4 110.9 -19.4Q112.8 -17.4 112.8 -13.8V0ZM98.6 -5.8Q98.6 -3.7 100.1 -2.4Q101.6 -1 104 -1Q107.2 -1 109.1 -3.1Q111 -5.1 111 -8.4V-10.4Q109.5 -10.5 108.5 -10.5Q107.4 -10.6 106.5 -10.6Q102.5 -10.6 100.5 -9.4Q98.6 -8.2 98.6 -5.8Z M131.9 -5.5Q131.9 -2.8 129.8 -1.2Q127.7 0.4 123.9 0.4Q120.3 0.4 118 -1.4Q115.8 -3.2 115.5 -6.4H117.3Q117.4 -3.9 119.2 -2.5Q121 -1.1 124 -1.1Q126.9 -1.1 128.5 -2.2Q130 -3.3 130 -5.3Q130 -7.1 128.9 -8Q127.8 -8.9 125.2 -9.4L122.4 -10Q119.3 -10.6 117.8 -12Q116.3 -13.4 116.3 -15.6Q116.3 -17.3 117.2 -18.6Q118.2 -19.9 119.8 -20.6Q121.5 -21.4 123.7 -21.4Q126 -21.4 127.7 -20.6Q129.5 -19.8 130.5 -18.3Q131.5 -16.8 131.7 -14.8H129.9Q129.7 -17.2 128 -18.6Q126.4 -19.9 123.7 -19.9Q121.2 -19.9 119.6 -18.8Q118.1 -17.7 118.1 -15.8Q118.1 -14 119.2 -13.1Q120.4 -12.1 123 -11.6L125.9 -11Q129 -10.4 130.5 -9.1Q131.9 -7.8 131.9 -5.5Z"/>
12
- </svg>