peachsim 0.1.626 → 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.626 | (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;
@@ -6221,10 +6221,10 @@ var init_registry = __esm({
6221
6221
  versions: [
6222
6222
  {
6223
6223
  range: ">=15.0.0",
6224
- coverage: 0.9,
6225
- note: "both APIs implemented over one shared synchronous localStorage VFS: the SDK-18+ class-based File/Directory/Paths API (the bare `expo-file-system` export on SDK 18+) and the legacy async API (`expo-file-system/legacy`), with real network ops via fetch + navigator.storage. legacy and new interoperate on the same filesystem. paired native and Peach runtime conformance verifies the published DownloadResumable class identity, savable state, and cancel-before-start behavior; the score stays at 90% because active-transfer control remains unsupported.",
6226
- working: "SDK-18+ class API: Paths (document/cache/bundle, join/basename/extname/dirname), File (exists, size, md5, name, extension, parentDirectory, create, write text|bytes, text(Sync), base64(Sync), bytes(Sync), arrayBuffer, slice, stream, delete, info, copy, move/rename, open FileHandle, static downloadFileAsync/pickFileAsync), Directory (exists, create, createFile, createDirectory, list, info, delete, copy, move, static pickDirectoryAsync). Legacy API: documentDirectory, cacheDirectory, bundleDirectory, EncodingType, FileSystemSessionType, FileSystemUploadType, FileSystemAcceptedUploadHttpMethod, readAsStringAsync, writeAsStringAsync, deleteAsync, getInfoAsync, makeDirectoryAsync, readDirectoryAsync, copyAsync, moveAsync, downloadAsync, createDownloadResumable (published DownloadResumable class with fileUri/savable state and cancel-before-start), uploadAsync (real fetch with binary or multipart body), createUploadTask, UploadTask class (uploadAsync, cancelAsync, fires completion callback), getFreeDiskStorageAsync (navigator.storage.estimate quota - usage), getTotalDiskCapacityAsync (navigator.storage.estimate quota), getContentUriAsync (returns uri unchanged \u2014 Android-only behavior), StorageAccessFramework namespace stub (Android-only methods throw with platform error). All upstream legacy types exported: FileSystemUploadOptions, FileSystemUploadResult, UploadProgressData, DownloadProgressData, FileSystemNetworkTaskProgressCallback.",
6227
- missing: "in-flight DownloadResumable pause, resume, cancellation, and streamed download progress; chunked upload progress events (fetch does not expose upload progress)"
6224
+ coverage: 0.98,
6225
+ note: "upstream package JS runs against the ExpoFileSystem and ExponentFileSystem native-module seams over one shared VFS. both modules follow the installed Swift sources: member shapes, sync and async twins, error codes and messages including the legacy module's own codes, and events. transfers use fetch and the browser XMLHttpRequest, so download progress, pause and resume, cancellation, and upload progress are real. paired native and Peach runtime conformance covers the class API, write and copy semantics, file handles, digests, directory watching, and the legacy API.",
6226
+ working: "class API over the native FileSystemFile, FileSystemDirectory, and FileSystemFileHandle: write and writeSync (text, bytes, base64, append), text, base64, bytes and their Sync twins, info, md5 and digest (MD5, SHA-1, SHA-256, SHA-384, SHA-512), type from the extension UTType, lastModified, create with its native options, copy, move, and their Sync twins with native overwrite and existence errors, rename, delete, file handles in every FileMode with readBytes, writeBytes, offset, and size, Directory create, createFile, createDirectory, list, listAsRecords, size, copy, move; Paths (document, cache, bundle, disk space); downloadFileAsync with uuid progress events and cancelDownloadAsync; FileSystemDownloadTask and FileSystemUploadTask with start, pause, resume, cancel, and progress events; FileSystemWatcher change events for modify, delete, and rename; pickFileAsync through the document picker into the tmp Inbox. legacy API through ExponentFileSystem: read with position and length, write with append, delete, info, makeDirectory, readDirectory, recursive copy and move, downloadAsync, createDownloadResumable with pause, resume, savable resume data, and cancellation, uploadAsync and createUploadTask (binary and multipart) with progress events, disk capacity reads.",
6227
+ missing: "pickDirectoryAsync always reports a cancelled pick (native opens the chosen folder in place); background URLSession transfers do not continue while the app is suspended; Quick Look canPreview and preview are absent; legacy copyAsync cannot read ph:// photo library assets; Directory.size omits the directory entries' own sizes"
6228
6228
  }
6229
6229
  ]
6230
6230
  },
@@ -6262,9 +6262,9 @@ var init_registry = __esm({
6262
6262
  {
6263
6263
  range: ">=12.0.0",
6264
6264
  coverage: 0.8,
6265
- note: "the published package runs unchanged; ordinary browser opens hand off to the host, and host-owned auth sessions deliver successful callback URLs, explicit user cancellation, programmatic dismissal, and replacement. the 80% implementation estimate follows the mounted published-package test under cross-origin isolation and ordinary popup blocking; native browser presentation and unrestricted redirect schemes remain unsupported.",
6266
- working: "openBrowserAsync host handoff, dismissBrowser result, warmUpAsync, coolDownAsync, mayInitWithUrlAsync, published non-Android getCustomTabsSupportingBrowsersAsync shape, openAuthSessionAsync success with callback URL, explicit host Cancel Sign-In, dismissAuthSession and replacement dismissal, identity-matched callback and control cleanup, preservation of guest alerts during sign-in, and upstream enums and option processing",
6267
- missing: "embedded SFSafariViewController or Chrome Custom Tab, native styling and cookie isolation, closing an ordinary handed-off host tab from dismissBrowser, custom-scheme or foreign-origin auth callbacks, and observing abandonment after browser isolation severs the popup proxy. auth requires a same-origin HTTP(S) callback and user activation allowed by popup policy; an abandoned isolated provider requires the visible host cancel control"
6265
+ note: "the published package runs unchanged; host-owned browser sessions settle on user cancellation or programmatic dismissal, and auth sessions deliver successful callback URLs, explicit user cancellation, programmatic dismissal, and replacement. the 80% implementation estimate follows the mounted published-package test under cross-origin isolation and popup blocking; native browser presentation and unrestricted redirect schemes remain unsupported.",
6266
+ working: "openBrowserAsync pending result, user cancel and programmatic dismiss, locked result for a second browser, warmUpAsync, coolDownAsync, mayInitWithUrlAsync, published iOS getCustomTabsSupportingBrowsersAsync UnavailabilityError, openAuthSessionAsync success with callback URL, explicit host Cancel Sign-In, dismissAuthSession and replacement dismissal, identity-matched callback and control cleanup, preservation of guest alerts during sign-in, and upstream enums and option processing",
6267
+ missing: "embedded SFSafariViewController or Chrome Custom Tab, native styling and cookie isolation, custom-scheme or foreign-origin auth callbacks, and observing abandonment after browser isolation severs the popup proxy. auth requires a same-origin HTTP(S) callback and user activation allowed by popup policy; an abandoned isolated provider requires the visible host close control"
6268
6268
  }
6269
6269
  ]
6270
6270
  },
@@ -7977,7 +7977,7 @@ var init_registry = __esm({
7977
7977
  {
7978
7978
  range: ">=14.0.0",
7979
7979
  coverage: 0.8,
7980
- note: "printAsync routes through real window.print() (browser print dialog). printToFileAsync now produces a REAL, structurally-valid PDF (verified by `file`/macOS as com.adobe.pdf) carrying the html's text content + a real page count, written to the same in-memory cache the expo-file-system stub serves \u2014 so the returned `file:///sootsim-cache/Print/*.pdf` URI resolves to genuine PDF bytes (readAsStringAsync / FormData upload / Sharing / Print preview all work), and base64 (when requested) decodes to those bytes. This replaces the old `mock://print.pdf` dangling URI that broke every consumer. Fidelity caveat: the worker has no WebView render engine, so the PDF reproduces the document's TEXT (paginated, escaped) but NOT CSS/image layout \u2014 a degraded-but-real simulation of iOS's WKWebView\u2192UIPrintPageRenderer output.",
7980
+ note: "printAsync routes through real window.print() (browser print dialog). printToFileAsync now produces a REAL, structurally-valid PDF (verified by `file`/macOS as com.adobe.pdf) carrying the html's text content + a real page count, written to the same in-memory cache the expo-file-system stub serves \u2014 so the returned `<cacheDirectory>Print/*.pdf` URI resolves to genuine PDF bytes (readAsStringAsync / FormData upload / Sharing / Print preview all work), and base64 (when requested) decodes to those bytes. This replaces the old `mock://print.pdf` dangling URI that broke every consumer. Fidelity caveat: the worker has no WebView render engine, so the PDF reproduces the document's TEXT (paginated, escaped) but NOT CSS/image layout \u2014 a degraded-but-real simulation of iOS's WKWebView\u2192UIPrintPageRenderer output.",
7981
7981
  working: "upstream validation and concurrency guard, printAsync (real window.print), printToFileAsync (real PDF bytes: %PDF header, catalog/pages tree, xref, paginated text content, real numberOfPages, real file:// uri + base64), selectPrinterAsync, Orientation enum",
7982
7982
  missing: "CSS/image/font layout fidelity in the generated PDF (text-only render \u2014 needs a worker WebView engine to match WKWebView pixel output), useMarkupFormatter (same text path), printerUrl/AirPrint physical printing, native printer selection sheet (selectPrinterAsync returns undefined)"
7983
7983
  }
@@ -18767,6 +18767,7 @@ var init_sleep = __esm({
18767
18767
  // src/bridge-constants.ts
18768
18768
  var bridge_constants_exports = {};
18769
18769
  __export(bridge_constants_exports, {
18770
+ AUTOMATION_SIM_IDLE_REAP_TTL_MS: () => AUTOMATION_SIM_IDLE_REAP_TTL_MS,
18770
18771
  DEFAULT_SOOTSIM_BRIDGE_PORT: () => DEFAULT_SOOTSIM_BRIDGE_PORT,
18771
18772
  DEFAULT_SOOTSIM_BRIDGE_URL: () => DEFAULT_SOOTSIM_BRIDGE_URL,
18772
18773
  SOOTSIM_BRIDGE_SIM_CLOSE_CODE: () => SOOTSIM_BRIDGE_SIM_CLOSE_CODE,
@@ -18877,7 +18878,7 @@ function resolveSootsimBridgeWebSocketUrl(input) {
18877
18878
  function isSootsimBridgeHealth(value, expectedPort) {
18878
18879
  return typeof value === "object" && value !== null && Reflect.get(value, "ok") === true && Reflect.get(value, "bridgePort") === expectedPort;
18879
18880
  }
18880
- var DEFAULT_SOOTSIM_BRIDGE_PORT, DEFAULT_SOOTSIM_BRIDGE_URL, CONTRAST_DEV_HTTP_BASE_PORT, SOOTSIM_SITE_DEV_HTTP_BASE_PORT, SOOTSIM_SHELL_DEV_HTTP_BASE_PORT, LOCAL_DEV_HTTP_BASE_PORTS, MAX_LOCAL_DEV_PORT_OFFSET, SOOTSIM_BRIDGE_SIM_CLOSE_CODE, SOOTSIM_BRIDGE_SIM_CLOSE_REASON;
18881
+ var DEFAULT_SOOTSIM_BRIDGE_PORT, DEFAULT_SOOTSIM_BRIDGE_URL, CONTRAST_DEV_HTTP_BASE_PORT, SOOTSIM_SITE_DEV_HTTP_BASE_PORT, SOOTSIM_SHELL_DEV_HTTP_BASE_PORT, LOCAL_DEV_HTTP_BASE_PORTS, MAX_LOCAL_DEV_PORT_OFFSET, SOOTSIM_BRIDGE_SIM_CLOSE_CODE, SOOTSIM_BRIDGE_SIM_CLOSE_REASON, AUTOMATION_SIM_IDLE_REAP_TTL_MS;
18881
18882
  var init_bridge_constants = __esm({
18882
18883
  "src/bridge-constants.ts"() {
18883
18884
  "use strict";
@@ -18893,6 +18894,170 @@ var init_bridge_constants = __esm({
18893
18894
  MAX_LOCAL_DEV_PORT_OFFSET = 2e3;
18894
18895
  SOOTSIM_BRIDGE_SIM_CLOSE_CODE = 4001;
18895
18896
  SOOTSIM_BRIDGE_SIM_CLOSE_REASON = "sootsim close";
18897
+ AUTOMATION_SIM_IDLE_REAP_TTL_MS = 5 * 6e4;
18898
+ }
18899
+ });
18900
+
18901
+ // ../contrast/src/sleep.ts
18902
+ function sleep2(ms) {
18903
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
18904
+ }
18905
+ var init_sleep2 = __esm({
18906
+ "../contrast/src/sleep.ts"() {
18907
+ "use strict";
18908
+ }
18909
+ });
18910
+
18911
+ // ../contrast/src/runtime/process-group.ts
18912
+ function signalProcessGroup(child, signal) {
18913
+ if (!child.pid) return;
18914
+ try {
18915
+ process.kill(-child.pid, signal);
18916
+ } catch {
18917
+ try {
18918
+ process.kill(child.pid, signal);
18919
+ } catch {
18920
+ }
18921
+ }
18922
+ }
18923
+ function runningPids(pids) {
18924
+ return pids.filter((pid) => {
18925
+ try {
18926
+ process.kill(pid, 0);
18927
+ return true;
18928
+ } catch (error) {
18929
+ return error instanceof Error && "code" in error && error.code === "EPERM";
18930
+ }
18931
+ });
18932
+ }
18933
+ function groupIsEmpty(pgid) {
18934
+ try {
18935
+ process.kill(-pgid, 0);
18936
+ } catch (error) {
18937
+ if (!(error instanceof Error && "code" in error && error.code === "EPERM"))
18938
+ return true;
18939
+ }
18940
+ const result = (0, import_node_child_process2.spawnSync)("ps", ["-A", "-o", "pgid=,stat="], {
18941
+ encoding: "utf8",
18942
+ stdio: ["ignore", "pipe", "pipe"]
18943
+ });
18944
+ if (result.error || result.status !== 0) return false;
18945
+ return !result.stdout.split("\n").some((line) => {
18946
+ const [group, state] = line.trim().split(/\s+/);
18947
+ return Number(group) === pgid && state !== void 0 && !state.startsWith("Z");
18948
+ });
18949
+ }
18950
+ async function waitForExit(pids, pgid, timeoutMs) {
18951
+ const deadline = Date.now() + timeoutMs;
18952
+ const gone = () => runningPids(pids).length === 0 && groupIsEmpty(pgid);
18953
+ while (Date.now() < deadline) {
18954
+ if (gone()) return true;
18955
+ await sleep2(50);
18956
+ }
18957
+ return gone();
18958
+ }
18959
+ async function terminateProcessGroup(child, signal = "SIGTERM", gracefulTimeoutMs = GRACEFUL_TIMEOUT_MS) {
18960
+ if (!child.pid) {
18961
+ try {
18962
+ child.kill(signal);
18963
+ } catch {
18964
+ }
18965
+ return;
18966
+ }
18967
+ signalProcessGroup(child, "SIGSTOP");
18968
+ const descendants = /* @__PURE__ */ new Set();
18969
+ let parents = [child.pid];
18970
+ let inspectionError = null;
18971
+ while (parents.length > 0) {
18972
+ const result = (0, import_node_child_process2.spawnSync)("pgrep", ["-P", parents.join(",")], {
18973
+ encoding: "utf8",
18974
+ stdio: ["ignore", "pipe", "pipe"]
18975
+ });
18976
+ if (result.error || result.status !== 0 && result.status !== 1) {
18977
+ inspectionError = new Error(
18978
+ `could not inspect descendants of processes ${parents.join(",")}: ${result.error?.message || result.stderr.trim() || `pgrep exited ${result.status}`}`
18979
+ );
18980
+ break;
18981
+ }
18982
+ const next = [];
18983
+ for (const line of result.stdout.split("\n")) {
18984
+ const descendantPid = Number(line.trim());
18985
+ if (Number.isInteger(descendantPid) && descendantPid > 1 && !descendants.has(descendantPid)) {
18986
+ descendants.add(descendantPid);
18987
+ next.push(descendantPid);
18988
+ try {
18989
+ process.kill(descendantPid, "SIGSTOP");
18990
+ } catch {
18991
+ }
18992
+ }
18993
+ }
18994
+ parents = next;
18995
+ }
18996
+ const pids = [child.pid, ...descendants];
18997
+ const gracefulDeadline = Date.now() + gracefulTimeoutMs;
18998
+ try {
18999
+ process.kill(child.pid, signal);
19000
+ } catch {
19001
+ }
19002
+ signalProcessGroup(child, "SIGCONT");
19003
+ for (const pid of descendants) {
19004
+ try {
19005
+ process.kill(pid, "SIGCONT");
19006
+ } catch {
19007
+ }
19008
+ }
19009
+ let rootExited = child.exitCode !== null || child.signalCode !== null;
19010
+ if (!rootExited) {
19011
+ rootExited = await new Promise((resolveExit) => {
19012
+ const onExit = () => {
19013
+ clearTimeout(deadlineTimer);
19014
+ resolveExit(true);
19015
+ };
19016
+ const deadlineTimer = setTimeout(
19017
+ () => {
19018
+ child.removeListener("exit", onExit);
19019
+ resolveExit(false);
19020
+ },
19021
+ Math.max(0, gracefulDeadline - Date.now())
19022
+ );
19023
+ child.once("exit", onExit);
19024
+ if (child.exitCode !== null || child.signalCode !== null) {
19025
+ child.removeListener("exit", onExit);
19026
+ onExit();
19027
+ }
19028
+ });
19029
+ }
19030
+ if (rootExited) {
19031
+ signalProcessGroup(child, signal);
19032
+ for (const pid of descendants) {
19033
+ try {
19034
+ process.kill(pid, signal);
19035
+ } catch {
19036
+ }
19037
+ }
19038
+ }
19039
+ if (await waitForExit(pids, child.pid, Math.max(0, gracefulDeadline - Date.now()))) {
19040
+ if (inspectionError) throw inspectionError;
19041
+ return;
19042
+ }
19043
+ for (const pid of pids) {
19044
+ try {
19045
+ process.kill(pid, "SIGKILL");
19046
+ } catch {
19047
+ }
19048
+ }
19049
+ signalProcessGroup(child, "SIGKILL");
19050
+ await waitForExit(pids, child.pid, FORCED_EXIT_TIMEOUT_MS);
19051
+ if (inspectionError) throw inspectionError;
19052
+ }
19053
+ var import_node_child_process2, GRACEFUL_TIMEOUT_MS, FORCED_EXIT_TIMEOUT_MS;
19054
+ var init_process_group = __esm({
19055
+ "../contrast/src/runtime/process-group.ts"() {
19056
+ "use strict";
19057
+ import_node_child_process2 = require("node:child_process");
19058
+ init_sleep2();
19059
+ GRACEFUL_TIMEOUT_MS = 3e3;
19060
+ FORCED_EXIT_TIMEOUT_MS = 1e3;
18896
19061
  }
18897
19062
  });
18898
19063
 
@@ -19456,28 +19621,51 @@ var init_optional_demo_registry = __esm({
19456
19621
  });
19457
19622
 
19458
19623
  // scripts/dev-server-scanner.ts
19459
- async function execGuarded(command, timeout, extra = {}) {
19460
- let child;
19461
- const ran = new Promise((resolve9, reject) => {
19462
- child = (0, import_child_process.exec)(command, { encoding: "utf8", timeout, ...extra }, (err, stdout) => {
19624
+ function execGuarded(command, timeout, extra = {}) {
19625
+ const maxBuffer = extra.maxBuffer ?? 1024 * 1024;
19626
+ return new Promise((resolve9, reject) => {
19627
+ const child = (0, import_child_process.spawn)("/bin/sh", ["-c", command], {
19628
+ detached: true,
19629
+ stdio: ["ignore", "pipe", "ignore"]
19630
+ });
19631
+ let stdout = "";
19632
+ let settled = false;
19633
+ let killing = false;
19634
+ const settle = (err) => {
19635
+ if (settled) return;
19636
+ settled = true;
19637
+ clearTimeout(deadline);
19463
19638
  if (err) reject(err);
19464
19639
  else resolve9({ stdout });
19640
+ };
19641
+ const killThenSettle = (err) => {
19642
+ if (killing) return;
19643
+ killing = true;
19644
+ clearTimeout(deadline);
19645
+ terminateProcessGroup(child, "SIGKILL", 0).finally(() => settle(err));
19646
+ };
19647
+ const deadline = setTimeout(() => {
19648
+ killThenSettle(
19649
+ Object.assign(new Error(`\`${command}\` did not exit within ${timeout}ms`), {
19650
+ killed: true
19651
+ })
19652
+ );
19653
+ }, timeout);
19654
+ child.stdout.setEncoding("utf8");
19655
+ child.stdout.on("data", (chunk) => {
19656
+ stdout += chunk;
19657
+ if (stdout.length > maxBuffer) {
19658
+ killThenSettle(new Error(`\`${command}\` exceeded maxBuffer ${maxBuffer}`));
19659
+ }
19660
+ });
19661
+ child.on("error", (err) => {
19662
+ if (!killing) settle(err);
19663
+ });
19664
+ child.on("close", (code) => {
19665
+ if (killing) return;
19666
+ settle(code === 0 ? null : new Error(`\`${command}\` exited with ${code}`));
19465
19667
  });
19466
19668
  });
19467
- let deadlineTimer;
19468
- const deadline = new Promise((_resolve, reject) => {
19469
- deadlineTimer = setTimeout(() => {
19470
- child?.kill("SIGKILL");
19471
- const err = new Error(`\`${command}\` did not exit within ${timeout}ms`);
19472
- Object.assign(err, { killed: true });
19473
- reject(err);
19474
- }, timeout + EXEC_EXIT_GRACE_MS);
19475
- });
19476
- try {
19477
- return await Promise.race([ran, deadline]);
19478
- } finally {
19479
- clearTimeout(deadlineTimer);
19480
- }
19481
19669
  }
19482
19670
  async function tcpPing(port, timeout = TCP_GATE_MS) {
19483
19671
  try {
@@ -19508,6 +19696,7 @@ async function tcpPing(port, timeout = TCP_GATE_MS) {
19508
19696
  function httpGet(hostname, port, path21, method = "GET", timeout = TIMEOUT_MS, headers = {}) {
19509
19697
  return new Promise((resolve9) => {
19510
19698
  let settled = false;
19699
+ let killing = false;
19511
19700
  const settle = (value) => {
19512
19701
  if (settled) return;
19513
19702
  settled = true;
@@ -19562,6 +19751,20 @@ function wasKilledByTimeout(err) {
19562
19751
  }
19563
19752
  async function scanListeningProcesses(excludePorts = []) {
19564
19753
  const excluded = new Set(excludePorts);
19754
+ const listing = await listListenersShared();
19755
+ if (listing.state === "could-not-look") return listing;
19756
+ return {
19757
+ state: "looked",
19758
+ processes: listing.processes.filter((p) => acceptPort(p.port, excluded))
19759
+ };
19760
+ }
19761
+ function listListenersShared() {
19762
+ inflightListing ??= listListeners().finally(() => {
19763
+ inflightListing = null;
19764
+ });
19765
+ return inflightListing;
19766
+ }
19767
+ async function listListeners() {
19565
19768
  let timedOut = false;
19566
19769
  try {
19567
19770
  const { stdout } = await execGuarded(
@@ -19573,11 +19776,9 @@ async function scanListeningProcesses(excludePorts = []) {
19573
19776
  const parts = line.trim().split(/\s+/);
19574
19777
  if (parts.length < 9) continue;
19575
19778
  const pid = Number(parts[1]);
19576
- const addr = parts[8];
19577
- const m = addr.match(/:(\d+)$/);
19779
+ const m = parts[8].match(/:(\d+)$/);
19578
19780
  if (!m) continue;
19579
19781
  const port = Number(m[1]);
19580
- if (!acceptPort(port, excluded)) continue;
19581
19782
  if (!seen.has(port)) seen.set(port, pid);
19582
19783
  }
19583
19784
  return {
@@ -19599,7 +19800,6 @@ async function scanListeningProcesses(excludePorts = []) {
19599
19800
  if (!portMatch) continue;
19600
19801
  const port = Number(portMatch[1]);
19601
19802
  const pid = pidMatch ? Number(pidMatch[1]) : 0;
19602
- if (!acceptPort(port, excluded)) continue;
19603
19803
  if (!seen.has(port)) seen.set(port, pid);
19604
19804
  }
19605
19805
  return {
@@ -19610,10 +19810,7 @@ async function scanListeningProcesses(excludePorts = []) {
19610
19810
  if (wasKilledByTimeout(err)) timedOut = true;
19611
19811
  }
19612
19812
  if (timedOut) return { state: "could-not-look" };
19613
- return {
19614
- state: "looked",
19615
- processes: FALLBACK_PORTS.filter((p) => acceptPort(p.port, excluded))
19616
- };
19813
+ return { state: "looked", processes: FALLBACK_PORTS };
19617
19814
  }
19618
19815
  function pidFromNetstat(stdout, port) {
19619
19816
  for (const line of stdout.split("\n")) {
@@ -20097,11 +20294,12 @@ async function scanDevServers(opts = {}) {
20097
20294
  }
20098
20295
  return results.filter((r) => !isSootSimSelfServer(r));
20099
20296
  }
20100
- var import_child_process, import_promises2, import_http, import_net, import_node_fs2, import_node_path3, EXEC_EXIT_GRACE_MS, TIMEOUT_MS, MANIFEST_TIMEOUT_MS, TCP_GATE_MS, BODY_TIMEOUT_MS, FALLBACK_PORTS, SELF_PORT_RANGE_START, SELF_PORT_RANGE_END, DevServerScanUnavailableError, LISTING_TIMEOUT_MS, cwdByPid, localIconPathCache, localIconProbeInFlight, ENRICH_TIMEOUT_MS, enrichmentCache, enrichmentInFlight, knownNonPatched, knownNonExpo, knownOne, portCache, portCacheInvalidatedAt, NEGATIVE_CACHE_TTL_MS, WEAK_RESULT_CACHE_TTL_MS;
20297
+ var import_child_process, import_promises2, import_http, import_net, import_node_fs2, import_node_path3, TIMEOUT_MS, MANIFEST_TIMEOUT_MS, TCP_GATE_MS, BODY_TIMEOUT_MS, FALLBACK_PORTS, SELF_PORT_RANGE_START, SELF_PORT_RANGE_END, DevServerScanUnavailableError, LISTING_TIMEOUT_MS, inflightListing, cwdByPid, localIconPathCache, localIconProbeInFlight, ENRICH_TIMEOUT_MS, enrichmentCache, enrichmentInFlight, knownNonPatched, knownNonExpo, knownOne, portCache, portCacheInvalidatedAt, NEGATIVE_CACHE_TTL_MS, WEAK_RESULT_CACHE_TTL_MS;
20101
20298
  var init_dev_server_scanner = __esm({
20102
20299
  "scripts/dev-server-scanner.ts"() {
20103
20300
  "use strict";
20104
20301
  import_child_process = require("child_process");
20302
+ init_process_group();
20105
20303
  import_promises2 = require("fs/promises");
20106
20304
  import_http = __toESM(require("http"), 1);
20107
20305
  import_net = __toESM(require("net"), 1);
@@ -20112,7 +20310,6 @@ var init_dev_server_scanner = __esm({
20112
20310
  init_native_dev_bundle_url();
20113
20311
  init_settings_bundle();
20114
20312
  init_optional_demo_registry();
20115
- EXEC_EXIT_GRACE_MS = 2e3;
20116
20313
  TIMEOUT_MS = 250;
20117
20314
  MANIFEST_TIMEOUT_MS = 1500;
20118
20315
  TCP_GATE_MS = 120;
@@ -20137,6 +20334,7 @@ var init_dev_server_scanner = __esm({
20137
20334
  }
20138
20335
  };
20139
20336
  LISTING_TIMEOUT_MS = 1e4;
20337
+ inflightListing = null;
20140
20338
  cwdByPid = /* @__PURE__ */ new Map();
20141
20339
  localIconPathCache = /* @__PURE__ */ new Map();
20142
20340
  localIconProbeInFlight = /* @__PURE__ */ new Set();
@@ -21157,7 +21355,7 @@ function sha256File(filePath) {
21157
21355
  }
21158
21356
  function extractTarball(tarPath, destDir) {
21159
21357
  return new Promise((resolve9, reject) => {
21160
- const child = (0, import_node_child_process2.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
21358
+ const child = (0, import_node_child_process3.spawn)("tar", ["-xzf", tarPath, "-C", destDir], {
21161
21359
  stdio: ["ignore", "inherit", "inherit"]
21162
21360
  });
21163
21361
  child.on("error", reject);
@@ -21167,11 +21365,11 @@ function extractTarball(tarPath, destDir) {
21167
21365
  });
21168
21366
  });
21169
21367
  }
21170
- var import_node_child_process2, import_node_crypto2, import_node_fs6, import_node_os2, import_node_path6, import_node_stream, import_promises3, DEFAULT_LOCAL_RUNTIME_RETENTION_COUNT;
21368
+ var import_node_child_process3, import_node_crypto2, import_node_fs6, import_node_os2, import_node_path6, import_node_stream, import_promises3, DEFAULT_LOCAL_RUNTIME_RETENTION_COUNT;
21171
21369
  var init_src = __esm({
21172
21370
  "../contrast-runtime-delivery/src/index.ts"() {
21173
21371
  "use strict";
21174
- import_node_child_process2 = require("node:child_process");
21372
+ import_node_child_process3 = require("node:child_process");
21175
21373
  import_node_crypto2 = __toESM(require("node:crypto"), 1);
21176
21374
  import_node_fs6 = __toESM(require("node:fs"), 1);
21177
21375
  import_node_os2 = __toESM(require("node:os"), 1);
@@ -21879,7 +22077,7 @@ function mkfifoSync(p) {
21879
22077
  import_node_fs9.default.unlinkSync(p);
21880
22078
  }
21881
22079
  }
21882
- const result = (0, import_node_child_process3.spawnSync)("mkfifo", ["-m", "600", p]);
22080
+ const result = (0, import_node_child_process4.spawnSync)("mkfifo", ["-m", "600", p]);
21883
22081
  if (result.status !== 0) {
21884
22082
  throw new Error(
21885
22083
  `mkfifo(${p}) failed: ${result.stderr?.toString().trim() || "unknown error"}`
@@ -21947,7 +22145,7 @@ async function startSession(opts) {
21947
22145
  if (claudeSessionUuid) {
21948
22146
  wrapperArgs.push("--claude-session-uuid", claudeSessionUuid);
21949
22147
  }
21950
- const child = (0, import_node_child_process3.spawn)(executable, wrapperArgs, {
22148
+ const child = (0, import_node_child_process4.spawn)(executable, wrapperArgs, {
21951
22149
  detached: true,
21952
22150
  stdio: "ignore",
21953
22151
  env: {
@@ -22099,11 +22297,11 @@ async function waitForFirstEvent(fifo, predicate, timeoutMs) {
22099
22297
  import_node_fs9.default.closeSync(fd);
22100
22298
  }
22101
22299
  }
22102
- var import_node_child_process3, import_node_crypto4, import_node_fs9, import_node_path9, import_node_readline, AgentSessionError;
22300
+ var import_node_child_process4, import_node_crypto4, import_node_fs9, import_node_path9, import_node_readline, AgentSessionError;
22103
22301
  var init_agent_sessions = __esm({
22104
22302
  "src/agent-sessions.ts"() {
22105
22303
  "use strict";
22106
- import_node_child_process3 = require("node:child_process");
22304
+ import_node_child_process4 = require("node:child_process");
22107
22305
  import_node_crypto4 = require("node:crypto");
22108
22306
  import_node_fs9 = __toESM(require("node:fs"), 1);
22109
22307
  import_node_path9 = __toESM(require("node:path"), 1);
@@ -23271,11 +23469,11 @@ function looksBinary(filePath, size) {
23271
23469
  if (fd !== void 0) import_node_fs11.default.closeSync(fd);
23272
23470
  }
23273
23471
  }
23274
- var import_node_child_process4, import_node_fs11, import_node_path11, CheckoutRequiresGitError, BINARY_SNIFF_BYTES, LIST_FILES_TIMEOUT_MS, LIST_FILES_ATTEMPTS, LIST_FILES_RETRY_MS, CheckoutFilePlane;
23472
+ var import_node_child_process5, import_node_fs11, import_node_path11, CheckoutRequiresGitError, BINARY_SNIFF_BYTES, LIST_FILES_TIMEOUT_MS, LIST_FILES_ATTEMPTS, LIST_FILES_RETRY_MS, CheckoutFilePlane;
23275
23473
  var init_checkout_plane = __esm({
23276
23474
  "cli/commands/box/checkout-plane.ts"() {
23277
23475
  "use strict";
23278
- import_node_child_process4 = require("node:child_process");
23476
+ import_node_child_process5 = require("node:child_process");
23279
23477
  import_node_fs11 = __toESM(require("node:fs"), 1);
23280
23478
  import_node_path11 = __toESM(require("node:path"), 1);
23281
23479
  CheckoutRequiresGitError = class extends Error {
@@ -23303,7 +23501,7 @@ var init_checkout_plane = __esm({
23303
23501
  }
23304
23502
  listFiles() {
23305
23503
  const args = this.trackedOnly ? ["ls-files", "-z"] : ["ls-files", "-co", "--exclude-standard", "-z"];
23306
- const stdout = (0, import_node_child_process4.execFileSync)("git", args, {
23504
+ const stdout = (0, import_node_child_process5.execFileSync)("git", args, {
23307
23505
  cwd: this.root,
23308
23506
  encoding: "utf8",
23309
23507
  maxBuffer: 64 * 1024 * 1024,
@@ -24365,8 +24563,8 @@ var init_shared_session = __esm({
24365
24563
  function rethrowIfExit(error) {
24366
24564
  if (error instanceof PeachExit) throw error;
24367
24565
  }
24368
- function peachExit(code) {
24369
- throw new PeachExit(code);
24566
+ function peachExit(code, reason) {
24567
+ throw new PeachExit(code, reason);
24370
24568
  }
24371
24569
  var import_node_async_hooks, PeachExit, sinks, rnxExit;
24372
24570
  var init_run_rnx = __esm({
@@ -24375,10 +24573,12 @@ var init_run_rnx = __esm({
24375
24573
  import_node_async_hooks = require("node:async_hooks");
24376
24574
  PeachExit = class extends Error {
24377
24575
  code;
24378
- constructor(code) {
24576
+ reason;
24577
+ constructor(code, reason = null) {
24379
24578
  super(`peach exited with code ${code}`);
24380
24579
  this.name = "PeachExit";
24381
24580
  this.code = code;
24581
+ this.reason = reason;
24382
24582
  }
24383
24583
  };
24384
24584
  sinks = new import_node_async_hooks.AsyncLocalStorage();
@@ -27031,6 +27231,8 @@ var init_metro_asset_path = __esm({
27031
27231
  function matchesModuleEntry(modulePath, pattern) {
27032
27232
  if (!modulePath || !pattern) return false;
27033
27233
  if (modulePath === pattern) return true;
27234
+ if (!modulePath.includes(pattern)) return false;
27235
+ if (!modulePath.includes(`node_modules/${pattern}`)) return false;
27034
27236
  if (modulePath === `node_modules/${pattern}`) return true;
27035
27237
  if (modulePath === `node_modules/${pattern}.js`) return true;
27036
27238
  if (modulePath === `node_modules/${pattern}.ts`) return true;
@@ -28850,24 +29052,8 @@ var init_bridge_host = __esm({
28850
29052
  // terminal 4001 code, so the client closes the abandoned window instead
28851
29053
  // of reconnecting the zombie straight back in.
28852
29054
  static SIM_IDLE_REAP_TTL_MS = 30 * 6e4;
28853
- // automation sims (spawned by the playwright driver: `peach open --driver
28854
- // playwright`, the factory/screenshot capture path — `meta.sootsimHostDriver
28855
- // === 'playwright'`) are unattended one-shot browsers, never a human's tab. a
28856
- // capture run opens one per slide/app/retry and the 30-min human/dev TTL lets
28857
- // hundreds pile up inside the window — orphaned ms-playwright chromiums that
28858
- // drove the box to swap-thrash (load hit 157 in one incident). give them a far
28859
- // shorter idle TTL, and cap how many can sit concurrently: reap the most-idle
28860
- // overflow, but never a sim active within ACTIVE_GRACE (an agent composing
28861
- // between captures), nor primary / cli-attached / leased sims.
28862
- static AUTOMATION_SIM_IDLE_REAP_TTL_MS = 10 * 6e4;
28863
- static MAX_CONCURRENT_AUTOMATION_SIMS = 6;
28864
- static AUTOMATION_SIM_ACTIVE_GRACE_MS = 6e4;
28865
29055
  preferredPort;
28866
29056
  portFallbackCount;
28867
- simIdleReapTtlMs;
28868
- automationSimIdleReapTtlMs;
28869
- maxConcurrentAutomationSims;
28870
- automationSimActiveGraceMs;
28871
29057
  shouldWriteLockfile;
28872
29058
  shouldWriteDevLockfile;
28873
29059
  getShellPort;
@@ -28910,23 +29096,12 @@ var init_bridge_host = __esm({
28910
29096
  getExcludePorts: opts.agentScanExcludes
28911
29097
  });
28912
29098
  this.contrastOrigin = opts.contrastOrigin?.replace(/\/$/, "") || null;
28913
- this.simIdleReapTtlMs = opts.simIdleReapTtlMs ?? _SootSimBridgeHost.SIM_IDLE_REAP_TTL_MS;
28914
- this.automationSimIdleReapTtlMs = opts.automationSimIdleReapTtlMs ?? _SootSimBridgeHost.AUTOMATION_SIM_IDLE_REAP_TTL_MS;
28915
- this.maxConcurrentAutomationSims = opts.maxConcurrentAutomationSims ?? _SootSimBridgeHost.MAX_CONCURRENT_AUTOMATION_SIMS;
28916
- this.automationSimActiveGraceMs = opts.automationSimActiveGraceMs ?? _SootSimBridgeHost.AUTOMATION_SIM_ACTIVE_GRACE_MS;
28917
29099
  }
28918
29100
  /** expose the agent host so tests and embedders can inspect state or
28919
29101
  * inject behavior. not part of the public WS protocol. */
28920
29102
  getAgentHost() {
28921
29103
  return this.agentHost;
28922
29104
  }
28923
- /** run the abandoned-sim GC pass on demand. the reaper normally fires off
28924
- * the 30s idle-sweep timer; this lets the F21-3 integration test drive
28925
- * the real path without waiting for the timer. not part of the public
28926
- * WS protocol. */
28927
- reapIdleSimsForTest(now = Date.now()) {
28928
- this.reapIdleSims(now);
28929
- }
28930
29105
  /** synchronous wrapper around startAsync for callers that don't care
28931
29106
  * about port fallback outcomes. returns immediately; actual binding
28932
29107
  * happens on the event loop. callers that need to know the bound port
@@ -29172,6 +29347,10 @@ var init_bridge_host = __esm({
29172
29347
  this.updateUserActivity(sim);
29173
29348
  return;
29174
29349
  }
29350
+ if (msg.type === "bridge:capture-active" && sim) {
29351
+ sim.lastActiveAt = Date.now();
29352
+ return;
29353
+ }
29175
29354
  if (msg.type === "bridge:write-shared-config") {
29176
29355
  if (!msg.patch || typeof msg.patch !== "object" || Array.isArray(msg.patch)) {
29177
29356
  return;
@@ -30172,53 +30351,21 @@ var init_bridge_host = __esm({
30172
30351
  // automation sims are spawned by the playwright driver (`peach open
30173
30352
  // --driver playwright`, the factory/screenshot capture path), which tags them
30174
30353
  // with `meta.sootsimHostDriver === 'playwright'` via the host's addInitScript.
30175
- // they are unattended one-shot browsers, never a human's tab, so they get a
30176
- // shorter idle TTL and a concurrent cap.
30177
30354
  isAutomationSim(sim) {
30178
30355
  return sim.meta?.sootsimHostDriver === "playwright";
30179
30356
  }
30180
- // an automation sim whose spawning playwright host process is still alive is
30181
- // an ACTIVE run, not an abandoned tab. mirrors the cli's playwrightHostPidForSim
30182
- // (control.ts): extract the host pid from meta, then check liveness so the
30183
- // host-side idle GC never kills a live capture/factory sim mid-run.
30184
- automationSimOwnerAlive(sim) {
30185
- const pid = Number(sim.meta?.sootsimHostPid);
30186
- if (!Number.isInteger(pid) || pid <= 1 || pid === process.pid) return false;
30187
- try {
30188
- process.kill(pid, 0);
30189
- return true;
30190
- } catch (err) {
30191
- return err.code === "EPERM";
30192
- }
30193
- }
30194
30357
  reapIdleSims(now = Date.now()) {
30195
30358
  const attachedSimIds = new Set(this.cliSimBySocket.values());
30196
- const automationLive = [];
30197
30359
  for (const sim of this.sims.values()) {
30198
- if (sim.id === this.primarySimId) continue;
30199
30360
  if (attachedSimIds.has(sim.id)) continue;
30200
- if (this.getActiveLease(sim)) continue;
30201
- const lastTouched = Math.max(sim.lastActiveAt, sim.connectedAt);
30202
- const idleMs = now - lastTouched;
30203
30361
  const automation = this.isAutomationSim(sim);
30204
- if (automation && this.automationSimOwnerAlive(sim)) continue;
30205
- const ttl = automation ? this.automationSimIdleReapTtlMs : this.simIdleReapTtlMs;
30206
- if (idleMs >= ttl) {
30207
- this.closeSimSocketFromHost(sim.ws);
30362
+ const lease = this.getActiveLease(sim);
30363
+ if (automation ? lease?.kind === "user-active" : sim.id === this.primarySimId || lease) {
30208
30364
  continue;
30209
30365
  }
30210
- if (automation) automationLive.push({ sim, idleMs });
30211
- }
30212
- const cap = this.maxConcurrentAutomationSims;
30213
- if (automationLive.length > cap) {
30214
- automationLive.sort((a, b) => b.idleMs - a.idleMs);
30215
- for (const { sim, idleMs } of automationLive.slice(
30216
- 0,
30217
- automationLive.length - cap
30218
- )) {
30219
- if (idleMs < this.automationSimActiveGraceMs) continue;
30220
- this.closeSimSocketFromHost(sim.ws);
30221
- }
30366
+ const idleMs = now - Math.max(sim.lastActiveAt, sim.connectedAt);
30367
+ const ttl = automation ? AUTOMATION_SIM_IDLE_REAP_TTL_MS : _SootSimBridgeHost.SIM_IDLE_REAP_TTL_MS;
30368
+ if (idleMs >= ttl) this.closeSimSocketFromHost(sim.ws);
30222
30369
  }
30223
30370
  }
30224
30371
  // ping every connected ws; if the previous round's ping went unanswered,
@@ -33791,7 +33938,7 @@ function resolvePlaywright() {
33791
33938
  }
33792
33939
  function probeBrowser(resolved, env) {
33793
33940
  const self = rnxSelfInvocation();
33794
- const probe3 = (0, import_node_child_process5.spawnSync)(
33941
+ const probe3 = (0, import_node_child_process6.spawnSync)(
33795
33942
  self.executable,
33796
33943
  [...self.prefixArgs, RNX_INTERNAL_COMMAND, RNX_INTERNAL_CHILDREN.playwrightProbe],
33797
33944
  {
@@ -33834,7 +33981,7 @@ function ensurePlaywrightBrowser(resolved, env = process.env) {
33834
33981
  return { ok: true, executablePath: before.executablePath, installed: false };
33835
33982
  }
33836
33983
  const installer = rnxSelfInvocation();
33837
- const install = (0, import_node_child_process5.spawnSync)(
33984
+ const install = (0, import_node_child_process6.spawnSync)(
33838
33985
  installer.executable,
33839
33986
  [
33840
33987
  ...installer.prefixArgs,
@@ -33869,11 +34016,11 @@ ${detail}` : ""}`
33869
34016
  }
33870
34017
  return { ok: true, executablePath: after.executablePath, installed: true };
33871
34018
  }
33872
- var import_node_child_process5, import_node_fs20, import_node_module4, import_node_path22;
34019
+ var import_node_child_process6, import_node_fs20, import_node_module4, import_node_path22;
33873
34020
  var init_playwright_provisioning = __esm({
33874
34021
  "cli/drivers/playwright-provisioning.ts"() {
33875
34022
  "use strict";
33876
- import_node_child_process5 = require("node:child_process");
34023
+ import_node_child_process6 = require("node:child_process");
33877
34024
  import_node_fs20 = require("node:fs");
33878
34025
  import_node_module4 = require("node:module");
33879
34026
  import_node_path22 = require("node:path");
@@ -33990,7 +34137,8 @@ ${hint}` : ""}`
33990
34137
  };
33991
34138
  }
33992
34139
  const ownerComm = getProcessComm(ownerPid);
33993
- const ownerNote = ` \u2014 browser closes when pid ${ownerPid}${ownerComm ? ` (${ownerComm})` : ""} exits`;
34140
+ const idleMinutes = AUTOMATION_SIM_IDLE_REAP_TTL_MS / 6e4;
34141
+ const ownerNote = ` \u2014 browser closes after ${idleMinutes} min with no peach command, or when pid ${ownerPid}${ownerComm ? ` (${ownerComm})` : ""} exits`;
33994
34142
  return {
33995
34143
  launched: true,
33996
34144
  message: `playwright chrome launched \u2192 ${opts.url}${ownerNote}`,
@@ -34017,6 +34165,7 @@ var init_playwright = __esm({
34017
34165
  import_fs8 = require("fs");
34018
34166
  import_os2 = require("os");
34019
34167
  import_path7 = require("path");
34168
+ init_bridge_constants();
34020
34169
  init_playwright_host_log();
34021
34170
  init_profiles();
34022
34171
  init_internal_child();
@@ -34550,7 +34699,7 @@ async function ensureRuntimeForOpen() {
34550
34699
  }
34551
34700
  function spawnSootsim(args) {
34552
34701
  const { executable, prefixArgs } = rnxSelfInvocation();
34553
- return (0, import_node_child_process6.spawn)(executable, [...prefixArgs, ...args], {
34702
+ return (0, import_node_child_process7.spawn)(executable, [...prefixArgs, ...args], {
34554
34703
  detached: true,
34555
34704
  stdio: "ignore",
34556
34705
  env: process.env
@@ -34906,12 +35055,14 @@ async function buildShellUrl(target, baseUrl = resolveDefaultShellBaseUrl(), opt
34906
35055
  await stampAppSplash(await stampAppFonts(resolved, project), project),
34907
35056
  project
34908
35057
  );
34909
- if (!opts.device && !opts.proof && !opts.posture && !opts.osVersion) return stamped;
35058
+ if (!opts.device && !opts.proof && !opts.posture && !opts.osVersion && !opts.env?.length)
35059
+ return stamped;
34910
35060
  const url = new URL(stamped);
34911
35061
  if (opts.device) url.searchParams.set("device", opts.device);
34912
35062
  if (opts.posture) url.searchParams.set("posture", opts.posture);
34913
35063
  if (opts.osVersion) url.searchParams.set("osVersion", opts.osVersion);
34914
35064
  if (opts.proof) url.searchParams.set("proof", "1");
35065
+ for (const entry of opts.env ?? []) url.searchParams.append("env", entry);
34915
35066
  return url.toString();
34916
35067
  }
34917
35068
  function deriveCurrentSimBaseUrl(sim, fallback) {
@@ -35089,11 +35240,11 @@ async function waitForProcessExit(pid, timeoutMs = 2500) {
35089
35240
  return !isProcessAlive2(pid);
35090
35241
  }
35091
35242
  function isPlaywrightHostProcess(pid) {
35092
- const plain = (0, import_node_child_process6.spawnSync)("ps", ["-o", "command=", "-p", String(pid)], {
35243
+ const plain = (0, import_node_child_process7.spawnSync)("ps", ["-o", "command=", "-p", String(pid)], {
35093
35244
  encoding: "utf8",
35094
35245
  stdio: ["ignore", "pipe", "pipe"]
35095
35246
  });
35096
- const withEnv = (0, import_node_child_process6.spawnSync)("ps", ["eww", "-o", "command=", "-p", String(pid)], {
35247
+ const withEnv = (0, import_node_child_process7.spawnSync)("ps", ["eww", "-o", "command=", "-p", String(pid)], {
35097
35248
  encoding: "utf8",
35098
35249
  stdio: ["ignore", "pipe", "pipe"]
35099
35250
  });
@@ -35319,7 +35470,8 @@ async function runOpenCommand(args, opts = {}) {
35319
35470
  "--device",
35320
35471
  "--posture",
35321
35472
  "--os-version",
35322
- "--viewport"
35473
+ "--viewport",
35474
+ "--env"
35323
35475
  ]
35324
35476
  });
35325
35477
  const requestedProfileId = args.find((_, i) => args[i - 1] === "--profile");
@@ -35464,6 +35616,17 @@ async function runOpenCommand(args, opts = {}) {
35464
35616
  );
35465
35617
  rnxExit(1);
35466
35618
  }
35619
+ const envArgs = [];
35620
+ for (let i = 0; i < args.length; i++) {
35621
+ if (args[i] !== "--env") continue;
35622
+ const raw = args[i + 1];
35623
+ const eqIndex = raw ? raw.indexOf("=") : -1;
35624
+ if (!raw || eqIndex <= 0) {
35625
+ console.error(` peach open: --env expects KEY=VALUE, got "${raw ?? ""}"`);
35626
+ rnxExit(1);
35627
+ }
35628
+ envArgs.push(raw);
35629
+ }
35467
35630
  const viewportArg = args.find((_, i) => args[i - 1] === "--viewport");
35468
35631
  const viewportMatch = viewportArg ? /^(\d+)x(\d+)$/.exec(viewportArg) : null;
35469
35632
  if (viewportArg && !viewportMatch) {
@@ -35480,7 +35643,8 @@ async function runOpenCommand(args, opts = {}) {
35480
35643
  osVersion: osVersionArg,
35481
35644
  resolvedBundle,
35482
35645
  project,
35483
- proof: args.includes("--proof")
35646
+ proof: args.includes("--proof"),
35647
+ env: envArgs.length ? envArgs : void 0
35484
35648
  };
35485
35649
  const selectedShellBaseUrl = configuredRuntimeVersion ? `http://localhost:${parsed.wsPort}/` : explicitBaseUrl || resolveShellBaseUrlForBridgePort(parsed.wsPort);
35486
35650
  const baseUrl = applyRuntimeGenerationOrigin(
@@ -35986,11 +36150,11 @@ async function runCloseCommand(args, opts = {}) {
35986
36150
  bridge.close();
35987
36151
  }
35988
36152
  }
35989
- var import_node_child_process6, import_node_fs21, import_node_os3, import_node_path23, import_app_catalog_wire, import_settings, import_ws4, DEFAULT_DRIVER_CONNECT_TIMEOUT_MS, DRIVER_CONNECT_INTERVAL_MS, DRIVER_CONNECT_HOST_GRACE_MS, OPEN_BRIDGE_SPAWN_TIMEOUT_MS, SHELL_APPS, DEV_BUNDLE_RESOLVE_ATTEMPTS, DEV_BUNDLE_RESOLVE_INTERVAL_MS;
36153
+ var import_node_child_process7, import_node_fs21, import_node_os3, import_node_path23, import_app_catalog_wire, import_settings, import_ws4, DEFAULT_DRIVER_CONNECT_TIMEOUT_MS, DRIVER_CONNECT_INTERVAL_MS, DRIVER_CONNECT_HOST_GRACE_MS, OPEN_BRIDGE_SPAWN_TIMEOUT_MS, SHELL_APPS, DEV_BUNDLE_RESOLVE_ATTEMPTS, DEV_BUNDLE_RESOLVE_INTERVAL_MS;
35990
36154
  var init_control = __esm({
35991
36155
  "cli/commands/control.ts"() {
35992
36156
  "use strict";
35993
- import_node_child_process6 = require("node:child_process");
36157
+ import_node_child_process7 = require("node:child_process");
35994
36158
  import_node_fs21 = __toESM(require("node:fs"), 1);
35995
36159
  import_node_os3 = __toESM(require("node:os"), 1);
35996
36160
  import_node_path23 = __toESM(require("node:path"), 1);