peachsim 0.1.626 → 0.1.628

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 (106) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +3 -2
  3. package/cli/bin.ts +6 -2
  4. package/cli/bridge-flow-runner.ts +79 -72
  5. package/cli/commands/box/bundle-plane.ts +7 -2
  6. package/cli/commands/box.ts +9 -2
  7. package/cli/commands/cleanup.ts +6 -6
  8. package/cli/commands/control.ts +36 -6
  9. package/cli/commands/detox.ts +25 -11
  10. package/cli/commands/flow.ts +20 -8
  11. package/cli/commands/inspect.ts +22 -37
  12. package/cli/commands/maestro-generate.ts +5 -12
  13. package/cli/commands/maestro.ts +16 -6
  14. package/cli/commands/mode.ts +11 -7
  15. package/cli/commands/platform.ts +1 -0
  16. package/cli/commands/report-issue.ts +1 -1
  17. package/cli/commands/setup.ts +16 -11
  18. package/cli/commands/show-touches-mode.ts +33 -0
  19. package/cli/commands/skills.ts +16 -0
  20. package/cli/commands/test.ts +22 -1
  21. package/cli/commands/upload.ts +18 -8
  22. package/cli/commands/welcome.ts +3 -3
  23. package/cli/drivers/playwright-sim-host.ts +16 -8
  24. package/cli/drivers/playwright.ts +8 -5
  25. package/cli/help-core.ts +9 -4
  26. package/cli/internal-child.ts +14 -2
  27. package/cli/levenshtein.ts +27 -0
  28. package/cli/main.ts +28 -18
  29. package/cli/run-rnx.ts +8 -4
  30. package/cli/ws-bridge.ts +3 -2
  31. package/detox/index.ts +234 -7
  32. package/dist-lib/agent-daemon-client.cjs +2 -1
  33. package/dist-lib/agent-events.cjs +1 -1
  34. package/dist-lib/agent-identity.cjs +1 -1
  35. package/dist-lib/agent-sessions.cjs +1 -1
  36. package/dist-lib/attached-projects.cjs +1 -1
  37. package/dist-lib/auth/shared-session.cjs +1 -1
  38. package/dist-lib/backend-origin.cjs +1 -1
  39. package/dist-lib/beta.cjs +1 -1
  40. package/dist-lib/beta.mjs +1 -1
  41. package/dist-lib/bridge-constants.cjs +4 -1
  42. package/dist-lib/bridge-contract-input.cjs +1 -1
  43. package/dist-lib/bridge-contract-input.mjs +1 -1
  44. package/dist-lib/bridge-contract.cjs +1 -1
  45. package/dist-lib/bridge-contract.mjs +1 -1
  46. package/dist-lib/capture-contract.cjs +18 -8
  47. package/dist-lib/capture-contract.mjs +18 -8
  48. package/dist-lib/cli-constants.cjs +1 -1
  49. package/dist-lib/cloud-contract.cjs +1 -1
  50. package/dist-lib/cloud-contract.mjs +1 -1
  51. package/dist-lib/cloud-test-args.cjs +1 -1
  52. package/dist-lib/cloud-test-args.mjs +1 -1
  53. package/dist-lib/cloud.cjs +1 -1
  54. package/dist-lib/cloud.mjs +1 -1
  55. package/dist-lib/config.cjs +1 -1
  56. package/dist-lib/detox/index.cjs +170 -13
  57. package/dist-lib/dev-bundle-resolution.cjs +2 -2
  58. package/dist-lib/home-paths.cjs +1 -1
  59. package/dist-lib/host/bridge-host.cjs +280 -138
  60. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  61. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  62. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  63. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  64. package/dist-lib/host/websocket-proxy.cjs +1 -1
  65. package/dist-lib/index.cjs +62 -12
  66. package/dist-lib/jump-to-source-babel.cjs +1 -1
  67. package/dist-lib/jump-to-source-native.cjs +1 -1
  68. package/dist-lib/menu.cjs +7 -1
  69. package/dist-lib/menu.mjs +7 -1
  70. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  71. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  72. package/dist-lib/metro-production-bundle.cjs +1 -1
  73. package/dist-lib/metro-production-bundle.mjs +1 -1
  74. package/dist-lib/metro.cjs +43 -4
  75. package/dist-lib/profiles.cjs +1 -1
  76. package/dist-lib/public-brand.cjs +1 -1
  77. package/dist-lib/react-native-host-modules.cjs +1 -1
  78. package/dist-lib/react-native-host-modules.mjs +1 -1
  79. package/dist-lib/render-mode.cjs +1 -1
  80. package/dist-lib/scripts/dev-server-scanner.cjs +215 -28
  81. package/dist-lib/sdk.cjs +1 -1
  82. package/dist-lib/sdk.mjs +1 -1
  83. package/dist-lib/skills.cjs +431 -276
  84. package/dist-lib/swift-compile.cjs +1 -1
  85. package/dist-lib/swift-compile.mjs +1 -1
  86. package/dist-lib/swift-package.cjs +1 -1
  87. package/dist-lib/swift-package.mjs +1 -1
  88. package/dist-lib/swift-submit.cjs +1 -1
  89. package/dist-lib/swift-submit.mjs +1 -1
  90. package/dist-lib/swift.cjs +14512 -14208
  91. package/dist-lib/vite-kotlin.cjs +1 -1
  92. package/dist-lib/vite-swift.cjs +1 -1
  93. package/dist-lib/vite.cjs +44 -4
  94. package/install.mjs +16 -0
  95. package/install.sh +199 -0
  96. package/package.json +6 -1
  97. package/scripts/dev-server-scanner.ts +77 -46
  98. package/skills/contrast/SKILL.md +1 -1
  99. package/src/bridge-constants.ts +11 -0
  100. package/src/capture-contract.ts +40 -7
  101. package/src/dev-bundle-resolution.ts +1 -1
  102. package/src/host/bridge-host.ts +26 -137
  103. package/src/menu.ts +7 -0
  104. package/src/native-seam-manifest.ts +4 -16
  105. package/src/runtime-assets.ts +59 -2
  106. package/src/vite-plugin.ts +723 -25
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.626 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -587,8 +587,8 @@ var init_shared_session = __esm({
587
587
  });
588
588
 
589
589
  // cli/run-rnx.ts
590
- function peachExit(code) {
591
- throw new PeachExit(code);
590
+ function peachExit(code, reason) {
591
+ throw new PeachExit(code, reason);
592
592
  }
593
593
  var import_node_async_hooks, PeachExit, sinks, rnxExit;
594
594
  var init_run_rnx = __esm({
@@ -597,10 +597,12 @@ var init_run_rnx = __esm({
597
597
  import_node_async_hooks = require("node:async_hooks");
598
598
  PeachExit = class extends Error {
599
599
  code;
600
- constructor(code) {
600
+ reason;
601
+ constructor(code, reason = null) {
601
602
  super(`peach exited with code ${code}`);
602
603
  this.name = "PeachExit";
603
604
  this.code = code;
605
+ this.reason = reason;
604
606
  }
605
607
  };
606
608
  sinks = new import_node_async_hooks.AsyncLocalStorage();
@@ -3253,6 +3255,8 @@ var init_metro_asset_path = __esm({
3253
3255
  function matchesModuleEntry(modulePath, pattern) {
3254
3256
  if (!modulePath || !pattern) return false;
3255
3257
  if (modulePath === pattern) return true;
3258
+ if (!modulePath.includes(pattern)) return false;
3259
+ if (!modulePath.includes(`node_modules/${pattern}`)) return false;
3256
3260
  if (modulePath === `node_modules/${pattern}`) return true;
3257
3261
  if (modulePath === `node_modules/${pattern}.js`) return true;
3258
3262
  if (modulePath === `node_modules/${pattern}.ts`) return true;
@@ -4675,6 +4679,161 @@ var import_ws = require("ws");
4675
4679
 
4676
4680
  // scripts/dev-server-scanner.ts
4677
4681
  var import_child_process = require("child_process");
4682
+
4683
+ // ../contrast/src/runtime/process-group.ts
4684
+ var import_node_child_process = require("node:child_process");
4685
+
4686
+ // ../contrast/src/sleep.ts
4687
+ function sleep(ms) {
4688
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
4689
+ }
4690
+
4691
+ // ../contrast/src/runtime/process-group.ts
4692
+ var GRACEFUL_TIMEOUT_MS = 3e3;
4693
+ var FORCED_EXIT_TIMEOUT_MS = 1e3;
4694
+ function signalProcessGroup(child, signal) {
4695
+ if (!child.pid) return;
4696
+ try {
4697
+ process.kill(-child.pid, signal);
4698
+ } catch {
4699
+ try {
4700
+ process.kill(child.pid, signal);
4701
+ } catch {
4702
+ }
4703
+ }
4704
+ }
4705
+ function runningPids(pids) {
4706
+ return pids.filter((pid) => {
4707
+ try {
4708
+ process.kill(pid, 0);
4709
+ return true;
4710
+ } catch (error) {
4711
+ return error instanceof Error && "code" in error && error.code === "EPERM";
4712
+ }
4713
+ });
4714
+ }
4715
+ function groupIsEmpty(pgid) {
4716
+ try {
4717
+ process.kill(-pgid, 0);
4718
+ } catch (error) {
4719
+ if (!(error instanceof Error && "code" in error && error.code === "EPERM"))
4720
+ return true;
4721
+ }
4722
+ const result = (0, import_node_child_process.spawnSync)("ps", ["-A", "-o", "pgid=,stat="], {
4723
+ encoding: "utf8",
4724
+ stdio: ["ignore", "pipe", "pipe"]
4725
+ });
4726
+ if (result.error || result.status !== 0) return false;
4727
+ return !result.stdout.split("\n").some((line) => {
4728
+ const [group, state] = line.trim().split(/\s+/);
4729
+ return Number(group) === pgid && state !== void 0 && !state.startsWith("Z");
4730
+ });
4731
+ }
4732
+ async function waitForExit(pids, pgid, timeoutMs) {
4733
+ const deadline = Date.now() + timeoutMs;
4734
+ const gone = () => runningPids(pids).length === 0 && groupIsEmpty(pgid);
4735
+ while (Date.now() < deadline) {
4736
+ if (gone()) return true;
4737
+ await sleep(50);
4738
+ }
4739
+ return gone();
4740
+ }
4741
+ async function terminateProcessGroup(child, signal = "SIGTERM", gracefulTimeoutMs = GRACEFUL_TIMEOUT_MS) {
4742
+ if (!child.pid) {
4743
+ try {
4744
+ child.kill(signal);
4745
+ } catch {
4746
+ }
4747
+ return;
4748
+ }
4749
+ signalProcessGroup(child, "SIGSTOP");
4750
+ const descendants = /* @__PURE__ */ new Set();
4751
+ let parents = [child.pid];
4752
+ let inspectionError = null;
4753
+ while (parents.length > 0) {
4754
+ const result = (0, import_node_child_process.spawnSync)("pgrep", ["-P", parents.join(",")], {
4755
+ encoding: "utf8",
4756
+ stdio: ["ignore", "pipe", "pipe"]
4757
+ });
4758
+ if (result.error || result.status !== 0 && result.status !== 1) {
4759
+ inspectionError = new Error(
4760
+ `could not inspect descendants of processes ${parents.join(",")}: ${result.error?.message || result.stderr.trim() || `pgrep exited ${result.status}`}`
4761
+ );
4762
+ break;
4763
+ }
4764
+ const next = [];
4765
+ for (const line of result.stdout.split("\n")) {
4766
+ const descendantPid = Number(line.trim());
4767
+ if (Number.isInteger(descendantPid) && descendantPid > 1 && !descendants.has(descendantPid)) {
4768
+ descendants.add(descendantPid);
4769
+ next.push(descendantPid);
4770
+ try {
4771
+ process.kill(descendantPid, "SIGSTOP");
4772
+ } catch {
4773
+ }
4774
+ }
4775
+ }
4776
+ parents = next;
4777
+ }
4778
+ const pids = [child.pid, ...descendants];
4779
+ const gracefulDeadline = Date.now() + gracefulTimeoutMs;
4780
+ try {
4781
+ process.kill(child.pid, signal);
4782
+ } catch {
4783
+ }
4784
+ signalProcessGroup(child, "SIGCONT");
4785
+ for (const pid of descendants) {
4786
+ try {
4787
+ process.kill(pid, "SIGCONT");
4788
+ } catch {
4789
+ }
4790
+ }
4791
+ let rootExited = child.exitCode !== null || child.signalCode !== null;
4792
+ if (!rootExited) {
4793
+ rootExited = await new Promise((resolveExit) => {
4794
+ const onExit = () => {
4795
+ clearTimeout(deadlineTimer);
4796
+ resolveExit(true);
4797
+ };
4798
+ const deadlineTimer = setTimeout(
4799
+ () => {
4800
+ child.removeListener("exit", onExit);
4801
+ resolveExit(false);
4802
+ },
4803
+ Math.max(0, gracefulDeadline - Date.now())
4804
+ );
4805
+ child.once("exit", onExit);
4806
+ if (child.exitCode !== null || child.signalCode !== null) {
4807
+ child.removeListener("exit", onExit);
4808
+ onExit();
4809
+ }
4810
+ });
4811
+ }
4812
+ if (rootExited) {
4813
+ signalProcessGroup(child, signal);
4814
+ for (const pid of descendants) {
4815
+ try {
4816
+ process.kill(pid, signal);
4817
+ } catch {
4818
+ }
4819
+ }
4820
+ }
4821
+ if (await waitForExit(pids, child.pid, Math.max(0, gracefulDeadline - Date.now()))) {
4822
+ if (inspectionError) throw inspectionError;
4823
+ return;
4824
+ }
4825
+ for (const pid of pids) {
4826
+ try {
4827
+ process.kill(pid, "SIGKILL");
4828
+ } catch {
4829
+ }
4830
+ }
4831
+ signalProcessGroup(child, "SIGKILL");
4832
+ await waitForExit(pids, child.pid, FORCED_EXIT_TIMEOUT_MS);
4833
+ if (inspectionError) throw inspectionError;
4834
+ }
4835
+
4836
+ // scripts/dev-server-scanner.ts
4678
4837
  var import_promises2 = require("fs/promises");
4679
4838
  var import_http = __toESM(require("http"), 1);
4680
4839
  var import_net = __toESM(require("net"), 1);
@@ -5088,29 +5247,51 @@ async function readAppSettingsBundle(projectRoot) {
5088
5247
 
5089
5248
  // scripts/dev-server-scanner.ts
5090
5249
  init_optional_demo_registry();
5091
- var EXEC_EXIT_GRACE_MS = 2e3;
5092
- async function execGuarded(command, timeout, extra = {}) {
5093
- let child;
5094
- const ran = new Promise((resolve5, reject) => {
5095
- child = (0, import_child_process.exec)(command, { encoding: "utf8", timeout, ...extra }, (err, stdout) => {
5250
+ function execGuarded(command, timeout, extra = {}) {
5251
+ const maxBuffer = extra.maxBuffer ?? 1024 * 1024;
5252
+ return new Promise((resolve5, reject) => {
5253
+ const child = (0, import_child_process.spawn)("/bin/sh", ["-c", command], {
5254
+ detached: true,
5255
+ stdio: ["ignore", "pipe", "ignore"]
5256
+ });
5257
+ let stdout = "";
5258
+ let settled = false;
5259
+ let killing = false;
5260
+ const settle = (err) => {
5261
+ if (settled) return;
5262
+ settled = true;
5263
+ clearTimeout(deadline);
5096
5264
  if (err) reject(err);
5097
5265
  else resolve5({ stdout });
5266
+ };
5267
+ const killThenSettle = (err) => {
5268
+ if (killing) return;
5269
+ killing = true;
5270
+ clearTimeout(deadline);
5271
+ terminateProcessGroup(child, "SIGKILL", 0).finally(() => settle(err));
5272
+ };
5273
+ const deadline = setTimeout(() => {
5274
+ killThenSettle(
5275
+ Object.assign(new Error(`\`${command}\` did not exit within ${timeout}ms`), {
5276
+ killed: true
5277
+ })
5278
+ );
5279
+ }, timeout);
5280
+ child.stdout.setEncoding("utf8");
5281
+ child.stdout.on("data", (chunk) => {
5282
+ stdout += chunk;
5283
+ if (stdout.length > maxBuffer) {
5284
+ killThenSettle(new Error(`\`${command}\` exceeded maxBuffer ${maxBuffer}`));
5285
+ }
5286
+ });
5287
+ child.on("error", (err) => {
5288
+ if (!killing) settle(err);
5289
+ });
5290
+ child.on("close", (code) => {
5291
+ if (killing) return;
5292
+ settle(code === 0 ? null : new Error(`\`${command}\` exited with ${code}`));
5098
5293
  });
5099
5294
  });
5100
- let deadlineTimer;
5101
- const deadline = new Promise((_resolve, reject) => {
5102
- deadlineTimer = setTimeout(() => {
5103
- child?.kill("SIGKILL");
5104
- const err = new Error(`\`${command}\` did not exit within ${timeout}ms`);
5105
- Object.assign(err, { killed: true });
5106
- reject(err);
5107
- }, timeout + EXEC_EXIT_GRACE_MS);
5108
- });
5109
- try {
5110
- return await Promise.race([ran, deadline]);
5111
- } finally {
5112
- clearTimeout(deadlineTimer);
5113
- }
5114
5295
  }
5115
5296
  var TIMEOUT_MS = 250;
5116
5297
  var MANIFEST_TIMEOUT_MS = 1500;
@@ -5145,6 +5326,7 @@ async function tcpPing(port, timeout = TCP_GATE_MS) {
5145
5326
  function httpGet(hostname, port, path12, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
5146
5327
  return new Promise((resolve5) => {
5147
5328
  let settled = false;
5329
+ let killing = false;
5148
5330
  const settle = (value) => {
5149
5331
  if (settled) return;
5150
5332
  settled = true;
@@ -5219,6 +5401,21 @@ function wasKilledByTimeout(err) {
5219
5401
  }
5220
5402
  async function scanListeningProcesses(excludePorts = []) {
5221
5403
  const excluded = new Set(excludePorts);
5404
+ const listing = await listListenersShared();
5405
+ if (listing.state === "could-not-look") return listing;
5406
+ return {
5407
+ state: "looked",
5408
+ processes: listing.processes.filter((p) => acceptPort(p.port, excluded))
5409
+ };
5410
+ }
5411
+ var inflightListing = null;
5412
+ function listListenersShared() {
5413
+ inflightListing ??= listListeners().finally(() => {
5414
+ inflightListing = null;
5415
+ });
5416
+ return inflightListing;
5417
+ }
5418
+ async function listListeners() {
5222
5419
  let timedOut = false;
5223
5420
  try {
5224
5421
  const { stdout } = await execGuarded(
@@ -5230,11 +5427,9 @@ async function scanListeningProcesses(excludePorts = []) {
5230
5427
  const parts = line.trim().split(/\s+/);
5231
5428
  if (parts.length < 9) continue;
5232
5429
  const pid = Number(parts[1]);
5233
- const addr = parts[8];
5234
- const m = addr.match(/:(\d+)$/);
5430
+ const m = parts[8].match(/:(\d+)$/);
5235
5431
  if (!m) continue;
5236
5432
  const port = Number(m[1]);
5237
- if (!acceptPort(port, excluded)) continue;
5238
5433
  if (!seen.has(port)) seen.set(port, pid);
5239
5434
  }
5240
5435
  return {
@@ -5256,7 +5451,6 @@ async function scanListeningProcesses(excludePorts = []) {
5256
5451
  if (!portMatch) continue;
5257
5452
  const port = Number(portMatch[1]);
5258
5453
  const pid = pidMatch ? Number(pidMatch[1]) : 0;
5259
- if (!acceptPort(port, excluded)) continue;
5260
5454
  if (!seen.has(port)) seen.set(port, pid);
5261
5455
  }
5262
5456
  return {
@@ -5267,10 +5461,7 @@ async function scanListeningProcesses(excludePorts = []) {
5267
5461
  if (wasKilledByTimeout(err)) timedOut = true;
5268
5462
  }
5269
5463
  if (timedOut) return { state: "could-not-look" };
5270
- return {
5271
- state: "looked",
5272
- processes: FALLBACK_PORTS.filter((p) => acceptPort(p.port, excluded))
5273
- };
5464
+ return { state: "looked", processes: FALLBACK_PORTS };
5274
5465
  }
5275
5466
  function pidFromNetstat(stdout, port) {
5276
5467
  for (const line of stdout.split("\n")) {
@@ -5776,6 +5967,7 @@ var DEFAULT_SOOTSIM_BRIDGE_PORT = 7668;
5776
5967
  var DEFAULT_SOOTSIM_BRIDGE_URL = `http://localhost:${DEFAULT_SOOTSIM_BRIDGE_PORT}/`;
5777
5968
  var SOOTSIM_BRIDGE_SIM_CLOSE_CODE = 4001;
5778
5969
  var SOOTSIM_BRIDGE_SIM_CLOSE_REASON = "sootsim close";
5970
+ var AUTOMATION_SIM_IDLE_REAP_TTL_MS = 5 * 6e4;
5779
5971
 
5780
5972
  // src/cli-version.ts
5781
5973
  var import_node_fs3 = require("node:fs");
@@ -5817,7 +6009,7 @@ var import_path = __toESM(require("path"), 1);
5817
6009
  var import_url = require("url");
5818
6010
 
5819
6011
  // ../contrast-runtime-delivery/src/index.ts
5820
- var import_node_child_process = require("node:child_process");
6012
+ var import_node_child_process2 = require("node:child_process");
5821
6013
  var import_node_crypto2 = __toESM(require("node:crypto"), 1);
5822
6014
  var import_node_fs6 = __toESM(require("node:fs"), 1);
5823
6015
  var import_node_os2 = __toESM(require("node:os"), 1);
@@ -5969,10 +6161,12 @@ function createRuntimeDelivery(product) {
5969
6161
  );
5970
6162
  import_node_fs6.default.rmSync(tmpDir, { recursive: true, force: true });
5971
6163
  try {
5972
- process.stderr.write(`${product.id}: downloading runtime ${version}\u2026
6164
+ if (!opts.quiet)
6165
+ process.stderr.write(`${product.id}: downloading runtime ${version}\u2026
5973
6166
  `);
5974
6167
  await downloadToFile(tarUrl, runtimeArchivePath);
5975
- process.stderr.write(`${product.id}: extracting runtime ${version}\u2026
6168
+ if (!opts.quiet)
6169
+ process.stderr.write(`${product.id}: extracting runtime ${version}\u2026
5976
6170
  `);
5977
6171
  const actualSha = await sha256File(runtimeArchivePath);
5978
6172
  if (actualSha !== entry.sha256) {
@@ -6216,7 +6410,7 @@ function sha256File(filePath) {
6216
6410
  }
6217
6411
  function extractTarball(tarPath, destDir) {
6218
6412
  return new Promise((resolve5, reject) => {
6219
- const child = (0, import_node_child_process.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
6413
+ const child = (0, import_node_child_process2.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
6220
6414
  stdio: ["ignore", "inherit", "inherit"]
6221
6415
  });
6222
6416
  child.on("error", reject);
@@ -6291,6 +6485,41 @@ function resolveRuntimeRootForHostname(hostname) {
6291
6485
  const version = runtimeVersionFromHostname(hostname);
6292
6486
  return version ? resolveInstalledRuntimeRoot(version) : null;
6293
6487
  }
6488
+ function sendFileWithRanges(req, res, filePath, total, headers) {
6489
+ const head = (req?.method || "GET").toUpperCase() === "HEAD";
6490
+ const range = /^bytes=(\d*)-(\d*)$/.exec(req?.headers?.range || "");
6491
+ const invalid = range && range[1] && range[2] && Number(range[2]) < Number(range[1]);
6492
+ if (range && (range[1] || range[2]) && !invalid) {
6493
+ const [, startRaw, endRaw] = range;
6494
+ const start = startRaw ? Number(startRaw) : Math.max(0, total - Number(endRaw));
6495
+ const end = startRaw && endRaw ? Math.min(Number(endRaw), total - 1) : total - 1;
6496
+ if (start > end || start >= total) {
6497
+ res.writeHead(416, { ...headers, "Content-Range": `bytes */${total}` });
6498
+ res.end();
6499
+ return;
6500
+ }
6501
+ res.writeHead(206, {
6502
+ ...headers,
6503
+ "Content-Range": `bytes ${start}-${end}/${total}`,
6504
+ "Accept-Ranges": "bytes",
6505
+ "Content-Length": String(end - start + 1)
6506
+ });
6507
+ if (head) res.end();
6508
+ else streamFile(res, import_fs.default.createReadStream(filePath, { start, end }));
6509
+ return;
6510
+ }
6511
+ res.writeHead(200, {
6512
+ ...headers,
6513
+ "Accept-Ranges": "bytes",
6514
+ "Content-Length": String(total)
6515
+ });
6516
+ if (head || total === 0) res.end();
6517
+ else streamFile(res, import_fs.default.createReadStream(filePath, { start: 0, end: total - 1 }));
6518
+ }
6519
+ function streamFile(res, stream) {
6520
+ stream.on("error", (error) => res.destroy(error));
6521
+ stream.pipe(res);
6522
+ }
6294
6523
 
6295
6524
  // src/runtime-delivery.ts
6296
6525
  init_home_paths();
@@ -6326,7 +6555,7 @@ var import_node_fs10 = __toESM(require("node:fs"), 1);
6326
6555
  var import_node_path10 = __toESM(require("node:path"), 1);
6327
6556
 
6328
6557
  // src/agent-sessions.ts
6329
- var import_node_child_process2 = require("node:child_process");
6558
+ var import_node_child_process3 = require("node:child_process");
6330
6559
  var import_node_crypto4 = require("node:crypto");
6331
6560
  var import_node_fs9 = __toESM(require("node:fs"), 1);
6332
6561
  var import_node_path9 = __toESM(require("node:path"), 1);
@@ -6880,7 +7109,7 @@ function mkfifoSync(p) {
6880
7109
  import_node_fs9.default.unlinkSync(p);
6881
7110
  }
6882
7111
  }
6883
- const result = (0, import_node_child_process2.spawnSync)("mkfifo", ["-m", "600", p]);
7112
+ const result = (0, import_node_child_process3.spawnSync)("mkfifo", ["-m", "600", p]);
6884
7113
  if (result.status !== 0) {
6885
7114
  throw new Error(
6886
7115
  `mkfifo(${p}) failed: ${result.stderr?.toString().trim() || "unknown error"}`
@@ -6955,7 +7184,7 @@ async function startSession(opts) {
6955
7184
  if (claudeSessionUuid) {
6956
7185
  wrapperArgs.push("--claude-session-uuid", claudeSessionUuid);
6957
7186
  }
6958
- const child = (0, import_node_child_process2.spawn)(executable, wrapperArgs, {
7187
+ const child = (0, import_node_child_process3.spawn)(executable, wrapperArgs, {
6959
7188
  detached: true,
6960
7189
  stdio: "ignore",
6961
7190
  env: {
@@ -8167,7 +8396,7 @@ async function dispatchPlaneCommand(plane, type, msg, mutationOptions) {
8167
8396
  }
8168
8397
 
8169
8398
  // cli/commands/box/checkout-plane.ts
8170
- var import_node_child_process3 = require("node:child_process");
8399
+ var import_node_child_process4 = require("node:child_process");
8171
8400
  var import_node_fs11 = __toESM(require("node:fs"), 1);
8172
8401
  var import_node_path11 = __toESM(require("node:path"), 1);
8173
8402
  var CheckoutRequiresGitError = class extends Error {
@@ -8209,7 +8438,7 @@ var CheckoutFilePlane = class {
8209
8438
  }
8210
8439
  listFiles() {
8211
8440
  const args = this.trackedOnly ? ["ls-files", "-z"] : ["ls-files", "-co", "--exclude-standard", "-z"];
8212
- const stdout = (0, import_node_child_process3.execFileSync)("git", args, {
8441
+ const stdout = (0, import_node_child_process4.execFileSync)("git", args, {
8213
8442
  cwd: this.root,
8214
8443
  encoding: "utf8",
8215
8444
  maxBuffer: 64 * 1024 * 1024,
@@ -9250,24 +9479,8 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
9250
9479
  // terminal 4001 code, so the client closes the abandoned window instead
9251
9480
  // of reconnecting the zombie straight back in.
9252
9481
  static SIM_IDLE_REAP_TTL_MS = 30 * 6e4;
9253
- // automation sims (spawned by the playwright driver: `peach open --driver
9254
- // playwright`, the factory/screenshot capture path — `meta.sootsimHostDriver
9255
- // === 'playwright'`) are unattended one-shot browsers, never a human's tab. a
9256
- // capture run opens one per slide/app/retry and the 30-min human/dev TTL lets
9257
- // hundreds pile up inside the window — orphaned ms-playwright chromiums that
9258
- // drove the box to swap-thrash (load hit 157 in one incident). give them a far
9259
- // shorter idle TTL, and cap how many can sit concurrently: reap the most-idle
9260
- // overflow, but never a sim active within ACTIVE_GRACE (an agent composing
9261
- // between captures), nor primary / cli-attached / leased sims.
9262
- static AUTOMATION_SIM_IDLE_REAP_TTL_MS = 10 * 6e4;
9263
- static MAX_CONCURRENT_AUTOMATION_SIMS = 6;
9264
- static AUTOMATION_SIM_ACTIVE_GRACE_MS = 6e4;
9265
9482
  preferredPort;
9266
9483
  portFallbackCount;
9267
- simIdleReapTtlMs;
9268
- automationSimIdleReapTtlMs;
9269
- maxConcurrentAutomationSims;
9270
- automationSimActiveGraceMs;
9271
9484
  shouldWriteLockfile;
9272
9485
  shouldWriteDevLockfile;
9273
9486
  getShellPort;
@@ -9310,23 +9523,12 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
9310
9523
  getExcludePorts: opts.agentScanExcludes
9311
9524
  });
9312
9525
  this.contrastOrigin = opts.contrastOrigin?.replace(/\/$/, "") || null;
9313
- this.simIdleReapTtlMs = opts.simIdleReapTtlMs ?? _SootSimBridgeHost.SIM_IDLE_REAP_TTL_MS;
9314
- this.automationSimIdleReapTtlMs = opts.automationSimIdleReapTtlMs ?? _SootSimBridgeHost.AUTOMATION_SIM_IDLE_REAP_TTL_MS;
9315
- this.maxConcurrentAutomationSims = opts.maxConcurrentAutomationSims ?? _SootSimBridgeHost.MAX_CONCURRENT_AUTOMATION_SIMS;
9316
- this.automationSimActiveGraceMs = opts.automationSimActiveGraceMs ?? _SootSimBridgeHost.AUTOMATION_SIM_ACTIVE_GRACE_MS;
9317
9526
  }
9318
9527
  /** expose the agent host so tests and embedders can inspect state or
9319
9528
  * inject behavior. not part of the public WS protocol. */
9320
9529
  getAgentHost() {
9321
9530
  return this.agentHost;
9322
9531
  }
9323
- /** run the abandoned-sim GC pass on demand. the reaper normally fires off
9324
- * the 30s idle-sweep timer; this lets the F21-3 integration test drive
9325
- * the real path without waiting for the timer. not part of the public
9326
- * WS protocol. */
9327
- reapIdleSimsForTest(now = Date.now()) {
9328
- this.reapIdleSims(now);
9329
- }
9330
9532
  /** synchronous wrapper around startAsync for callers that don't care
9331
9533
  * about port fallback outcomes. returns immediately; actual binding
9332
9534
  * happens on the event loop. callers that need to know the bound port
@@ -9572,6 +9774,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
9572
9774
  this.updateUserActivity(sim);
9573
9775
  return;
9574
9776
  }
9777
+ if (msg.type === "bridge:capture-active" && sim) {
9778
+ sim.lastActiveAt = Date.now();
9779
+ return;
9780
+ }
9575
9781
  if (msg.type === "bridge:write-shared-config") {
9576
9782
  if (!msg.patch || typeof msg.patch !== "object" || Array.isArray(msg.patch)) {
9577
9783
  return;
@@ -10020,42 +10226,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
10020
10226
  return;
10021
10227
  }
10022
10228
  const contentType = HTTP_MIME_TYPES[import_path3.default.extname(filePath).toLowerCase()] || "application/octet-stream";
10023
- const total = stats.size;
10024
- const range = /^bytes=(\d*)-(\d*)$/.exec(req.headers.range || "");
10025
- if (range) {
10026
- const [, startRaw, endRaw] = range;
10027
- const start = startRaw ? Number(startRaw) : 0;
10028
- const end = endRaw ? Math.min(Number(endRaw), total - 1) : total - 1;
10029
- if (!Number.isFinite(start) || start > end || start >= total) {
10030
- res.writeHead(416, { ...cors, "Content-Range": `bytes */${total}` });
10031
- res.end();
10032
- return;
10033
- }
10034
- res.writeHead(206, {
10035
- ...cors,
10036
- "Content-Type": contentType,
10037
- "Content-Range": `bytes ${start}-${end}/${total}`,
10038
- "Accept-Ranges": "bytes",
10039
- "Content-Length": String(end - start + 1)
10040
- });
10041
- if (method === "HEAD") {
10042
- res.end();
10043
- return;
10044
- }
10045
- import_fs3.default.createReadStream(filePath, { start, end }).pipe(res);
10046
- return;
10047
- }
10048
- res.writeHead(200, {
10229
+ sendFileWithRanges(req, res, filePath, stats.size, {
10049
10230
  ...cors,
10050
- "Content-Type": contentType,
10051
- "Accept-Ranges": "bytes",
10052
- "Content-Length": String(total)
10231
+ "Content-Type": contentType
10053
10232
  });
10054
- if (method === "HEAD") {
10055
- res.end();
10056
- return;
10057
- }
10058
- import_fs3.default.createReadStream(filePath).pipe(res);
10059
10233
  }
10060
10234
  handleServerScan(res) {
10061
10235
  const sendJson = (body) => {
@@ -10572,53 +10746,21 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
10572
10746
  // automation sims are spawned by the playwright driver (`peach open
10573
10747
  // --driver playwright`, the factory/screenshot capture path), which tags them
10574
10748
  // with `meta.sootsimHostDriver === 'playwright'` via the host's addInitScript.
10575
- // they are unattended one-shot browsers, never a human's tab, so they get a
10576
- // shorter idle TTL and a concurrent cap.
10577
10749
  isAutomationSim(sim) {
10578
10750
  return sim.meta?.sootsimHostDriver === "playwright";
10579
10751
  }
10580
- // an automation sim whose spawning playwright host process is still alive is
10581
- // an ACTIVE run, not an abandoned tab. mirrors the cli's playwrightHostPidForSim
10582
- // (control.ts): extract the host pid from meta, then check liveness so the
10583
- // host-side idle GC never kills a live capture/factory sim mid-run.
10584
- automationSimOwnerAlive(sim) {
10585
- const pid = Number(sim.meta?.sootsimHostPid);
10586
- if (!Number.isInteger(pid) || pid <= 1 || pid === process.pid) return false;
10587
- try {
10588
- process.kill(pid, 0);
10589
- return true;
10590
- } catch (err) {
10591
- return err.code === "EPERM";
10592
- }
10593
- }
10594
10752
  reapIdleSims(now = Date.now()) {
10595
10753
  const attachedSimIds = new Set(this.cliSimBySocket.values());
10596
- const automationLive = [];
10597
10754
  for (const sim of this.sims.values()) {
10598
- if (sim.id === this.primarySimId) continue;
10599
10755
  if (attachedSimIds.has(sim.id)) continue;
10600
- if (this.getActiveLease(sim)) continue;
10601
- const lastTouched = Math.max(sim.lastActiveAt, sim.connectedAt);
10602
- const idleMs = now - lastTouched;
10603
10756
  const automation = this.isAutomationSim(sim);
10604
- if (automation && this.automationSimOwnerAlive(sim)) continue;
10605
- const ttl = automation ? this.automationSimIdleReapTtlMs : this.simIdleReapTtlMs;
10606
- if (idleMs >= ttl) {
10607
- this.closeSimSocketFromHost(sim.ws);
10757
+ const lease = this.getActiveLease(sim);
10758
+ if (automation ? lease?.kind === "user-active" : sim.id === this.primarySimId || lease) {
10608
10759
  continue;
10609
10760
  }
10610
- if (automation) automationLive.push({ sim, idleMs });
10611
- }
10612
- const cap = this.maxConcurrentAutomationSims;
10613
- if (automationLive.length > cap) {
10614
- automationLive.sort((a, b) => b.idleMs - a.idleMs);
10615
- for (const { sim, idleMs } of automationLive.slice(
10616
- 0,
10617
- automationLive.length - cap
10618
- )) {
10619
- if (idleMs < this.automationSimActiveGraceMs) continue;
10620
- this.closeSimSocketFromHost(sim.ws);
10621
- }
10761
+ const idleMs = now - Math.max(sim.lastActiveAt, sim.connectedAt);
10762
+ const ttl = automation ? AUTOMATION_SIM_IDLE_REAP_TTL_MS : _SootSimBridgeHost.SIM_IDLE_REAP_TTL_MS;
10763
+ if (idleMs >= ttl) this.closeSimSocketFromHost(sim.ws);
10622
10764
  }
10623
10765
  }
10624
10766
  // ping every connected ws; if the previous round's ping went unanswered,
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.626 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.626 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.626 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/host/fetch-proxy-overrides.ts
4
4
  var FETCH_PROXY_BROWSER_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.626 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.626 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.628 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;