peachsim 0.1.625 → 0.1.627

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 (82) hide show
  1. package/README.md +3 -2
  2. package/cli/commands/box/bundle-plane.ts +7 -2
  3. package/cli/commands/box.ts +9 -2
  4. package/cli/commands/control.ts +32 -1
  5. package/cli/commands/flow.ts +17 -6
  6. package/cli/commands/maestro-generate.ts +5 -12
  7. package/cli/commands/maestro.ts +16 -6
  8. package/cli/commands/platform.ts +1 -0
  9. package/cli/commands/test.ts +14 -0
  10. package/cli/commands/upload.ts +18 -8
  11. package/cli/drivers/playwright-sim-host.ts +10 -3
  12. package/cli/drivers/playwright.ts +8 -5
  13. package/cli/run-rnx.ts +8 -4
  14. package/dist-lib/agent-daemon-client.cjs +2 -1
  15. package/dist-lib/agent-events.cjs +1 -1
  16. package/dist-lib/agent-identity.cjs +1 -1
  17. package/dist-lib/agent-sessions.cjs +1 -1
  18. package/dist-lib/attached-projects.cjs +1 -1
  19. package/dist-lib/auth/shared-session.cjs +1 -1
  20. package/dist-lib/backend-origin.cjs +1 -1
  21. package/dist-lib/beta.cjs +1 -1
  22. package/dist-lib/beta.mjs +1 -1
  23. package/dist-lib/bridge-constants.cjs +4 -1
  24. package/dist-lib/bridge-contract-input.cjs +1 -1
  25. package/dist-lib/bridge-contract-input.mjs +1 -1
  26. package/dist-lib/bridge-contract.cjs +1 -1
  27. package/dist-lib/bridge-contract.mjs +1 -1
  28. package/dist-lib/capture-contract.cjs +18 -8
  29. package/dist-lib/capture-contract.mjs +18 -8
  30. package/dist-lib/cli-constants.cjs +1 -1
  31. package/dist-lib/cloud-contract.cjs +1 -1
  32. package/dist-lib/cloud-contract.mjs +1 -1
  33. package/dist-lib/cloud-test-args.cjs +1 -1
  34. package/dist-lib/cloud-test-args.mjs +1 -1
  35. package/dist-lib/cloud.cjs +1 -1
  36. package/dist-lib/cloud.mjs +1 -1
  37. package/dist-lib/config.cjs +1 -1
  38. package/dist-lib/detox/index.cjs +1 -1
  39. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  40. package/dist-lib/home-paths.cjs +1 -1
  41. package/dist-lib/host/bridge-host.cjs +239 -102
  42. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  43. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  44. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  45. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  46. package/dist-lib/host/websocket-proxy.cjs +1 -1
  47. package/dist-lib/index.cjs +19 -8
  48. package/dist-lib/jump-to-source-babel.cjs +1 -1
  49. package/dist-lib/jump-to-source-native.cjs +1 -1
  50. package/dist-lib/menu.cjs +1 -1
  51. package/dist-lib/menu.mjs +1 -1
  52. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  53. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  54. package/dist-lib/metro-production-bundle.cjs +1 -1
  55. package/dist-lib/metro-production-bundle.mjs +1 -1
  56. package/dist-lib/metro.cjs +1 -1
  57. package/dist-lib/profiles.cjs +1 -1
  58. package/dist-lib/public-brand.cjs +1 -1
  59. package/dist-lib/react-native-host-modules.cjs +1 -1
  60. package/dist-lib/react-native-host-modules.mjs +1 -1
  61. package/dist-lib/render-mode.cjs +1 -1
  62. package/dist-lib/scripts/dev-server-scanner.cjs +215 -28
  63. package/dist-lib/sdk.cjs +1 -1
  64. package/dist-lib/sdk.mjs +1 -1
  65. package/dist-lib/skills.cjs +292 -128
  66. package/dist-lib/swift-compile.cjs +1 -1
  67. package/dist-lib/swift-compile.mjs +1 -1
  68. package/dist-lib/swift-package.cjs +1 -1
  69. package/dist-lib/swift-package.mjs +1 -1
  70. package/dist-lib/swift-submit.cjs +1 -1
  71. package/dist-lib/swift-submit.mjs +1 -1
  72. package/dist-lib/swift.cjs +14488 -14349
  73. package/dist-lib/vite-kotlin.cjs +1 -1
  74. package/dist-lib/vite-swift.cjs +1 -1
  75. package/dist-lib/vite.cjs +2 -1
  76. package/install.mjs +16 -0
  77. package/install.sh +199 -0
  78. package/package.json +6 -1
  79. package/scripts/dev-server-scanner.ts +77 -46
  80. package/src/bridge-constants.ts +11 -0
  81. package/src/capture-contract.ts +40 -7
  82. package/src/host/bridge-host.ts +24 -103
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (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);
@@ -6216,7 +6408,7 @@ function sha256File(filePath) {
6216
6408
  }
6217
6409
  function extractTarball(tarPath, destDir) {
6218
6410
  return new Promise((resolve5, reject) => {
6219
- const child = (0, import_node_child_process.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
6411
+ const child = (0, import_node_child_process2.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
6220
6412
  stdio: ["ignore", "inherit", "inherit"]
6221
6413
  });
6222
6414
  child.on("error", reject);
@@ -6326,7 +6518,7 @@ var import_node_fs10 = __toESM(require("node:fs"), 1);
6326
6518
  var import_node_path10 = __toESM(require("node:path"), 1);
6327
6519
 
6328
6520
  // src/agent-sessions.ts
6329
- var import_node_child_process2 = require("node:child_process");
6521
+ var import_node_child_process3 = require("node:child_process");
6330
6522
  var import_node_crypto4 = require("node:crypto");
6331
6523
  var import_node_fs9 = __toESM(require("node:fs"), 1);
6332
6524
  var import_node_path9 = __toESM(require("node:path"), 1);
@@ -6880,7 +7072,7 @@ function mkfifoSync(p) {
6880
7072
  import_node_fs9.default.unlinkSync(p);
6881
7073
  }
6882
7074
  }
6883
- const result = (0, import_node_child_process2.spawnSync)("mkfifo", ["-m", "600", p]);
7075
+ const result = (0, import_node_child_process3.spawnSync)("mkfifo", ["-m", "600", p]);
6884
7076
  if (result.status !== 0) {
6885
7077
  throw new Error(
6886
7078
  `mkfifo(${p}) failed: ${result.stderr?.toString().trim() || "unknown error"}`
@@ -6955,7 +7147,7 @@ async function startSession(opts) {
6955
7147
  if (claudeSessionUuid) {
6956
7148
  wrapperArgs.push("--claude-session-uuid", claudeSessionUuid);
6957
7149
  }
6958
- const child = (0, import_node_child_process2.spawn)(executable, wrapperArgs, {
7150
+ const child = (0, import_node_child_process3.spawn)(executable, wrapperArgs, {
6959
7151
  detached: true,
6960
7152
  stdio: "ignore",
6961
7153
  env: {
@@ -8167,7 +8359,7 @@ async function dispatchPlaneCommand(plane, type, msg, mutationOptions) {
8167
8359
  }
8168
8360
 
8169
8361
  // cli/commands/box/checkout-plane.ts
8170
- var import_node_child_process3 = require("node:child_process");
8362
+ var import_node_child_process4 = require("node:child_process");
8171
8363
  var import_node_fs11 = __toESM(require("node:fs"), 1);
8172
8364
  var import_node_path11 = __toESM(require("node:path"), 1);
8173
8365
  var CheckoutRequiresGitError = class extends Error {
@@ -8209,7 +8401,7 @@ var CheckoutFilePlane = class {
8209
8401
  }
8210
8402
  listFiles() {
8211
8403
  const args = this.trackedOnly ? ["ls-files", "-z"] : ["ls-files", "-co", "--exclude-standard", "-z"];
8212
- const stdout = (0, import_node_child_process3.execFileSync)("git", args, {
8404
+ const stdout = (0, import_node_child_process4.execFileSync)("git", args, {
8213
8405
  cwd: this.root,
8214
8406
  encoding: "utf8",
8215
8407
  maxBuffer: 64 * 1024 * 1024,
@@ -9250,24 +9442,8 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
9250
9442
  // terminal 4001 code, so the client closes the abandoned window instead
9251
9443
  // of reconnecting the zombie straight back in.
9252
9444
  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
9445
  preferredPort;
9266
9446
  portFallbackCount;
9267
- simIdleReapTtlMs;
9268
- automationSimIdleReapTtlMs;
9269
- maxConcurrentAutomationSims;
9270
- automationSimActiveGraceMs;
9271
9447
  shouldWriteLockfile;
9272
9448
  shouldWriteDevLockfile;
9273
9449
  getShellPort;
@@ -9310,23 +9486,12 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
9310
9486
  getExcludePorts: opts.agentScanExcludes
9311
9487
  });
9312
9488
  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
9489
  }
9318
9490
  /** expose the agent host so tests and embedders can inspect state or
9319
9491
  * inject behavior. not part of the public WS protocol. */
9320
9492
  getAgentHost() {
9321
9493
  return this.agentHost;
9322
9494
  }
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
9495
  /** synchronous wrapper around startAsync for callers that don't care
9331
9496
  * about port fallback outcomes. returns immediately; actual binding
9332
9497
  * happens on the event loop. callers that need to know the bound port
@@ -9572,6 +9737,10 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
9572
9737
  this.updateUserActivity(sim);
9573
9738
  return;
9574
9739
  }
9740
+ if (msg.type === "bridge:capture-active" && sim) {
9741
+ sim.lastActiveAt = Date.now();
9742
+ return;
9743
+ }
9575
9744
  if (msg.type === "bridge:write-shared-config") {
9576
9745
  if (!msg.patch || typeof msg.patch !== "object" || Array.isArray(msg.patch)) {
9577
9746
  return;
@@ -10572,53 +10741,21 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
10572
10741
  // automation sims are spawned by the playwright driver (`peach open
10573
10742
  // --driver playwright`, the factory/screenshot capture path), which tags them
10574
10743
  // 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
10744
  isAutomationSim(sim) {
10578
10745
  return sim.meta?.sootsimHostDriver === "playwright";
10579
10746
  }
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
10747
  reapIdleSims(now = Date.now()) {
10595
10748
  const attachedSimIds = new Set(this.cliSimBySocket.values());
10596
- const automationLive = [];
10597
10749
  for (const sim of this.sims.values()) {
10598
- if (sim.id === this.primarySimId) continue;
10599
10750
  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
10751
  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);
10752
+ const lease = this.getActiveLease(sim);
10753
+ if (automation ? lease?.kind === "user-active" : sim.id === this.primarySimId || lease) {
10608
10754
  continue;
10609
10755
  }
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
- }
10756
+ const idleMs = now - Math.max(sim.lastActiveAt, sim.connectedAt);
10757
+ const ttl = automation ? AUTOMATION_SIM_IDLE_REAP_TTL_MS : _SootSimBridgeHost.SIM_IDLE_REAP_TTL_MS;
10758
+ if (idleMs >= ttl) this.closeSimSocketFromHost(sim.ws);
10622
10759
  }
10623
10760
  }
10624
10761
  // ping every connected ws; if the previous round's ping went unanswered,
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (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.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (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.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (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.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (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.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (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.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (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;
@@ -973,6 +973,7 @@ function resolveSootsimBridgePort(input = {}) {
973
973
  if (explicitPort !== null) return explicitPort;
974
974
  return DEFAULT_SOOTSIM_BRIDGE_PORT + parsePortOffset(input.portOffset);
975
975
  }
976
+ var AUTOMATION_SIM_IDLE_REAP_TTL_MS = 5 * 6e4;
976
977
 
977
978
  // src/cli-constants.ts
978
979
  var DEFAULT_SOOTSIM_SHELL_URL = "http://localhost:5173/";
@@ -3155,13 +3156,18 @@ async function connect(options = {}) {
3155
3156
 
3156
3157
  // src/capture-contract.ts
3157
3158
  var RNX_SCREEN_CAPTURE_VERSION = 1;
3159
+ function isLiveRasterSource(node) {
3160
+ const source = node.props?.source;
3161
+ return node.type === "image" && !!source && typeof source === "object" && "uri" in source && typeof source.uri === "string" && source.uri.startsWith("live:");
3162
+ }
3158
3163
  function selectRnxRasterNodeIds(tree) {
3159
3164
  const nodesById = new Map(tree.nodes.map((node) => [node.nodeId, node]));
3160
3165
  const unsupported = /* @__PURE__ */ new Set();
3161
3166
  for (const node of tree.nodes) {
3162
- const visibleFrame = node.visibleFrame ?? node.frame;
3167
+ const liveVideo = isLiveRasterSource(node);
3168
+ const visibleFrame = liveVideo && node.visibleFrame && (node.visibleFrame.width <= 0 || node.visibleFrame.height <= 0) ? node.frame : node.visibleFrame ?? node.frame;
3163
3169
  if (!visibleFrame || visibleFrame.width <= 0 || visibleFrame.height <= 0) continue;
3164
- if (node.type === "masked-view" || node.type === "text" && typeof node.text === "string" && (new RegExp("\\p{Emoji_Presentation}", "u").test(node.text) || new RegExp("\\uFE0F|\\u200D|\\u20E3|\\p{Emoji_Modifier}|\\p{Regional_Indicator}", "u").test(
3170
+ if (node.type === "masked-view" || liveVideo || node.type === "text" && typeof node.text === "string" && (new RegExp("\\p{Emoji_Presentation}", "u").test(node.text) || new RegExp("\\uFE0F|\\u200D|\\u20E3|\\p{Emoji_Modifier}|\\p{Regional_Indicator}", "u").test(
3165
3171
  node.text
3166
3172
  )) || node.style._liquidGlass !== void 0 || node.style._liquidGlassEdge !== void 0 || node.style._liquidGlassMaterialTint !== void 0) {
3167
3173
  unsupported.add(node.nodeId);
@@ -3179,17 +3185,21 @@ function selectRnxRasterNodeIds(tree) {
3179
3185
  }
3180
3186
  const screenArea = tree.width * tree.height;
3181
3187
  const maxLayerArea = screenArea * 0.25;
3182
- const maxAtlasArea = screenArea * 0.5;
3183
3188
  const candidates = [...unsupported].map((nodeId) => {
3184
3189
  const node = nodesById.get(nodeId);
3185
- const frame = node?.visibleFrame ?? node?.frame;
3186
- return frame ? { nodeId, area: frame.width * frame.height } : null;
3190
+ if (!node) return null;
3191
+ const liveVideo = isLiveRasterSource(node);
3192
+ const frame = liveVideo && node.visibleFrame && (node.visibleFrame.width <= 0 || node.visibleFrame.height <= 0) ? node.frame : node.visibleFrame ?? node.frame;
3193
+ return frame ? { nodeId, area: frame.width * frame.height, liveVideo } : null;
3187
3194
  }).filter(
3188
- (candidate) => candidate !== null && candidate.area <= maxLayerArea
3189
- ).sort((left, right) => left.area - right.area || left.nodeId - right.nodeId);
3195
+ (candidate) => candidate !== null && (candidate.liveVideo || candidate.area <= maxLayerArea)
3196
+ ).sort(
3197
+ (left, right) => Number(right.liveVideo) - Number(left.liveVideo) || left.area - right.area || left.nodeId - right.nodeId
3198
+ );
3190
3199
  const selected = /* @__PURE__ */ new Set();
3191
3200
  let atlasArea = 0;
3192
3201
  for (const candidate of candidates) {
3202
+ const maxAtlasArea = screenArea * (candidate.liveVideo ? 1 : 0.5);
3193
3203
  if (atlasArea + candidate.area > maxAtlasArea) continue;
3194
3204
  selected.add(candidate.nodeId);
3195
3205
  atlasArea += candidate.area;
@@ -3270,6 +3280,7 @@ function rnxCaptureRasterRegions(capture) {
3270
3280
  nodeId: node.nodeId,
3271
3281
  parentNodeId: node.parentNodeId,
3272
3282
  type: node.type,
3283
+ props: node.props,
3273
3284
  text: node.text,
3274
3285
  style: node.style.resolved,
3275
3286
  layout: node.layout.local,
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (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.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (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;
package/dist-lib/menu.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (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;
package/dist-lib/menu.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/public-brand.ts
4
4
  var name = "Peach";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (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.625 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.627 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/is-record.ts
4
4
  function isRecord(value) {