replicas-computer 0.3.15 → 0.3.16

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
@@ -44,7 +44,7 @@ The server records every decision that changes the picture (viewer connects and
44
44
  | `REPLICAS_DESKTOP_STREAM_TRANSPORT` | `webrtc` | Transport for viewers whose URL does not choose one |
45
45
  | `REPLICAS_DESKTOP_STREAM_PROFILE` | `balanced` | Encoder profile for viewers whose URL does not choose one |
46
46
  | `REPLICAS_DESKTOP_STREAM_ADAPTIVE` | `1` | `0` pins the top quality rung by default |
47
- | `REPLICAS_DESKTOP_STREAM_ICE_SERVERS` | `stun:stun.l.google.com:19302` | Comma-separated STUN/TURN servers; `turn:user:secret@host:port?transport=tcp` adds a relay. Percent-encode the username and password; the browser receives their decoded values. Re-read from the workspace runtime env on every viewer, so the monolith's `DESKTOP_STREAM_ICE_SERVERS` reaches running workspaces |
47
+ | `REPLICAS_DESKTOP_STREAM_ICE_SERVERS` | `stun:stun.l.google.com:19302` | Comma-separated STUN/TURN servers; `turn:user:secret@host:port?transport=tcp` adds a relay. Percent-encode the username and password; the browser receives their decoded values. Re-read from the workspace runtime env on every viewer, so the monolith's `DESKTOP_STREAM_ICE_SERVERS` reaches running workspaces. Short-lived credentials for the [TURN relay](../services/turn/README.md) are fetched from the monolith and appended; the server itself only uses UDP relays |
48
48
  | `REPLICAS_DESKTOP_STREAM_ICE_PORTS` | ephemeral | `begin-end` UDP port range for the server's ICE candidates |
49
49
  | `REPLICAS_DESKTOP_STREAM_WEBRTC` | `1` | `0` serves the WebSocket transport only |
50
50
  | `REPLICAS_DESKTOP_STREAM_COLOR` | `bt709` | `legacy` disables the explicit BT.709 conversion and signalling |
package/dist/index.js CHANGED
@@ -2937,7 +2937,7 @@ async function computerStatusCommand() {
2937
2937
  }
2938
2938
 
2939
2939
  // src/version.ts
2940
- var COMPUTER_USE_VERSION = "0.3.15";
2940
+ var COMPUTER_USE_VERSION = "0.3.16";
2941
2941
  var COMPUTER_USE_PROTOCOL_VERSION = 1;
2942
2942
 
2943
2943
  // src/program.ts
@@ -3179,69 +3179,11 @@ async function runComputerClient(argv) {
3179
3179
  // src/media-server.ts
3180
3180
  import { spawn as spawn5 } from "child_process";
3181
3181
  import { createHash as createHash3 } from "crypto";
3182
- import { createReadStream, existsSync as existsSync7, readdirSync, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
3182
+ import { createReadStream, existsSync as existsSync7, readdirSync, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
3183
3183
  import { createServer as createServer2 } from "http";
3184
3184
  import { availableParallelism as availableParallelism2, loadavg } from "os";
3185
3185
  import { dirname as dirname7, extname, resolve as resolve3 } from "path";
3186
3186
  import { fileURLToPath as fileURLToPath2 } from "url";
3187
-
3188
- // ../shared/src/runtime-env.ts
3189
- function parsePosixEnvFile(content) {
3190
- const result = {};
3191
- let pos = 0;
3192
- while (pos < content.length) {
3193
- let lineStart;
3194
- if (pos === 0 || content[pos - 1] === "\n") {
3195
- lineStart = pos;
3196
- } else {
3197
- const nl2 = content.indexOf("\n", pos);
3198
- if (nl2 === -1) break;
3199
- lineStart = nl2 + 1;
3200
- }
3201
- if (!content.startsWith("export ", lineStart)) {
3202
- const nl2 = content.indexOf("\n", lineStart);
3203
- if (nl2 === -1) break;
3204
- pos = nl2 + 1;
3205
- continue;
3206
- }
3207
- const nameStart = lineStart + "export ".length;
3208
- const eqIdx = content.indexOf("=", nameStart);
3209
- if (eqIdx === -1) break;
3210
- const name = content.slice(nameStart, eqIdx).trim();
3211
- let cursor = eqIdx + 1;
3212
- if (content[cursor] !== "'") {
3213
- const nl2 = content.indexOf("\n", cursor);
3214
- pos = nl2 === -1 ? content.length : nl2 + 1;
3215
- continue;
3216
- }
3217
- cursor += 1;
3218
- let value = "";
3219
- let closed = false;
3220
- while (cursor < content.length) {
3221
- const ch = content[cursor];
3222
- if (ch === "'") {
3223
- if (content.startsWith("\\''", cursor + 1)) {
3224
- value += "'";
3225
- cursor += 4;
3226
- continue;
3227
- }
3228
- cursor += 1;
3229
- closed = true;
3230
- break;
3231
- }
3232
- value += ch;
3233
- cursor += 1;
3234
- }
3235
- if (closed && name) {
3236
- result[name] = value;
3237
- }
3238
- const nl = content.indexOf("\n", cursor);
3239
- pos = nl === -1 ? content.length : nl + 1;
3240
- }
3241
- return result;
3242
- }
3243
-
3244
- // src/media-server.ts
3245
3187
  import WebSocket2, { WebSocketServer } from "ws";
3246
3188
 
3247
3189
  // src/encoder.ts
@@ -3573,13 +3515,17 @@ function superviseEncoder(options) {
3573
3515
  };
3574
3516
  }
3575
3517
 
3576
- // src/video-encoder.ts
3577
- import { existsSync as existsSync6 } from "fs";
3578
- import { availableParallelism } from "os";
3579
- import { dirname as dirname6, resolve as resolve2 } from "path";
3580
- import { fileURLToPath } from "url";
3518
+ // src/ice-servers.ts
3519
+ import { readFileSync as readFileSync6 } from "fs";
3581
3520
 
3582
3521
  // ../shared/src/desktop-stream.ts
3522
+ function isStreamIceServer(value) {
3523
+ return isRecord(value) && typeof value.urls === "string" && (value.username === void 0 || typeof value.username === "string") && (value.credential === void 0 || typeof value.credential === "string");
3524
+ }
3525
+ var DESKTOP_ICE_SERVERS_PATH = "/v1/engine/desktop/ice-servers";
3526
+ function isDesktopIceServers(value) {
3527
+ return isRecord(value) && typeof value.expiresAt === "number" && Array.isArray(value.iceServers) && value.iceServers.every(isStreamIceServer);
3528
+ }
3583
3529
  var DEFAULT_STREAM_REQUEST = {
3584
3530
  transport: "webrtc",
3585
3531
  profile: "balanced",
@@ -3636,6 +3582,144 @@ function isDesktopWebRtcFailure(value) {
3636
3582
  ].every((key) => typeof value[key] === "string" && value[key].length <= 200) && ["elapsedMs", "candidateErrors", "framesDecoded", "packetsReceived", "packetsLost", "bytesReceived", "lastFrameAgeMs", "encoderFps"].every((key) => typeof value[key] === "number" && Number.isFinite(value[key]));
3637
3583
  }
3638
3584
 
3585
+ // ../shared/src/runtime-env.ts
3586
+ var REPLICAS_RUNTIME_ENV_ALIASES = {
3587
+ monolithUrl: ["REPLICAS_MONOLITH_URL", "MONOLITH_URL"],
3588
+ workspaceId: ["REPLICAS_WORKSPACE_ID", "WORKSPACE_ID"],
3589
+ linearSessionId: ["REPLICAS_LINEAR_SESSION_ID", "LINEAR_SESSION_ID"],
3590
+ linearAccessToken: ["REPLICAS_LINEAR_ACCESS_TOKEN", "LINEAR_ACCESS_TOKEN"],
3591
+ slackBotToken: ["REPLICAS_SLACK_BOT_TOKEN", "SLACK_BOT_TOKEN"],
3592
+ slackChannelId: ["REPLICAS_SLACK_CHANNEL_ID", "SLACK_CHANNEL_ID"],
3593
+ slackThreadTs: ["REPLICAS_SLACK_THREAD_TS", "SLACK_THREAD_TS"]
3594
+ };
3595
+ function readReplicasRuntimeEnv(readEnv, [namespacedKey, legacyKey]) {
3596
+ return readEnv(namespacedKey) ?? readEnv(legacyKey);
3597
+ }
3598
+ function parsePosixEnvFile(content) {
3599
+ const result = {};
3600
+ let pos = 0;
3601
+ while (pos < content.length) {
3602
+ let lineStart;
3603
+ if (pos === 0 || content[pos - 1] === "\n") {
3604
+ lineStart = pos;
3605
+ } else {
3606
+ const nl2 = content.indexOf("\n", pos);
3607
+ if (nl2 === -1) break;
3608
+ lineStart = nl2 + 1;
3609
+ }
3610
+ if (!content.startsWith("export ", lineStart)) {
3611
+ const nl2 = content.indexOf("\n", lineStart);
3612
+ if (nl2 === -1) break;
3613
+ pos = nl2 + 1;
3614
+ continue;
3615
+ }
3616
+ const nameStart = lineStart + "export ".length;
3617
+ const eqIdx = content.indexOf("=", nameStart);
3618
+ if (eqIdx === -1) break;
3619
+ const name = content.slice(nameStart, eqIdx).trim();
3620
+ let cursor = eqIdx + 1;
3621
+ if (content[cursor] !== "'") {
3622
+ const nl2 = content.indexOf("\n", cursor);
3623
+ pos = nl2 === -1 ? content.length : nl2 + 1;
3624
+ continue;
3625
+ }
3626
+ cursor += 1;
3627
+ let value = "";
3628
+ let closed = false;
3629
+ while (cursor < content.length) {
3630
+ const ch = content[cursor];
3631
+ if (ch === "'") {
3632
+ if (content.startsWith("\\''", cursor + 1)) {
3633
+ value += "'";
3634
+ cursor += 4;
3635
+ continue;
3636
+ }
3637
+ cursor += 1;
3638
+ closed = true;
3639
+ break;
3640
+ }
3641
+ value += ch;
3642
+ cursor += 1;
3643
+ }
3644
+ if (closed && name) {
3645
+ result[name] = value;
3646
+ }
3647
+ const nl = content.indexOf("\n", cursor);
3648
+ pos = nl === -1 ? content.length : nl + 1;
3649
+ }
3650
+ return result;
3651
+ }
3652
+
3653
+ // src/ice-servers.ts
3654
+ var runtimeEnvFile = process.env.REPLICAS_RUNTIME_ENV_FILE || "/home/user/.replicas/runtime-env.sh";
3655
+ var refreshMs = 60 * 6e4;
3656
+ var retryMs = 6e4;
3657
+ var relays = [];
3658
+ var expiresAt = 0;
3659
+ var refreshAt = 0;
3660
+ var refreshing;
3661
+ async function currentIceServers() {
3662
+ let env = process.env;
3663
+ try {
3664
+ env = { ...env, ...parsePosixEnvFile(readFileSync6(runtimeEnvFile, "utf8")) };
3665
+ } catch {
3666
+ }
3667
+ const monolithUrl = readReplicasRuntimeEnv((name) => env[name], REPLICAS_RUNTIME_ENV_ALIASES.monolithUrl);
3668
+ const workspaceId = readReplicasRuntimeEnv((name) => env[name], REPLICAS_RUNTIME_ENV_ALIASES.workspaceId);
3669
+ const engineSecret = env.REPLICAS_ENGINE_SECRET;
3670
+ if (monolithUrl && workspaceId && engineSecret && Date.now() >= refreshAt) {
3671
+ const refreshed = refreshing ??= refreshRelays(monolithUrl, workspaceId, engineSecret).finally(() => {
3672
+ refreshing = void 0;
3673
+ });
3674
+ if (expiresAt <= Date.now()) await refreshed;
3675
+ }
3676
+ return toIceServers(env.REPLICAS_DESKTOP_STREAM_ICE_SERVERS, expiresAt > Date.now() ? relays : []);
3677
+ }
3678
+ async function refreshRelays(monolithUrl, workspaceId, engineSecret) {
3679
+ try {
3680
+ const response = await fetch(new URL(DESKTOP_ICE_SERVERS_PATH, monolithUrl), {
3681
+ headers: { authorization: `Bearer ${engineSecret}`, "x-workspace-id": workspaceId },
3682
+ redirect: "error",
3683
+ signal: AbortSignal.timeout(2e3)
3684
+ });
3685
+ if (response.status === 404) {
3686
+ relays = [];
3687
+ expiresAt = 0;
3688
+ } else {
3689
+ const body = response.ok ? await response.json() : void 0;
3690
+ if (!isDesktopIceServers(body)) throw new Error(`HTTP ${response.status}`);
3691
+ ({ iceServers: relays, expiresAt } = body);
3692
+ }
3693
+ refreshAt = Date.now() + refreshMs;
3694
+ } catch (error) {
3695
+ process.stderr.write(`[desktop-stream] TURN credentials unavailable: ${error instanceof Error ? error.message : String(error)}
3696
+ `);
3697
+ refreshAt = Date.now() + retryMs;
3698
+ }
3699
+ }
3700
+ function toIceServers(spec = "stun:stun.l.google.com:19302", relayServers) {
3701
+ const browser = spec.split(",").map((url) => url.trim()).filter(Boolean).map((url) => {
3702
+ const match = url.match(/^(turns?):([^:@]+):(.+)@([^@]+)$/);
3703
+ if (!match) return { urls: url };
3704
+ const [username, credential] = match.slice(2, 4).map((value) => {
3705
+ try {
3706
+ return decodeURIComponent(value);
3707
+ } catch {
3708
+ return value;
3709
+ }
3710
+ });
3711
+ return { urls: `${match[1]}:${match[4]}`, username, credential };
3712
+ }).concat(relayServers);
3713
+ const native = browser.filter(({ urls }) => !/^turns:|[?&]transport=tcp/i.test(urls)).map(({ urls, username, credential }) => username === void 0 || credential === void 0 ? urls : urls.replace(/^turn:/i, `turn:${encodeURIComponent(username)}:${encodeURIComponent(credential)}@`));
3714
+ return { native, browser };
3715
+ }
3716
+
3717
+ // src/video-encoder.ts
3718
+ import { existsSync as existsSync6 } from "fs";
3719
+ import { availableParallelism } from "os";
3720
+ import { dirname as dirname6, resolve as resolve2 } from "path";
3721
+ import { fileURLToPath } from "url";
3722
+
3639
3723
  // src/input-validation.ts
3640
3724
  var finite = (value) => typeof value === "number" && Number.isFinite(value);
3641
3725
  var optionalFinite = (value) => value === void 0 || finite(value);
@@ -3951,22 +4035,6 @@ async function loadWebRtc() {
3951
4035
  }
3952
4036
  }
3953
4037
  var cleanupWebRtc = () => ndc?.cleanup();
3954
- function parseIceServers(spec) {
3955
- const native = (spec ?? "stun:stun.l.google.com:19302").split(",").map((server) => server.trim()).filter(Boolean);
3956
- const browser = native.map((url) => {
3957
- const match = url.match(/^(turns?):([^:@]+):(.+)@([^@]+)$/);
3958
- if (!match) return { urls: url };
3959
- const [username, credential] = match.slice(2, 4).map((value) => {
3960
- try {
3961
- return decodeURIComponent(value);
3962
- } catch {
3963
- return value;
3964
- }
3965
- });
3966
- return { urls: `${match[1]}:${match[4]}`, username, credential };
3967
- });
3968
- return { native, browser };
3969
- }
3970
4038
  var WebRtcViewer = class {
3971
4039
  constructor(options, handlers) {
3972
4040
  this.handlers = handlers;
@@ -4478,7 +4546,6 @@ var standbyRung = 5;
4478
4546
  var port = Number.parseInt(process.env.REPLICAS_DESKTOP_STREAM_PORT || String(DESKTOP_STREAM_PORT), 10);
4479
4547
  var display = process.env.REPLICAS_DESKTOP_DISPLAY?.replace(/^([^:])/, ":$1") || ":99";
4480
4548
  var webrtcEnabled = process.env.REPLICAS_DESKTOP_STREAM_WEBRTC !== "0";
4481
- var runtimeEnvFile = process.env.REPLICAS_RUNTIME_ENV_FILE || "/home/user/.replicas/runtime-env.sh";
4482
4549
  var colorMode = process.env.REPLICAS_DESKTOP_STREAM_COLOR === "legacy" ? "legacy" : "bt709";
4483
4550
  var defaultRequest = parseStreamRequest(new URLSearchParams({
4484
4551
  transport: process.env.REPLICAS_DESKTOP_STREAM_TRANSPORT ?? "",
@@ -4534,14 +4601,6 @@ function mediaPacket(kind, timestamp, payload) {
4534
4601
  header.writeBigUInt64BE(BigInt(Math.max(0, timestamp)), 4);
4535
4602
  return Buffer.concat([header, payload]);
4536
4603
  }
4537
- function currentIceServers() {
4538
- let spec = process.env.REPLICAS_DESKTOP_STREAM_ICE_SERVERS;
4539
- try {
4540
- spec = parsePosixEnvFile(readFileSync6(runtimeEnvFile, "utf8")).REPLICAS_DESKTOP_STREAM_ICE_SERVERS ?? spec;
4541
- } catch {
4542
- }
4543
- return parseIceServers(spec);
4544
- }
4545
4604
  var appliedTheme;
4546
4605
  var pendingTheme;
4547
4606
  var applyingTheme = false;
@@ -4567,7 +4626,7 @@ function drainDesktopTheme() {
4567
4626
  function processCpuMs(pid) {
4568
4627
  if (!pid) return 0;
4569
4628
  try {
4570
- const stat = readFileSync6(`/proc/${pid}/stat`, "utf8");
4629
+ const stat = readFileSync7(`/proc/${pid}/stat`, "utf8");
4571
4630
  const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
4572
4631
  return (Number(fields[11]) + Number(fields[12])) * 10;
4573
4632
  } catch {
@@ -5252,10 +5311,13 @@ async function runMediaServer() {
5252
5311
  // A socket viewer whose WebRTC attempt failed tries again in the background;
5253
5312
  // the socket keeps carrying its media until the new peer shows video.
5254
5313
  case "retry-webrtc": {
5255
- const ice = webrtc && viewer.fallback && !viewer.rtc && viewer.request.transport === "webrtc" ? currentIceServers() : void 0;
5256
- if (!ice) return;
5257
- record("retrying webrtc", "socket media continues meanwhile");
5258
- openWebRtc(viewer, reply, ice.native);
5314
+ const retryable = () => webrtc && viewer.fallback && !viewer.rtc && viewer.request.transport === "webrtc" && viewer.socket.readyState === WebSocket2.OPEN;
5315
+ if (!retryable()) return;
5316
+ void currentIceServers().then((ice) => {
5317
+ if (!retryable()) return;
5318
+ record("retrying webrtc", "socket media continues meanwhile");
5319
+ openWebRtc(viewer, reply, ice.native);
5320
+ });
5259
5321
  return;
5260
5322
  }
5261
5323
  case "webrtc-ready":
@@ -5359,10 +5421,11 @@ async function runMediaServer() {
5359
5421
  if (extname(path) === ".html") {
5360
5422
  ensureEncoders();
5361
5423
  if (viewers.size === 0) stopEncodersSoon();
5424
+ if (webrtc) void currentIceServers();
5362
5425
  response.setHeader("cache-control", "no-store");
5363
5426
  const digest = createHash3("sha1");
5364
- for (const name of readdirSync(webroot).filter((file) => /\.(js|css)$/.test(file)).sort()) digest.update(readFileSync6(resolve3(webroot, name)));
5365
- response.end(readFileSync6(path, "utf8").replaceAll("__VERSION__", `${COMPUTER_USE_VERSION}-${digest.digest("hex").slice(0, 8)}`));
5427
+ for (const name of readdirSync(webroot).filter((file) => /\.(js|css)$/.test(file)).sort()) digest.update(readFileSync7(resolve3(webroot, name)));
5428
+ response.end(readFileSync7(path, "utf8").replaceAll("__VERSION__", `${COMPUTER_USE_VERSION}-${digest.digest("hex").slice(0, 8)}`));
5366
5429
  return;
5367
5430
  }
5368
5431
  response.setHeader("cache-control", "private, max-age=31536000, immutable");
@@ -5375,9 +5438,10 @@ async function runMediaServer() {
5375
5438
  socket.destroy();
5376
5439
  return;
5377
5440
  }
5378
- webSocketServer.handleUpgrade(request, socket, head, (client) => webSocketServer.emit("connection", client, parseStreamRequest(url.searchParams, defaultRequest), url.searchParams.get("standby") === "1"));
5441
+ const streamRequest = parseStreamRequest(url.searchParams, defaultRequest);
5442
+ 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)));
5379
5443
  });
5380
- webSocketServer.on("connection", (socket, request, standby) => {
5444
+ webSocketServer.on("connection", (socket, request, standby, ice) => {
5381
5445
  const viewer = {
5382
5446
  socket,
5383
5447
  input: new InputBridge(display, width, height),
@@ -5411,8 +5475,6 @@ async function runMediaServer() {
5411
5475
  const reply = (message) => {
5412
5476
  if (socket.readyState === WebSocket2.OPEN) socket.send(JSON.stringify(message));
5413
5477
  };
5414
- const offerWebRtc = webrtc && request.transport === "webrtc";
5415
- const ice = offerWebRtc ? currentIceServers() : void 0;
5416
5478
  const fallbackProfile = profile() === "high444" ? "main" : profile();
5417
5479
  const configMessage = { ...config, videoCodec: H264_PROFILES[fallbackProfile].codec, iceServers: ice?.browser, request };
5418
5480
  reply(configMessage);
@@ -4,6 +4,9 @@ function isRecord(value) {
4
4
  }
5
5
 
6
6
  // ../shared/src/desktop-stream.ts
7
+ function isStreamIceServer(value) {
8
+ return isRecord(value) && typeof value.urls === "string" && (value.username === void 0 || typeof value.username === "string") && (value.credential === void 0 || typeof value.credential === "string");
9
+ }
7
10
  var DEFAULT_STREAM_REQUEST = {
8
11
  transport: "webrtc",
9
12
  profile: "balanced",
@@ -49,10 +52,6 @@ var DESKTOP_QUALITY_MESSAGE = "replicas-desktop-quality";
49
52
 
50
53
  // src/input-validation.ts
51
54
  var finite = (value) => typeof value === "number" && Number.isFinite(value);
52
- var iceServer = (value) => {
53
- if (!isRecord(value)) return false;
54
- return typeof value.urls === "string" && (value.username === void 0 || typeof value.username === "string") && (value.credential === void 0 || typeof value.credential === "string");
55
- };
56
55
  function validServerMessage(value) {
57
56
  if (!isRecord(value) || typeof value.type !== "string") return false;
58
57
  switch (value.type) {
@@ -66,7 +65,7 @@ function validServerMessage(value) {
66
65
  case "candidate":
67
66
  return typeof value.candidate === "string" && value.candidate.length < 1024 && typeof value.mid === "string" && value.mid.length < 16;
68
67
  case "config":
69
- return finite(value.protocolVersion) && (value.serverVersion === void 0 || typeof value.serverVersion === "string") && (value.webrtcUnavailableReason === void 0 || typeof value.webrtcUnavailableReason === "string") && (value.inputRace === void 0 || typeof value.inputRace === "boolean") && finite(value.width) && finite(value.height) && finite(value.fps) && typeof value.videoCodec === "string" && typeof value.audioCodec === "string" && finite(value.audioSampleRate) && finite(value.audioChannels) && isRecord(value.request) && (value.iceServers === void 0 || Array.isArray(value.iceServers) && value.iceServers.every(iceServer));
68
+ return finite(value.protocolVersion) && (value.serverVersion === void 0 || typeof value.serverVersion === "string") && (value.webrtcUnavailableReason === void 0 || typeof value.webrtcUnavailableReason === "string") && (value.inputRace === void 0 || typeof value.inputRace === "boolean") && finite(value.width) && finite(value.height) && finite(value.fps) && typeof value.videoCodec === "string" && typeof value.audioCodec === "string" && finite(value.audioSampleRate) && finite(value.audioChannels) && isRecord(value.request) && (value.iceServers === void 0 || Array.isArray(value.iceServers) && value.iceServers.every(isStreamIceServer));
70
69
  case "telemetry":
71
70
  return isRecord(value.encoder) && isRecord(value.cpu) && isRecord(value.viewer) && Array.isArray(value.events);
72
71
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-computer",
3
- "version": "0.3.15",
3
+ "version": "0.3.16",
4
4
  "description": "Replicas workspace computer-use runtime",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",