peachsim 0.1.577 → 0.1.579

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 (178) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +11 -9
  3. package/cli/app-catalog.ts +77 -0
  4. package/cli/app-fonts.ts +4 -20
  5. package/cli/app-splash.ts +2 -18
  6. package/cli/bridge-flow-runner.ts +505 -139
  7. package/cli/cloud-client.ts +1 -4
  8. package/cli/cloud-session.ts +1 -4
  9. package/cli/cloud-test-result-files.ts +3 -0
  10. package/cli/cloud-test-tunnel.ts +2 -5
  11. package/cli/command-registry.ts +2 -0
  12. package/cli/commands/agent.ts +149 -23
  13. package/cli/commands/assert.ts +1 -4
  14. package/cli/commands/box/peach-command.ts +62 -0
  15. package/cli/commands/box/plane-share.ts +0 -70
  16. package/cli/commands/box/rnx-command.ts +1 -60
  17. package/cli/commands/box/search-command.ts +14 -0
  18. package/cli/commands/box.ts +2 -2
  19. package/cli/commands/control.ts +121 -27
  20. package/cli/commands/cpu-profile.ts +23 -10
  21. package/cli/commands/daemon.ts +1 -9
  22. package/cli/commands/detox.ts +26 -18
  23. package/cli/commands/device.ts +129 -1
  24. package/cli/commands/film.ts +25 -16
  25. package/cli/commands/flow.ts +9 -2
  26. package/cli/commands/inspect/actions.ts +75 -1
  27. package/cli/commands/inspect/core.ts +81 -2
  28. package/cli/commands/inspect/describe.ts +2 -6
  29. package/cli/commands/inspect/env.ts +1 -1
  30. package/cli/commands/inspect/settling.ts +20 -3
  31. package/cli/commands/inspect/shared.ts +1 -4
  32. package/cli/commands/inspect/sleep.ts +1 -1
  33. package/cli/commands/inspect.ts +16 -49
  34. package/cli/commands/install-desktop.ts +8 -6
  35. package/cli/commands/login.ts +3 -5
  36. package/cli/commands/maestro-generate.ts +1 -4
  37. package/cli/commands/maestro.ts +38 -21
  38. package/cli/commands/no-bridge-hint.ts +0 -14
  39. package/cli/commands/permissions.ts +2 -2
  40. package/cli/commands/platform.ts +13 -6
  41. package/cli/commands/screenshot.ts +6 -1
  42. package/cli/commands/skills.ts +1 -1
  43. package/cli/commands/test.ts +6 -2
  44. package/cli/commands/three-mode.ts +162 -19
  45. package/cli/commands/upload.ts +3 -18
  46. package/cli/current-sim.ts +0 -1
  47. package/cli/desktop-companion.ts +40 -8
  48. package/cli/drivers/playwright-provisioning.ts +1 -4
  49. package/cli/flow-export.ts +1 -8
  50. package/cli/flow-session.ts +0 -4
  51. package/cli/hints.ts +1 -16
  52. package/cli/inspect-notice-state.ts +0 -4
  53. package/cli/main.ts +2 -4
  54. package/cli/outbound-endpoints.ts +1 -24
  55. package/cli/parse-args.ts +1 -2
  56. package/cli/rnx-in-process.ts +3 -1
  57. package/cli/run-rnx-core.ts +4 -2
  58. package/cli/run-rnx.ts +18 -10
  59. package/cli/send-to-box.ts +1 -4
  60. package/cli/telemetry.ts +1 -1
  61. package/cli/ws-bridge.ts +1 -5
  62. package/detox/gestures.ts +20 -6
  63. package/detox/index.ts +379 -123
  64. package/detox/navigation-lifecycle.ts +22 -13
  65. package/dist-lib/agent-daemon-client.cjs +21 -15
  66. package/dist-lib/agent-events.cjs +1 -1
  67. package/dist-lib/agent-identity.cjs +3 -17
  68. package/dist-lib/agent-sessions.cjs +5 -4
  69. package/dist-lib/attached-projects.cjs +1 -77
  70. package/dist-lib/auth/shared-session.cjs +1 -1
  71. package/dist-lib/backend-origin.cjs +11 -1
  72. package/dist-lib/beta.cjs +1 -1
  73. package/dist-lib/beta.mjs +1 -1
  74. package/dist-lib/bridge-constants.cjs +1 -1
  75. package/dist-lib/bridge-contract-input.cjs +7 -5
  76. package/dist-lib/bridge-contract-input.mjs +7 -5
  77. package/dist-lib/bridge-contract.cjs +1 -1
  78. package/dist-lib/bridge-contract.mjs +1 -1
  79. package/dist-lib/capture-contract.cjs +30 -7
  80. package/dist-lib/capture-contract.mjs +30 -7
  81. package/dist-lib/cli-constants.cjs +1 -1
  82. package/dist-lib/cloud-contract.cjs +1 -1
  83. package/dist-lib/cloud-contract.mjs +1 -1
  84. package/dist-lib/cloud-test-args.cjs +1 -1
  85. package/dist-lib/cloud-test-args.mjs +1 -1
  86. package/dist-lib/cloud.cjs +17 -11
  87. package/dist-lib/cloud.mjs +17 -11
  88. package/dist-lib/config.cjs +1 -1
  89. package/dist-lib/detox/index.cjs +321 -122
  90. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  91. package/dist-lib/home-paths.cjs +1 -1
  92. package/dist-lib/host/bridge-host.cjs +427 -212
  93. package/dist-lib/host/fetch-proxy-handler.cjs +5 -28
  94. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  95. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  96. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  97. package/dist-lib/host/websocket-proxy.cjs +1 -1
  98. package/dist-lib/index.cjs +315 -58
  99. package/dist-lib/jump-to-source-babel.cjs +1 -1
  100. package/dist-lib/jump-to-source-native.cjs +1 -1
  101. package/dist-lib/menu.cjs +67 -31
  102. package/dist-lib/menu.mjs +64 -28
  103. package/dist-lib/metro-fingerprint-registry.cjs +8 -4
  104. package/dist-lib/metro-fingerprint-registry.mjs +6 -4
  105. package/dist-lib/metro-production-bundle.cjs +2 -2
  106. package/dist-lib/metro-production-bundle.mjs +2 -2
  107. package/dist-lib/metro.cjs +8 -4
  108. package/dist-lib/profiles.cjs +1 -1
  109. package/dist-lib/public-brand.cjs +7 -4
  110. package/dist-lib/react-native-host-modules.cjs +1 -1
  111. package/dist-lib/react-native-host-modules.mjs +1 -1
  112. package/dist-lib/render-mode.cjs +3 -13
  113. package/dist-lib/scripts/dev-server-scanner.cjs +5 -17
  114. package/dist-lib/sdk.cjs +129 -10
  115. package/dist-lib/sdk.mjs +127 -10
  116. package/dist-lib/skills.cjs +1635 -1121
  117. package/dist-lib/swift-compile.cjs +277 -70
  118. package/dist-lib/swift-compile.mjs +275 -70
  119. package/dist-lib/swift-package.cjs +17 -5
  120. package/dist-lib/swift-package.mjs +15 -4
  121. package/dist-lib/swift-submit.cjs +32 -8
  122. package/dist-lib/swift-submit.mjs +29 -7
  123. package/dist-lib/swift.cjs +30912 -577
  124. package/dist-lib/vite-kotlin.cjs +356 -0
  125. package/dist-lib/vite-swift.cjs +1743 -0
  126. package/dist-lib/vite.cjs +208 -1039
  127. package/kotlin/README.md +18 -0
  128. package/kotlin/Sources/androidx/annotation/Keep.kt +4 -0
  129. package/kotlin/Sources/dev/onejs/one/OneNativeSourceDispatch.kt +7 -0
  130. package/kotlin/Sources/dev/onejs/one/source/OneModule.kt +4 -0
  131. package/kotlin/Sources/org/json/JSON.kt +244 -0
  132. package/kotlin/toolchain.json +19 -0
  133. package/package.json +14 -2
  134. package/scripts/dev-server-scanner.ts +0 -18
  135. package/scripts/optional-demo-registry.ts +0 -17
  136. package/skills/contrast/SKILL.md +2 -2
  137. package/skills/peach-setup/SKILL.md +16 -0
  138. package/src/agent-daemon-client.ts +36 -22
  139. package/src/agent-identity.ts +0 -15
  140. package/src/agent-prompt.ts +12 -0
  141. package/src/attached-projects.ts +0 -72
  142. package/src/backend-origin.ts +10 -0
  143. package/src/bridge-contract-input.ts +2 -5
  144. package/src/bridge-contract.ts +2 -0
  145. package/src/capture-contract.ts +53 -11
  146. package/src/cloud.ts +4 -9
  147. package/src/dev-server-open.ts +0 -99
  148. package/src/home-paths.ts +2 -3
  149. package/src/host/agent-host.ts +82 -36
  150. package/src/host/bridge-host.ts +5 -11
  151. package/src/host/fetch-proxy-handler.ts +19 -43
  152. package/src/is-record.ts +4 -0
  153. package/src/menu.ts +82 -37
  154. package/src/metro-fingerprint-registry.ts +1 -4
  155. package/src/metro-production-bundle.ts +1 -1
  156. package/src/plist.ts +1 -4
  157. package/src/public-brand.ts +5 -3
  158. package/src/render-mode.ts +0 -12
  159. package/src/runtime-assets.ts +9 -2
  160. package/src/screenshots/compose.ts +7 -1
  161. package/src/screenshots/frame-compose.ts +21 -10
  162. package/src/screenshots/schema.ts +1 -4
  163. package/src/sim-client.ts +2 -4
  164. package/src/skills/builtin/maestro-test.ts +1 -4
  165. package/src/sleep.ts +4 -0
  166. package/src/swift-compile.ts +209 -47
  167. package/src/swift-icu.ts +140 -0
  168. package/src/swift-package.ts +26 -2
  169. package/src/swift-submit.ts +84 -4
  170. package/src/swift.ts +2 -0
  171. package/src/vite-plugin-kotlin.ts +376 -0
  172. package/src/vite-plugin-one.ts +224 -36
  173. package/src/vite-plugin-swift.ts +382 -96
  174. package/src/vite-plugin.ts +121 -134
  175. package/src/vite.d.ts +10 -0
  176. package/cli/resolve-assets.ts +0 -63
  177. package/src/rolldown-plugin-swift.ts +0 -244
  178. package/src/team-machine-prompt.ts +0 -73
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.577 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.579 | (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;
@@ -57,10 +57,19 @@ var init_backend_origin = __esm({
57
57
  }
58
58
  });
59
59
 
60
+ // src/is-record.ts
61
+ function isRecord(value) {
62
+ return typeof value === "object" && value !== null && !Array.isArray(value);
63
+ }
64
+ var init_is_record = __esm({
65
+ "src/is-record.ts"() {
66
+ "use strict";
67
+ }
68
+ });
69
+
60
70
  // scripts/optional-demo-registry.ts
61
71
  var optional_demo_registry_exports = {};
62
72
  __export(optional_demo_registry_exports, {
63
- __withOptionalDemoAppsForTests: () => __withOptionalDemoAppsForTests,
64
73
  findInternalDemoRegistry: () => findInternalDemoRegistry,
65
74
  loadOptionalDemoApps: () => loadOptionalDemoApps,
66
75
  optionalDemoAppsSync: () => optionalDemoAppsSync
@@ -98,15 +107,6 @@ async function loadOptionalDemoApps() {
98
107
  function optionalDemoAppsSync() {
99
108
  return cached ?? [];
100
109
  }
101
- function __withOptionalDemoAppsForTests(apps) {
102
- const previous = cached;
103
- cached = apps;
104
- return {
105
- restore: () => {
106
- cached = previous;
107
- }
108
- };
109
- }
110
110
  var import_node_fs, import_node_path2, import_node_url, REGISTRY_REPO_PATH, MAX_ANCESTOR_STEPS, cached;
111
111
  var init_optional_demo_registry = __esm({
112
112
  "scripts/optional-demo-registry.ts"() {
@@ -121,18 +121,19 @@ var init_optional_demo_registry = __esm({
121
121
  });
122
122
 
123
123
  // src/public-brand.ts
124
- var name, rnxPublicBrand;
124
+ var name, peachPublicBrand, rnxPublicBrand;
125
125
  var init_public_brand = __esm({
126
126
  "src/public-brand.ts"() {
127
127
  "use strict";
128
128
  name = "Peach";
129
- rnxPublicBrand = Object.freeze({
129
+ peachPublicBrand = Object.freeze({
130
130
  name,
131
131
  packageName: "peachsim",
132
132
  commandName: "peach",
133
- domain: "rnxsim.com",
134
- origin: "https://rnxsim.com"
133
+ domain: "peachsim.com",
134
+ origin: "https://peachsim.com"
135
135
  });
136
+ rnxPublicBrand = peachPublicBrand;
136
137
  }
137
138
  });
138
139
 
@@ -586,23 +587,24 @@ var init_shared_session = __esm({
586
587
  });
587
588
 
588
589
  // cli/run-rnx.ts
589
- function rnxExit(code) {
590
- throw new RnxExit(code);
590
+ function peachExit(code) {
591
+ throw new PeachExit(code);
591
592
  }
592
- var import_node_async_hooks, RnxExit, sinks;
593
+ var import_node_async_hooks, PeachExit, sinks, rnxExit;
593
594
  var init_run_rnx = __esm({
594
595
  "cli/run-rnx.ts"() {
595
596
  "use strict";
596
597
  import_node_async_hooks = require("node:async_hooks");
597
- RnxExit = class extends Error {
598
+ PeachExit = class extends Error {
598
599
  code;
599
600
  constructor(code) {
600
601
  super(`peach exited with code ${code}`);
601
- this.name = "RnxExit";
602
+ this.name = "PeachExit";
602
603
  this.code = code;
603
604
  }
604
605
  };
605
606
  sinks = new import_node_async_hooks.AsyncLocalStorage();
607
+ rnxExit = peachExit;
606
608
  }
607
609
  });
608
610
 
@@ -817,6 +819,53 @@ function uniformFrameInsets(value) {
817
819
  function cornerRadii(value) {
818
820
  return { topLeft: value, topRight: value, bottomRight: value, bottomLeft: value };
819
821
  }
822
+ function erodeCornerRadii(radii, by) {
823
+ return {
824
+ topLeft: Math.max(0, radii.topLeft - (by.top + by.left) / 2),
825
+ topRight: Math.max(0, radii.topRight - (by.top + by.right) / 2),
826
+ bottomRight: Math.max(0, radii.bottomRight - (by.bottom + by.right) / 2),
827
+ bottomLeft: Math.max(0, radii.bottomLeft - (by.bottom + by.left) / 2)
828
+ };
829
+ }
830
+ function resolveDeviceFrameCorners(device) {
831
+ const { top, right, bottom, left } = device.frameInsets;
832
+ return {
833
+ frameRadius: device.frameCornerRadius ?? {
834
+ topLeft: device.cornerRadius.topLeft + (top + left) / 2,
835
+ topRight: device.cornerRadius.topRight + (top + right) / 2,
836
+ bottomRight: device.cornerRadius.bottomRight + (bottom + right) / 2,
837
+ bottomLeft: device.cornerRadius.bottomLeft + (bottom + left) / 2
838
+ },
839
+ frameRadiusApproximationError: Math.max(
840
+ Math.abs(top - left),
841
+ Math.abs(top - right),
842
+ Math.abs(bottom - right),
843
+ Math.abs(bottom - left)
844
+ ) / 2,
845
+ screenRadius: device.cornerRadius
846
+ };
847
+ }
848
+ function resolveDeviceFrameGeometry(device) {
849
+ const corners = resolveDeviceFrameCorners(device);
850
+ const housing = device.housing ? {
851
+ ...device.housing,
852
+ // the black glass ring is the body eroded by the rail, so it stays
853
+ // concentric with the body however the body's own corners were set
854
+ glassRadius: erodeCornerRadii(corners.frameRadius, device.housing.rail)
855
+ } : null;
856
+ const bodyLeft = housing?.hingeBarrel?.protrusion ?? 0;
857
+ return {
858
+ ...corners,
859
+ housing,
860
+ bodyLeft,
861
+ frameHeight: device.frameInsets.top + device.height + device.frameInsets.bottom,
862
+ frameWidth: bodyLeft + device.frameInsets.left + device.width + device.frameInsets.right,
863
+ screenBottomInset: device.frameInsets.bottom,
864
+ screenLeft: bodyLeft + device.frameInsets.left,
865
+ screenRightInset: device.frameInsets.right,
866
+ screenTop: device.frameInsets.top
867
+ };
868
+ }
820
869
  function iosDevice(spec) {
821
870
  return {
822
871
  ...spec,
@@ -848,12 +897,147 @@ function deviceDefaultOrientation(model, posture) {
848
897
  }
849
898
  return "portrait";
850
899
  }
851
- function getDevice(model, posture, orientation) {
900
+ function rotateFrameInsets(insets, side) {
901
+ return side === "left" ? { top: insets.left, right: insets.top, bottom: insets.right, left: insets.bottom } : { top: insets.right, right: insets.bottom, bottom: insets.left, left: insets.top };
902
+ }
903
+ function rotateCorners(radii, side) {
904
+ return side === "left" ? {
905
+ topLeft: radii.bottomLeft,
906
+ topRight: radii.topLeft,
907
+ bottomRight: radii.topRight,
908
+ bottomLeft: radii.bottomRight
909
+ } : {
910
+ topLeft: radii.topRight,
911
+ topRight: radii.bottomRight,
912
+ bottomRight: radii.bottomLeft,
913
+ bottomLeft: radii.topLeft
914
+ };
915
+ }
916
+ function rotateHardwareButtons(buttons, portraitFrameHeight, side) {
917
+ const rotate = (button) => side === "left" ? {
918
+ side: button.side === "left" ? "top" : "bottom",
919
+ top: portraitFrameHeight - button.top - button.height,
920
+ height: button.height
921
+ } : {
922
+ side: button.side === "left" ? "bottom" : "top",
923
+ top: button.top,
924
+ height: button.height
925
+ };
926
+ return {
927
+ width: buttons.width,
928
+ lock: rotate(buttons.lock),
929
+ ringToggle: rotate(buttons.ringToggle),
930
+ volumeUp: rotate(buttons.volumeUp),
931
+ volumeDown: rotate(buttons.volumeDown)
932
+ };
933
+ }
934
+ function rotateDevice(device, side) {
935
+ const frameHeight = resolveDeviceFrameGeometry(device).frameHeight;
936
+ const insets = rotateFrameInsets(device.frameInsets, side);
937
+ const barrelProtrusion = device.housing?.hingeBarrel?.protrusion ?? 0;
938
+ if (side === "left") insets.top += barrelProtrusion;
939
+ else insets.bottom += barrelProtrusion;
940
+ const safeArea = device.model === "ipad-pro-11" ? { top: 10, bottom: 20, left: 0, right: 0 } : device.model === "iphone-duo" ? { top: 0, bottom: 20, left: 0, right: 84 } : device.platform === "ios" && device.homeIndicatorHeight > 0 ? {
941
+ top: 0,
942
+ bottom: 20,
943
+ left: device.safeAreaInsets.top,
944
+ right: device.safeAreaInsets.top
945
+ } : device.platform === "ios" ? { top: 0, bottom: 0, left: 0, right: 0 } : {
946
+ top: device.safeAreaInsets.top,
947
+ bottom: device.safeAreaInsets.bottom,
948
+ left: 0,
949
+ right: device.displayCutout.top
950
+ };
951
+ return {
952
+ ...device,
953
+ width: device.height,
954
+ height: device.width,
955
+ orientation: "landscape",
956
+ landscapeSide: side,
957
+ frameInsets: insets,
958
+ frameCornerRadius: device.frameCornerRadius ? rotateCorners(device.frameCornerRadius, side) : void 0,
959
+ cornerRadius: rotateCorners(device.cornerRadius, side),
960
+ continuousCornerRadius: rotateCorners(device.continuousCornerRadius, side),
961
+ housing: device.housing ? {
962
+ rail: rotateFrameInsets(device.housing.rail, side),
963
+ // the rotated barrel is accounted for in frameInsets.
964
+ hingeBarrel: null
965
+ } : void 0,
966
+ hardwareButtons: rotateHardwareButtons(device.hardwareButtons, frameHeight, side),
967
+ cameraCutout: device.cameraCutout ? {
968
+ ...device.cameraCutout,
969
+ top: device.cameraCutout.top ?? (device.statusBarHeight - device.cameraCutout.diameter) / 2
970
+ } : null,
971
+ displayCutout: rotateFrameInsets(device.displayCutout, side),
972
+ // the bars stay on the interface's top and bottom; a cutout's inset moves
973
+ // to whichever side the cutout turned to.
974
+ safeAreaInsets: side === "left" ? safeArea : { ...safeArea, left: safeArea.right, right: safeArea.left },
975
+ statusBarHeight: device.platform === "ios" && device.model !== "ipad-pro-11" && device.model !== "iphone-duo" ? 0 : device.statusBarHeight,
976
+ homeIndicatorHeight: device.platform === "ios" && device.homeIndicatorHeight > 0 ? 20 : device.homeIndicatorHeight
977
+ };
978
+ }
979
+ function getDevice(model, posture, orientation, landscapeSide = "left") {
852
980
  const device = devices[model];
853
- if (!device.supportedPostures) return device;
981
+ if (!device.supportedPostures) {
982
+ return orientation === "landscape" && model !== "canvas-workspace" ? rotateDevice(device, landscapeSide) : device;
983
+ }
854
984
  const resolvedPosture = resolveDevicePosture(model, posture);
855
- if (resolvedPosture === "closed") return device;
856
985
  const resolvedOrientation = orientation ?? deviceDefaultOrientation(model, resolvedPosture);
986
+ if (resolvedPosture === "closed") {
987
+ if (resolvedOrientation === "portrait") return device;
988
+ const insets = device.frameInsets;
989
+ const radii = device.cornerRadius;
990
+ const body = device.frameCornerRadius ?? resolveDeviceFrameCorners(device).frameRadius;
991
+ const housing = device.housing;
992
+ const protrusion = housing?.hingeBarrel?.protrusion ?? 0;
993
+ return {
994
+ ...device,
995
+ width: device.height,
996
+ height: device.width,
997
+ orientation: "landscape",
998
+ cornerRadius: {
999
+ topLeft: radii.topRight,
1000
+ topRight: radii.bottomRight,
1001
+ bottomRight: radii.bottomLeft,
1002
+ bottomLeft: radii.topLeft
1003
+ },
1004
+ continuousCornerRadius: {
1005
+ topLeft: device.continuousCornerRadius.topRight,
1006
+ topRight: device.continuousCornerRadius.bottomRight,
1007
+ bottomRight: device.continuousCornerRadius.bottomLeft,
1008
+ bottomLeft: device.continuousCornerRadius.topLeft
1009
+ },
1010
+ frameInsets: {
1011
+ top: insets.right,
1012
+ right: insets.bottom,
1013
+ bottom: insets.left + protrusion,
1014
+ left: insets.top
1015
+ },
1016
+ frameCornerRadius: {
1017
+ topLeft: body.topRight,
1018
+ topRight: body.bottomRight,
1019
+ bottomRight: body.bottomLeft,
1020
+ bottomLeft: body.topLeft
1021
+ },
1022
+ housing: housing ? {
1023
+ rail: {
1024
+ top: housing.rail.right,
1025
+ right: housing.rail.bottom,
1026
+ bottom: housing.rail.left,
1027
+ left: housing.rail.top
1028
+ },
1029
+ hingeBarrel: null
1030
+ } : void 0,
1031
+ cameraCutout: device.cameraCutout ? {
1032
+ diameter: device.cameraCutout.diameter,
1033
+ top: device.cameraCutout.top,
1034
+ leading: device.cameraCutout.trailing
1035
+ } : null,
1036
+ safeAreaInsets: { top: 0, bottom: 34, left: 84, right: 0 },
1037
+ // vertically compact, so the status bar is hidden.
1038
+ statusBarHeight: 0
1039
+ };
1040
+ }
857
1041
  const isRotatedPortrait = resolvedOrientation === "portrait";
858
1042
  return {
859
1043
  ...device,
@@ -873,6 +1057,10 @@ function getDevice(model, posture, orientation) {
873
1057
  cameraCutout: null,
874
1058
  physicalPPI: 430,
875
1059
  posture: "open",
1060
+ // the fold band exists on the open profile even though the os region
1061
+ // reads inactive while flat; two-group compositions anchor to its centre.
1062
+ reservedDivisionWidth: DUO_DIVISION_REGION_WIDTH,
1063
+ orientation: resolvedOrientation,
876
1064
  // open inner display in landscape carries the same trailing column as
877
1065
  // the closed outer display, so it takes the same 84pt inset: a ~50pt
878
1066
  // control at a ~23pt inset needs 73pt, and anything narrower would shift
@@ -888,11 +1076,10 @@ function listDeviceModels() {
888
1076
  function getRuntimePlatform(model) {
889
1077
  return getDevice(model).platform;
890
1078
  }
891
- var import_menu, LEGACY_HARDWARE_BUTTONS, PIXEL_HARDWARE_BUTTONS, GALAXY_HARDWARE_BUTTONS, ANDROID_GESTURE_NAV_BAR_HEIGHT, ANDROID_OS_VERSION, ANDROID_API_LEVEL, PIXEL_8_WIDTH, PIXEL_8_HEIGHT, PIXEL_8_STATUS_BAR_HEIGHT, devices, DEFAULT_DEVICE, DEFAULT_DEVICE_BY_PLATFORM;
1079
+ var LEGACY_HARDWARE_BUTTONS, PIXEL_HARDWARE_BUTTONS, GALAXY_HARDWARE_BUTTONS, ANDROID_GESTURE_NAV_BAR_HEIGHT, ANDROID_OS_VERSION, ANDROID_API_LEVEL, PIXEL_8_WIDTH, PIXEL_8_HEIGHT, PIXEL_8_STATUS_BAR_HEIGHT, DUO_DIVISION_REGION_WIDTH, devices, DEFAULT_DEVICE, DEFAULT_DEVICE_BY_PLATFORM;
892
1080
  var init_devices = __esm({
893
1081
  "../peach-engine/src/settings/devices.ts"() {
894
1082
  "use strict";
895
- import_menu = require("peachsim/menu");
896
1083
  init_development_mode();
897
1084
  LEGACY_HARDWARE_BUTTONS = {
898
1085
  width: 2.5,
@@ -923,6 +1110,7 @@ var init_devices = __esm({
923
1110
  PIXEL_8_WIDTH = 1080 / 2.625;
924
1111
  PIXEL_8_HEIGHT = 2400 / 2.625;
925
1112
  PIXEL_8_STATUS_BAR_HEIGHT = 132 / 2.625;
1113
+ DUO_DIVISION_REGION_WIDTH = 40;
926
1114
  devices = {
927
1115
  "canvas-workspace": iosDevice({
928
1116
  model: "canvas-workspace",
@@ -937,7 +1125,7 @@ var init_devices = __esm({
937
1125
  frameInsets: uniformFrameInsets(16),
938
1126
  dynamicIsland: null,
939
1127
  safeAreaInsets: { top: 0, bottom: 0, left: 0, right: 0 },
940
- iosVersion: "26.4",
1128
+ iosVersion: "27.0",
941
1129
  // no native device has this geometry; carries the iphone 16 keyboard
942
1130
  // height so the experimental profile still shows a keyboard.
943
1131
  keyboardHeight: 308,
@@ -966,7 +1154,7 @@ var init_devices = __esm({
966
1154
  frameInsets: uniformFrameInsets(28.7),
967
1155
  dynamicIsland: null,
968
1156
  safeAreaInsets: { top: 20, bottom: 0, left: 0, right: 0 },
969
- iosVersion: "15.8",
1157
+ iosVersion: "27.0",
970
1158
  keyboardHeight: 233,
971
1159
  physicalPPI: 326,
972
1160
  hardwareButtons: LEGACY_HARDWARE_BUTTONS
@@ -984,7 +1172,7 @@ var init_devices = __esm({
984
1172
  frameInsets: uniformFrameInsets(19.5),
985
1173
  dynamicIsland: null,
986
1174
  safeAreaInsets: { top: 47, bottom: 34, left: 0, right: 0 },
987
- iosVersion: "17.6",
1175
+ iosVersion: "27.0",
988
1176
  keyboardHeight: 308,
989
1177
  physicalPPI: 460,
990
1178
  hardwareButtons: modernButtons(
@@ -1005,9 +1193,10 @@ var init_devices = __esm({
1005
1193
  cornerRadius: cornerRadii(53.9),
1006
1194
  continuousCornerRadius: cornerRadii(73.402),
1007
1195
  frameInsets: uniformFrameInsets(18.2),
1008
- dynamicIsland: { width: 126, height: 37.3, top: 14.1 },
1196
+ dynamicIsland: { width: 125, height: 110 / 3, top: 34 / 3 },
1197
+ statusRowCenter: 32.75,
1009
1198
  safeAreaInsets: { top: 59, bottom: 34, left: 0, right: 0 },
1010
- iosVersion: "17.6",
1199
+ iosVersion: "27.0",
1011
1200
  keyboardHeight: 308,
1012
1201
  physicalPPI: 460,
1013
1202
  hardwareButtons: modernButtons(
@@ -1028,9 +1217,10 @@ var init_devices = __esm({
1028
1217
  cornerRadius: cornerRadii(53.9),
1029
1218
  continuousCornerRadius: cornerRadii(74.654),
1030
1219
  frameInsets: uniformFrameInsets(17.3),
1031
- dynamicIsland: { width: 126, height: 37.3, top: 11.4 },
1220
+ dynamicIsland: { width: 376 / 3, height: 110 / 3, top: 34 / 3 },
1221
+ statusRowCenter: 30.05,
1032
1222
  safeAreaInsets: { top: 59, bottom: 34, left: 0, right: 0 },
1033
- iosVersion: "17.6",
1223
+ iosVersion: "27.0",
1034
1224
  keyboardHeight: 318,
1035
1225
  physicalPPI: 460,
1036
1226
  hardwareButtons: modernButtons(
@@ -1051,9 +1241,10 @@ var init_devices = __esm({
1051
1241
  cornerRadius: cornerRadii(53.9),
1052
1242
  continuousCornerRadius: cornerRadii(73.402),
1053
1243
  frameInsets: uniformFrameInsets(15.3),
1054
- dynamicIsland: { width: 126, height: 38, top: 13.4 },
1244
+ dynamicIsland: { width: 125, height: 110 / 3, top: 34 / 3 },
1245
+ statusRowCenter: 32.4,
1055
1246
  safeAreaInsets: { top: 59, bottom: 34, left: 0, right: 0 },
1056
- iosVersion: "18.2",
1247
+ iosVersion: "27.0",
1057
1248
  keyboardHeight: 308,
1058
1249
  physicalPPI: 460,
1059
1250
  hardwareButtons: modernButtons(
@@ -1074,9 +1265,10 @@ var init_devices = __esm({
1074
1265
  cornerRadius: cornerRadii(53.9),
1075
1266
  continuousCornerRadius: cornerRadii(74.654),
1076
1267
  frameInsets: uniformFrameInsets(15.3),
1077
- dynamicIsland: { width: 126, height: 38, top: 13.3 },
1268
+ dynamicIsland: { width: 376 / 3, height: 110 / 3, top: 34 / 3 },
1269
+ statusRowCenter: 32.3,
1078
1270
  safeAreaInsets: { top: 59, bottom: 34, left: 0, right: 0 },
1079
- iosVersion: "18.2",
1271
+ iosVersion: "27.0",
1080
1272
  keyboardHeight: 318,
1081
1273
  physicalPPI: 460,
1082
1274
  hardwareButtons: modernButtons(
@@ -1101,11 +1293,10 @@ var init_devices = __esm({
1101
1293
  cornerRadius: cornerRadii(53.9),
1102
1294
  continuousCornerRadius: cornerRadii(73.402),
1103
1295
  frameInsets: uniformFrameInsets(15.3),
1104
- // island top kept at 11 (real 11.3) — this is the calibrated conformance /
1105
- // proof oracle profile; the status-bar clock offsets are tuned against it.
1106
- dynamicIsland: { width: 126, height: 37.3, top: 11 },
1296
+ dynamicIsland: { width: 125, height: 110 / 3, top: 34 / 3 },
1297
+ statusRowCenter: 29.65,
1107
1298
  safeAreaInsets: { top: 59, bottom: 34, left: 0, right: 0 },
1108
- iosVersion: "26.4",
1299
+ iosVersion: "27.0",
1109
1300
  keyboardHeight: 308,
1110
1301
  physicalPPI: 460,
1111
1302
  hardwareButtons: modernButtons(
@@ -1126,9 +1317,10 @@ var init_devices = __esm({
1126
1317
  cornerRadius: cornerRadii(53.9),
1127
1318
  continuousCornerRadius: cornerRadii(74.654),
1128
1319
  frameInsets: uniformFrameInsets(15.3),
1129
- dynamicIsland: { width: 126, height: 37.3, top: 11.4 },
1320
+ dynamicIsland: { width: 376 / 3, height: 110 / 3, top: 34 / 3 },
1321
+ statusRowCenter: 30.05,
1130
1322
  safeAreaInsets: { top: 59, bottom: 34, left: 0, right: 0 },
1131
- iosVersion: "26.4",
1323
+ iosVersion: "27.0",
1132
1324
  keyboardHeight: 318,
1133
1325
  physicalPPI: 460,
1134
1326
  hardwareButtons: modernButtons(
@@ -1151,9 +1343,10 @@ var init_devices = __esm({
1151
1343
  cornerRadius: cornerRadii(61),
1152
1344
  continuousCornerRadius: cornerRadii(84.661),
1153
1345
  frameInsets: uniformFrameInsets(15.3),
1154
- dynamicIsland: { width: 126, height: 38, top: 13.5 },
1346
+ dynamicIsland: { width: 125, height: 110 / 3, top: 14 },
1347
+ statusRowCenter: 32.5,
1155
1348
  safeAreaInsets: { top: 62, bottom: 34, left: 0, right: 0 },
1156
- iosVersion: "26.4",
1349
+ iosVersion: "27.0",
1157
1350
  keyboardHeight: 308,
1158
1351
  physicalPPI: 460,
1159
1352
  hardwareButtons: modernButtons(
@@ -1177,9 +1370,10 @@ var init_devices = __esm({
1177
1370
  cornerRadius: cornerRadii(61.3),
1178
1371
  continuousCornerRadius: cornerRadii(85.163),
1179
1372
  frameInsets: uniformFrameInsets(15.4),
1180
- dynamicIsland: { width: 126, height: 38, top: 14 },
1373
+ dynamicIsland: { width: 376 / 3, height: 110 / 3, top: 14 },
1374
+ statusRowCenter: 33,
1181
1375
  safeAreaInsets: { top: 62, bottom: 34, left: 0, right: 0 },
1182
- iosVersion: "26.4",
1376
+ iosVersion: "27.0",
1183
1377
  keyboardHeight: 318,
1184
1378
  physicalPPI: 460,
1185
1379
  hardwareButtons: modernButtons(
@@ -1201,9 +1395,10 @@ var init_devices = __esm({
1201
1395
  continuousCornerRadius: cornerRadii(84.661),
1202
1396
  // display border 1.44mm plus the visible flat housing rail.
1203
1397
  frameInsets: uniformFrameInsets(15.3),
1204
- dynamicIsland: { width: 126, height: 37.3, top: 14 },
1398
+ dynamicIsland: { width: 125, height: 110 / 3, top: 14 },
1399
+ statusRowCenter: 32.65,
1205
1400
  safeAreaInsets: { top: 59, bottom: 34, left: 0, right: 0 },
1206
- iosVersion: "26.4",
1401
+ iosVersion: "27.0",
1207
1402
  keyboardHeight: 308,
1208
1403
  physicalPPI: 460,
1209
1404
  hardwareButtons: modernButtons(
@@ -1242,9 +1437,10 @@ var init_devices = __esm({
1242
1437
  // face-on. The 17 Pro's flat rail contributes ~6.6pt on the same basis,
1243
1438
  // which is why it sits at 15.3 and the Air should not.
1244
1439
  frameInsets: uniformFrameInsets(10.7),
1245
- dynamicIsland: { width: 126, height: 38, top: 20 },
1440
+ dynamicIsland: { width: 125, height: 110 / 3, top: 20 },
1441
+ statusRowCenter: 39,
1246
1442
  safeAreaInsets: { top: 59, bottom: 34, left: 0, right: 0 },
1247
- iosVersion: "26.4",
1443
+ iosVersion: "27.0",
1248
1444
  keyboardHeight: 318,
1249
1445
  physicalPPI: 460,
1250
1446
  hardwareButtons: modernButtons(
@@ -1268,11 +1464,15 @@ var init_devices = __esm({
1268
1464
  homeIndicatorHeight: 34,
1269
1465
  cornerRadius: cornerRadii(61),
1270
1466
  continuousCornerRadius: cornerRadii(84.661),
1467
+ // the ios 27 simulator's ContainerRelativeShape at the root draws 62
1468
+ // less its smallest inset from the display (54 at 8pt, 32 at 30pt).
1469
+ systemCornerRadius: cornerRadii(62),
1271
1470
  // display border 1.44mm plus the visible flat housing rail.
1272
1471
  frameInsets: uniformFrameInsets(15.3),
1273
- dynamicIsland: { width: 126, height: 38, top: 14 },
1472
+ dynamicIsland: { width: 125, height: 110 / 3, top: 14 },
1473
+ statusRowCenter: 33,
1274
1474
  safeAreaInsets: { top: 62, bottom: 34, left: 0, right: 0 },
1275
- iosVersion: "26.4",
1475
+ iosVersion: "27.0",
1276
1476
  keyboardHeight: 308,
1277
1477
  physicalPPI: 460,
1278
1478
  hardwareButtons: modernButtons(
@@ -1296,9 +1496,10 @@ var init_devices = __esm({
1296
1496
  continuousCornerRadius: cornerRadii(85.163),
1297
1497
  // display border 1.36mm plus the visible flat housing rail.
1298
1498
  frameInsets: uniformFrameInsets(15.4),
1299
- dynamicIsland: { width: 126, height: 38, top: 14 },
1499
+ dynamicIsland: { width: 376 / 3, height: 110 / 3, top: 14 },
1500
+ statusRowCenter: 33,
1300
1501
  safeAreaInsets: { top: 62, bottom: 34, left: 0, right: 0 },
1301
- iosVersion: "26.4",
1502
+ iosVersion: "27.0",
1302
1503
  keyboardHeight: 318,
1303
1504
  physicalPPI: 460,
1304
1505
  hardwareButtons: modernButtons(
@@ -1319,7 +1520,10 @@ var init_devices = __esm({
1319
1520
  height: 678,
1320
1521
  scale: 3,
1321
1522
  statusBarHeight: 66,
1322
- homeIndicatorHeight: 0,
1523
+ // the ios 27.1 duo simulator draws the gesture home indicator in both
1524
+ // postures: a 167pt x 5pt pill 8pt above the display bottom on the
1525
+ // cover, sized by the same rule as the other iphones (HomeIndicator).
1526
+ homeIndicatorHeight: 34,
1323
1527
  // hinge on the left. the hinge-side display corners measure 5pt in
1324
1528
  // apple's 2d outer product art; the free side keeps the 61pt pro curve.
1325
1529
  cornerRadius: { topLeft: 5, topRight: 61, bottomRight: 61, bottomLeft: 5 },
@@ -1329,6 +1533,9 @@ var init_devices = __esm({
1329
1533
  bottomRight: 61,
1330
1534
  bottomLeft: 5
1331
1535
  },
1536
+ // the ios 27.1 duo simulator reports 59, and sheets 8pt in on the free
1537
+ // side carry 51 there (41 at 18pt in).
1538
+ systemCornerRadius: { topLeft: 5, topRight: 59, bottomRight: 59, bottomLeft: 5 },
1332
1539
  // display edge to silhouette, measured off apple's 2d outer product art
1333
1540
  // at 3x. the barrel is not part of the body: it protrudes 11.67pt past
1334
1541
  // the left silhouette, so the full outer width is frame + protrusion.
@@ -1362,6 +1569,45 @@ var init_devices = __esm({
1362
1569
  trailing: 29
1363
1570
  }
1364
1571
  },
1572
+ "ipad-pro-11": {
1573
+ // frame and hardware buttons are not sourced yet, so this stays off the
1574
+ // shipping picker the same way the duo does.
1575
+ developmentOnly: true,
1576
+ ...iosDevice({
1577
+ model: "ipad-pro-11",
1578
+ name: "iPad Pro 11-inch (M5)",
1579
+ // iPad Pro 11-inch (M5) 12GB, iOS 27.0, simulator DBD4B183, UIKit probe
1580
+ // scenario `metrics`. portrait UIScreen.bounds 834×1210, scale 2,
1581
+ // nativeBounds 1668×2420. the window matched the screen.
1582
+ width: 834,
1583
+ height: 1210,
1584
+ scale: 2,
1585
+ // portrait statusBarFrame was 834×32 at the top, and safeAreaInsets.top
1586
+ // was 32. landscape statusBarFrame stayed 32 tall while safeAreaInsets.top
1587
+ // was 10; this field is the portrait bar.
1588
+ statusBarHeight: 32,
1589
+ // portrait and landscape safeAreaInsets.bottom were both 20. the probe
1590
+ // has no separate home-indicator height.
1591
+ homeIndicatorHeight: 20,
1592
+ // UIScreen _displayCornerRadius, both orientations. the framebuffer
1593
+ // mask's tangent extent was not measured separately.
1594
+ cornerRadius: cornerRadii(30),
1595
+ continuousCornerRadius: cornerRadii(30),
1596
+ // housing is unsourced. the frame is the display until it is measured.
1597
+ frameInsets: uniformFrameInsets(0),
1598
+ dynamicIsland: null,
1599
+ // portrait safeAreaInsets. landscape was top 10, left 0, bottom 20, right 0.
1600
+ safeAreaInsets: { top: 32, bottom: 20, left: 0, right: 0 },
1601
+ iosVersion: "27.0",
1602
+ // portrait docked keyboard, UIKeyboardFrameEndUserInfoKey
1603
+ // {0, 870, 834, 340}. landscape posted a zero-height frame.
1604
+ keyboardHeight: 340,
1605
+ // hypot(1668, 2420) / 11, measured native pixels over the device type's
1606
+ // 11-inch name. not a separate density read.
1607
+ physicalPPI: 267.19578360606573,
1608
+ hardwareButtons: modernButtons([0, 0], [0, 0], [0, 0], [0, 0])
1609
+ })
1610
+ },
1365
1611
  "pixel-8": {
1366
1612
  model: "pixel-8",
1367
1613
  platform: "android",
@@ -2810,6 +3056,28 @@ var init_stub_manifest = __esm({
2810
3056
  "../peach-compat/src/stub-manifest.ts"() {
2811
3057
  "use strict";
2812
3058
  COMPAT_REACT_NATIVE_DEEP_STUBS = {
3059
+ assetRegistry: {
3060
+ specifier: "react-native/asset-registry",
3061
+ stubFile: "rn-libraries/AssetRegistry.ts",
3062
+ runtimeTier: "core",
3063
+ buildResolvers: ["dep-prebundle"]
3064
+ },
3065
+ // react native 0.87 absorbed this package into react-native. packages built
3066
+ // against older releases still import the identical legacy entry.
3067
+ assetRegistryLegacy: {
3068
+ specifier: "@react-native/assets-registry/registry",
3069
+ stubFile: "rn-libraries/AssetRegistry.ts",
3070
+ runtimeTier: "core",
3071
+ buildResolvers: ["dep-prebundle"]
3072
+ },
3073
+ // expo 58's keyboard interop deep-imports TextInputState, whose flow source
3074
+ // does not parse outside metro. preserve its focus bookkeeping in the stub.
3075
+ textInputState: {
3076
+ specifier: "react-native/Libraries/Components/TextInput/TextInputState",
3077
+ stubFile: "rn-libraries/TextInputState.ts",
3078
+ runtimeTier: "core",
3079
+ buildResolvers: ["vite", "dep-prebundle"]
3080
+ },
2813
3081
  usePressability: {
2814
3082
  specifier: "react-native/Libraries/Pressability/usePressability",
2815
3083
  stubFile: "rn-libraries/usePressability.ts",
@@ -2846,6 +3114,12 @@ var init_stub_manifest = __esm({
2846
3114
  runtimeTier: "core",
2847
3115
  buildResolvers: ["vite", "dep-prebundle"]
2848
3116
  },
3117
+ reactFabricPublicInstance: {
3118
+ specifier: "react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance",
3119
+ stubFile: "rn-libraries/ReactFabricPublicInstance.ts",
3120
+ runtimeTier: "core",
3121
+ buildResolvers: ["vite", "dep-prebundle"]
3122
+ },
2849
3123
  reactNativeRenderer: {
2850
3124
  specifier: "react-native/Libraries/Renderer/shims/ReactNative",
2851
3125
  stubFile: "rn-libraries/ReactNative.ts",
@@ -3689,6 +3963,7 @@ var init_cloud_session = __esm({
3689
3963
  "cli/cloud-session.ts"() {
3690
3964
  "use strict";
3691
3965
  init_backend_origin();
3966
+ init_is_record();
3692
3967
  CLOUD_SESSION_VERSION = 3;
3693
3968
  MAX_ENCODED_SESSION_BYTES = 32 * 1024;
3694
3969
  RNX_SHELL_UPDATE_PREFIX = "__RNX_SHELL_UPDATE_V1__";
@@ -3743,9 +4018,6 @@ var init_detect = __esm({
3743
4018
  });
3744
4019
 
3745
4020
  // cli/cloud-client.ts
3746
- function isRecord4(value) {
3747
- return value !== null && typeof value === "object" && !Array.isArray(value);
3748
- }
3749
4021
  function failureSentence(text) {
3750
4022
  let parsed;
3751
4023
  try {
@@ -3753,8 +4025,8 @@ function failureSentence(text) {
3753
4025
  } catch {
3754
4026
  return null;
3755
4027
  }
3756
- if (!isRecord4(parsed)) return null;
3757
- if (isRecord4(parsed.error) && typeof parsed.error.message === "string") {
4028
+ if (!isRecord(parsed)) return null;
4029
+ if (isRecord(parsed.error) && typeof parsed.error.message === "string") {
3758
4030
  const code = typeof parsed.error.code === "string" && parsed.error.code ? ` [${parsed.error.code}]` : "";
3759
4031
  return `${parsed.error.message}${code}`;
3760
4032
  }
@@ -3881,7 +4153,7 @@ function listMetroAssetDest(assetsPath) {
3881
4153
  return { directory, files, truncated };
3882
4154
  }
3883
4155
  function metroDescriptorHash(scaleBytes) {
3884
- const hasher = (0, import_node_crypto5.createHash)("md5");
4156
+ const hasher = (0, import_node_crypto6.createHash)("md5");
3885
4157
  for (const bytes of scaleBytes) hasher.update(bytes);
3886
4158
  return hasher.digest("hex");
3887
4159
  }
@@ -3910,7 +4182,7 @@ function distinctByContent(directory, options) {
3910
4182
  } catch {
3911
4183
  continue;
3912
4184
  }
3913
- const digest = (0, import_node_crypto5.createHash)("md5").update(bytes).digest("hex");
4185
+ const digest = (0, import_node_crypto6.createHash)("md5").update(bytes).digest("hex");
3914
4186
  if (!byContent.has(digest)) byContent.set(digest, relative2);
3915
4187
  }
3916
4188
  return [...byContent.values()];
@@ -3944,7 +4216,7 @@ function placeMetroDescriptor(listing, descriptor) {
3944
4216
  continue;
3945
4217
  }
3946
4218
  const identical = fileHashes ? bytes.every((file, index) => {
3947
- const digest = (0, import_node_crypto5.createHash)("md5").update(file).digest("hex");
4219
+ const digest = (0, import_node_crypto6.createHash)("md5").update(file).digest("hex");
3948
4220
  return descriptor.scales.some(
3949
4221
  (scale, declared) => scale === scales[index] && fileHashes[declared] === digest
3950
4222
  );
@@ -4179,23 +4451,23 @@ async function produceArtifact(bundlePath, maxBytes, command, assetPolicy) {
4179
4451
  }
4180
4452
  return {
4181
4453
  bytes,
4182
- sha256: (0, import_node_crypto5.createHash)("sha256").update(bytes).digest("hex"),
4454
+ sha256: (0, import_node_crypto6.createHash)("sha256").update(bytes).digest("hex"),
4183
4455
  receipt: artifact.receipt,
4184
4456
  assetsDirectory: prepared.dest?.directory ?? null,
4185
4457
  warnings
4186
4458
  };
4187
4459
  }
4188
4460
  function readCreateResponse(value, expectedBytes, expectedSha256) {
4189
- if (!isRecord4(value)) throw new Error("remote simulator create returned invalid JSON");
4461
+ if (!isRecord(value)) throw new Error("remote simulator create returned invalid JSON");
4190
4462
  const artifactId = `sha256:${expectedSha256}`;
4191
- const isExpectedArtifact = (artifact) => isRecord4(artifact) && artifact.id === artifactId && typeof artifact.bytes === "number" && Number.isInteger(artifact.bytes) && artifact.bytes === expectedBytes;
4463
+ const isExpectedArtifact = (artifact) => isRecord(artifact) && artifact.id === artifactId && typeof artifact.bytes === "number" && Number.isInteger(artifact.bytes) && artifact.bytes === expectedBytes;
4192
4464
  const boxId = value.boxId;
4193
4465
  const boxToken = value.token;
4194
4466
  const simulator = value.simulator;
4195
- const simId = isRecord4(simulator) ? simulator.simId : null;
4196
- const claim = isRecord4(simulator) ? simulator.claim : null;
4197
- const simulatorToken = isRecord4(simulator) ? simulator.token : null;
4198
- if (typeof boxId !== "string" || !boxId || value.size !== "nano" || typeof boxToken !== "string" || !boxToken || !isExpectedArtifact(value.artifact) || !isRecord4(simulator) || typeof simId !== "string" || !simId || !isExpectedArtifact(simulator.artifact) || !isRecord4(claim) || typeof claim.id !== "string" || !claim.id || typeof claim.expiresAt !== "number" || typeof simulatorToken !== "string" || !simulatorToken) {
4467
+ const simId = isRecord(simulator) ? simulator.simId : null;
4468
+ const claim = isRecord(simulator) ? simulator.claim : null;
4469
+ const simulatorToken = isRecord(simulator) ? simulator.token : null;
4470
+ if (typeof boxId !== "string" || !boxId || value.size !== "nano" || typeof boxToken !== "string" || !boxToken || !isExpectedArtifact(value.artifact) || !isRecord(simulator) || typeof simId !== "string" || !simId || !isExpectedArtifact(simulator.artifact) || !isRecord(claim) || typeof claim.id !== "string" || !claim.id || typeof claim.expiresAt !== "number" || typeof simulatorToken !== "string" || !simulatorToken) {
4199
4471
  throw new Error("remote simulator create returned an invalid receipt");
4200
4472
  }
4201
4473
  return {
@@ -4284,11 +4556,11 @@ async function createCloudBox(options) {
4284
4556
  warnings: artifact.warnings
4285
4557
  };
4286
4558
  }
4287
- var import_node_crypto5, import_node_fs14, import_node_path15, DEFAULT_RNX_CLOUD_ORIGIN, MAX_RNX_CLOUD_BUNDLE_BYTES, REMOTE_COMMAND, MAX_ASSET_DEST_FILES, ACCOUNT_CHOICE_REFUSALS, PROJECT_ONLY_DIRECTORIES, MAX_PLACEMENT_COMBINATIONS;
4559
+ var import_node_crypto6, import_node_fs14, import_node_path15, DEFAULT_RNX_CLOUD_ORIGIN, MAX_RNX_CLOUD_BUNDLE_BYTES, REMOTE_COMMAND, MAX_ASSET_DEST_FILES, ACCOUNT_CHOICE_REFUSALS, PROJECT_ONLY_DIRECTORIES, MAX_PLACEMENT_COMBINATIONS;
4288
4560
  var init_cloud_client = __esm({
4289
4561
  "cli/cloud-client.ts"() {
4290
4562
  "use strict";
4291
- import_node_crypto5 = require("node:crypto");
4563
+ import_node_crypto6 = require("node:crypto");
4292
4564
  import_node_fs14 = require("node:fs");
4293
4565
  import_node_path15 = require("node:path");
4294
4566
  init_metroAssetArtifact();
@@ -4302,6 +4574,7 @@ var init_cloud_client = __esm({
4302
4574
  init_auth();
4303
4575
  init_cloud_session();
4304
4576
  init_detect();
4577
+ init_is_record();
4305
4578
  DEFAULT_RNX_CLOUD_ORIGIN = rnxPublicBrand.origin;
4306
4579
  MAX_RNX_CLOUD_BUNDLE_BYTES = RNX_CLOUD_MAX_ARTIFACT_BYTES;
4307
4580
  REMOTE_COMMAND = "peach remote";
@@ -4329,9 +4602,6 @@ var send_to_box_exports = {};
4329
4602
  __export(send_to_box_exports, {
4330
4603
  sendToBox: () => sendToBox
4331
4604
  });
4332
- function isRecord5(value) {
4333
- return !!value && typeof value === "object" && !Array.isArray(value);
4334
- }
4335
4605
  function cloudDevice(capturedModel) {
4336
4606
  const captured = listDeviceModels().find((model) => model === capturedModel);
4337
4607
  return captured && getRuntimePlatform(captured) === "ios" ? captured : DEFAULT_DEVICE_BY_PLATFORM.ios;
@@ -4342,8 +4612,8 @@ async function sendToBox(options) {
4342
4612
  simId: options.simId,
4343
4613
  code: '(typeof window.__sootsimCaptureBundle === "function") ? window.__sootsimCaptureBundle() : null'
4344
4614
  });
4345
- const bundleUrl = isRecord5(captured) ? captured.bundleUrl : null;
4346
- if (!isRecord5(captured) || typeof bundleUrl !== "string" || !bundleUrl) {
4615
+ const bundleUrl = isRecord(captured) ? captured.bundleUrl : null;
4616
+ if (!isRecord(captured) || typeof bundleUrl !== "string" || !bundleUrl) {
4347
4617
  throw new Error(
4348
4618
  "send to box needs a simulator running an app: this one has no bundle loaded"
4349
4619
  );
@@ -4359,7 +4629,7 @@ async function sendToBox(options) {
4359
4629
  const storage = dumped;
4360
4630
  const created = await createCloudBox({
4361
4631
  bundlePath: toRNXProductionBundleUrl(bundleUrl),
4362
- device: options.device ?? cloudDevice(isRecord5(captured.deviceSpec) ? captured.deviceSpec.model : null),
4632
+ device: options.device ?? cloudDevice(isRecord(captured.deviceSpec) ? captured.deviceSpec.model : null),
4363
4633
  authorization: options.authorization,
4364
4634
  accountId: options.accountId,
4365
4635
  apiOrigin: options.apiOrigin,
@@ -4386,6 +4656,7 @@ var init_send_to_box = __esm({
4386
4656
  init_devices();
4387
4657
  init_metro_production_bundle();
4388
4658
  init_cloud_client();
4659
+ init_is_record();
4389
4660
  }
4390
4661
  });
4391
4662
 
@@ -4481,9 +4752,7 @@ var import_node_path = require("node:path");
4481
4752
 
4482
4753
  // src/plist.ts
4483
4754
  var import_plist = __toESM(require("@expo/plist"), 1);
4484
- function isRecord(value) {
4485
- return typeof value === "object" && value !== null && !Array.isArray(value);
4486
- }
4755
+ init_is_record();
4487
4756
  function resolvePlistParser(value) {
4488
4757
  if (!isRecord(value)) return null;
4489
4758
  const direct = value.parse;
@@ -6052,6 +6321,7 @@ var RNX_RUNTIME_PRODUCT = {
6052
6321
  var rnxRuntime = createRuntimeDelivery(RNX_RUNTIME_PRODUCT);
6053
6322
 
6054
6323
  // src/host/agent-host.ts
6324
+ var import_node_crypto5 = require("node:crypto");
6055
6325
  var import_node_fs10 = __toESM(require("node:fs"), 1);
6056
6326
  var import_node_path10 = __toESM(require("node:path"), 1);
6057
6327
 
@@ -6838,78 +7108,6 @@ async function waitForFirstEvent(fifo, predicate, timeoutMs) {
6838
7108
  }
6839
7109
  }
6840
7110
 
6841
- // src/team-machine-prompt.ts
6842
- var import_node_child_process3 = require("node:child_process");
6843
-
6844
- // src/agent-identity.ts
6845
- var TEAM_MACHINE_SESSION_RE = /^[A-Za-z0-9._:-]{1,160}$/;
6846
- function teamMachineSessionIdFromCliIdentity(identity) {
6847
- if (!identity?.startsWith("TM_SESSION:")) return null;
6848
- const sessionId = identity.slice("TM_SESSION:".length).trim();
6849
- if (!TEAM_MACHINE_SESSION_RE.test(sessionId)) return null;
6850
- return sessionId;
6851
- }
6852
- function isValidTeamMachineSessionId(sessionId) {
6853
- return TEAM_MACHINE_SESSION_RE.test(sessionId);
6854
- }
6855
-
6856
- // src/team-machine-prompt.ts
6857
- function formatTeamMachinePromptText(prompt) {
6858
- return typeof prompt.text === "string" ? prompt.text.trim() : "";
6859
- }
6860
- function resolveTeamMachineCommand(explicit) {
6861
- const fromArg = explicit?.trim();
6862
- if (fromArg) return fromArg;
6863
- return "tm";
6864
- }
6865
- async function sendPromptToTeamMachineSession(input) {
6866
- const sessionId = input.sessionId.trim();
6867
- if (!isValidTeamMachineSessionId(sessionId)) {
6868
- throw new Error(`invalid Team Machine session id: ${sessionId || "<empty>"}`);
6869
- }
6870
- const text = formatTeamMachinePromptText(input.prompt);
6871
- if (!text) {
6872
- throw new Error("prompt text is empty");
6873
- }
6874
- const command = resolveTeamMachineCommand(input.command);
6875
- const timeoutMs = input.timeoutMs ?? 15e3;
6876
- await new Promise((resolve5, reject) => {
6877
- const child = (0, import_node_child_process3.spawn)(command, ["send", sessionId, text], {
6878
- stdio: ["ignore", "ignore", "pipe"],
6879
- env: process.env
6880
- });
6881
- let stderr = "";
6882
- const timer = setTimeout(() => {
6883
- try {
6884
- child.kill("SIGTERM");
6885
- } catch {
6886
- }
6887
- reject(new Error(`tm send timed out after ${Math.round(timeoutMs / 1e3)}s`));
6888
- }, timeoutMs);
6889
- child.stderr?.setEncoding("utf8");
6890
- child.stderr?.on("data", (chunk) => {
6891
- if (stderr.length < 4e3) stderr += String(chunk);
6892
- });
6893
- child.on("error", (err) => {
6894
- clearTimeout(timer);
6895
- reject(err);
6896
- });
6897
- child.on("close", (code, signal) => {
6898
- clearTimeout(timer);
6899
- if (code === 0) {
6900
- resolve5();
6901
- return;
6902
- }
6903
- const detail = stderr.trim();
6904
- reject(
6905
- new Error(
6906
- detail || `tm send exited with ${signal ? `signal ${signal}` : `code ${code ?? "unknown"}`}`
6907
- )
6908
- );
6909
- });
6910
- });
6911
- }
6912
-
6913
7111
  // src/host/agent-host.ts
6914
7112
  var WS_OPEN = 1;
6915
7113
  var SESSION_STATUS_PERSIST_DELAY_MS = 25;
@@ -6931,6 +7129,8 @@ var AgentHost = class {
6931
7129
  // pushes so a CLI `peach agent sessions --watch` can react to state
6932
7130
  // changes driven by another client.
6933
7131
  allSockets = /* @__PURE__ */ new Set();
7132
+ inspectQueues = /* @__PURE__ */ new Map();
7133
+ inspectListeners = /* @__PURE__ */ new Map();
6934
7134
  // agent wrappers also emit prompt-received from the FIFO reader, but the
6935
7135
  // shell/daemon already knows the user-facing display text when send-prompt
6936
7136
  // is accepted. emit the friendly prompt immediately and suppress the raw
@@ -6962,6 +7162,18 @@ var AgentHost = class {
6962
7162
  }
6963
7163
  this.sessionsBySocket.delete(ws);
6964
7164
  }
7165
+ for (const [simId, listeners] of this.inspectListeners) {
7166
+ const remaining = [];
7167
+ for (const listener of listeners) {
7168
+ if (listener.socket === ws) {
7169
+ listener.reject(new AgentSessionError("DISCONNECT", "listener disconnected"));
7170
+ } else {
7171
+ remaining.push(listener);
7172
+ }
7173
+ }
7174
+ if (remaining.length > 0) this.inspectListeners.set(simId, remaining);
7175
+ else this.inspectListeners.delete(simId);
7176
+ }
6965
7177
  this.allSockets.delete(ws);
6966
7178
  }
6967
7179
  /** handle an agent:* message. returns true iff the message was recognized
@@ -7010,6 +7222,13 @@ var AgentHost = class {
7010
7222
  }
7011
7223
  this.subscriptions.clear();
7012
7224
  this.sessionsBySocket.clear();
7225
+ for (const listeners of this.inspectListeners.values()) {
7226
+ for (const listener of listeners) {
7227
+ listener.reject(new AgentSessionError("DISCONNECT", "daemon closed"));
7228
+ }
7229
+ }
7230
+ this.inspectListeners.clear();
7231
+ this.inspectQueues.clear();
7013
7232
  this.allSockets.clear();
7014
7233
  }
7015
7234
  // --- dispatch ---
@@ -7028,8 +7247,17 @@ var AgentHost = class {
7028
7247
  return listSessions(msg.projectId ? String(msg.projectId) : void 0);
7029
7248
  case "agent:start-session":
7030
7249
  return this.doStartSession(msg.input ?? {});
7031
- case "agent:send-claimed-prompt":
7032
- return this.sendClaimedPrompt(msg);
7250
+ case "agent:listen-inspect":
7251
+ return this.listenForInspect(ws, this.requireSimId(msg, type));
7252
+ case "agent:get-inspect-listener-status": {
7253
+ const simId = this.requireSimId(msg, type);
7254
+ return { simId, listening: (this.inspectListeners.get(simId)?.length ?? 0) > 0 };
7255
+ }
7256
+ case "agent:send-inspect-message":
7257
+ return this.sendInspectMessage(
7258
+ this.requireSimId(msg, type),
7259
+ this.normalizePromptEnvelope(msg)
7260
+ );
7033
7261
  case "agent:send-prompt": {
7034
7262
  const sessionId = String(msg.sessionId);
7035
7263
  const session = findSessionById(sessionId);
@@ -7061,27 +7289,45 @@ var AgentHost = class {
7061
7289
  }
7062
7290
  }
7063
7291
  // --- operation impls ---
7064
- async sendClaimedPrompt(msg) {
7292
+ requireSimId(msg, type) {
7065
7293
  const simId = typeof msg.simId === "string" ? msg.simId.trim() : "";
7066
- if (!simId) {
7067
- throw new AgentSessionError("NO_SIM", "agent:send-claimed-prompt requires simId");
7068
- }
7069
- const lease = this.opts.resolveCliLease?.(simId) ?? null;
7070
- if (!lease || lease.kind !== "cli" || lease.expiresAt <= Date.now()) {
7071
- throw new AgentSessionError("NO_CLAIM", `sim ${simId} has no active CLI claim`);
7072
- }
7073
- const sessionId = teamMachineSessionIdFromCliIdentity(lease.cliIdentityKey);
7074
- if (!sessionId) {
7075
- throw new AgentSessionError(
7076
- "UNSUPPORTED_CLAIM",
7077
- `sim ${simId} is claimed by a CLI identity that is not promptable`
7078
- );
7294
+ if (!simId) throw new AgentSessionError("NO_SIM", `${type} requires simId`);
7295
+ return simId;
7296
+ }
7297
+ listenForInspect(socket, simId) {
7298
+ const queue = this.inspectQueues.get(simId);
7299
+ const queued = queue?.shift();
7300
+ if (queued && queue) {
7301
+ if (queue.length === 0) this.inspectQueues.delete(simId);
7302
+ return Promise.resolve(queued);
7079
7303
  }
7080
- await sendPromptToTeamMachineSession({
7081
- sessionId,
7082
- prompt: this.normalizePromptEnvelope(msg)
7304
+ return new Promise((resolve5, reject) => {
7305
+ const listeners = this.inspectListeners.get(simId) ?? [];
7306
+ listeners.push({ socket, resolve: resolve5, reject });
7307
+ this.inspectListeners.set(simId, listeners);
7083
7308
  });
7084
- return { ok: true, routed: "team-machine", sessionId };
7309
+ }
7310
+ sendInspectMessage(simId, prompt) {
7311
+ if (!prompt.text.trim()) {
7312
+ throw new AgentSessionError("EMPTY_PROMPT", "inspect message cannot be empty");
7313
+ }
7314
+ const message = {
7315
+ id: `inspect-${(0, import_node_crypto5.randomUUID)()}`,
7316
+ simId,
7317
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7318
+ prompt
7319
+ };
7320
+ const listeners = this.inspectListeners.get(simId);
7321
+ const listener = listeners?.shift();
7322
+ if (listener && listeners) {
7323
+ if (listeners.length === 0) this.inspectListeners.delete(simId);
7324
+ listener.resolve(message);
7325
+ return { ok: true, queued: false, messageId: message.id };
7326
+ }
7327
+ const queue = this.inspectQueues.get(simId) ?? [];
7328
+ queue.push(message);
7329
+ this.inspectQueues.set(simId, queue);
7330
+ return { ok: true, queued: true, messageId: message.id };
7085
7331
  }
7086
7332
  async doStartSession(input) {
7087
7333
  const project = findProjectById(input.projectId);
@@ -7537,20 +7783,6 @@ function applyFetchProxyCors(res) {
7537
7783
  res.setHeader(key, value);
7538
7784
  }
7539
7785
  }
7540
- function formatFetchProxyError(targetUrl, err) {
7541
- const details = [];
7542
- const error = err;
7543
- if (error?.code) details.push(error.code);
7544
- if (error?.message) details.push(error.message);
7545
- if (error?.cause?.code) details.push(error.cause.code);
7546
- if (error?.cause?.message) details.push(error.cause.message);
7547
- const uniqueDetails = [...new Set(details.filter(Boolean))];
7548
- const message = uniqueDetails.join(" | ") || String(err);
7549
- if (targetUrl.includes("stored-in-.env.local")) {
7550
- return `${message} | upstream url still contains placeholder env values`;
7551
- }
7552
- return message;
7553
- }
7554
7786
  function buildFetchProxyHeaders(reqHeaders, targetUrl) {
7555
7787
  const headers = {};
7556
7788
  for (const [key, value] of Object.entries(reqHeaders)) {
@@ -7572,7 +7804,7 @@ function buildAppApiProxyHeaders(reqHeaders, targetUrl) {
7572
7804
  headers[key] = value;
7573
7805
  }
7574
7806
  headers.host = targetUrl.host;
7575
- if (!Object.keys(headers).some((name2) => name2.toLowerCase() === "expo-origin")) {
7807
+ if (!Object.keys(headers).some((name2) => name2.toLowerCase() === "mobile-origin")) {
7576
7808
  headers.origin = targetUrl.origin;
7577
7809
  headers.referer = `${targetUrl.origin}/`;
7578
7810
  }
@@ -7694,12 +7926,8 @@ async function handleFetchProxyRequest(req, res) {
7694
7926
  try {
7695
7927
  await proxy(upstreamUrl, originalMethod, body, 0);
7696
7928
  } catch (err) {
7697
- if (res.headersSent) {
7698
- res.destroy(err instanceof Error ? err : new Error(String(err)));
7699
- return;
7700
- }
7701
7929
  const norm = normalizeHostname(upstreamUrl.hostname);
7702
- if (isLoopbackHost(norm) && err?.code === "ECONNREFUSED") {
7930
+ if (!res.headersSent && isLoopbackHost(norm) && err?.code === "ECONNREFUSED") {
7703
7931
  const altHost = norm === "::1" ? "127.0.0.1" : norm === "127.0.0.1" ? "::1" : "127.0.0.1";
7704
7932
  if (altHost !== norm) {
7705
7933
  try {
@@ -7711,9 +7939,7 @@ async function handleFetchProxyRequest(req, res) {
7711
7939
  }
7712
7940
  }
7713
7941
  }
7714
- applyFetchProxyCors(res);
7715
- res.writeHead(502, { "Content-Type": "text/plain" });
7716
- res.end(`fetch proxy error: ${formatFetchProxyError(upstreamUrl.href, err)}`);
7942
+ req.socket.destroy();
7717
7943
  }
7718
7944
  }
7719
7945
  async function handleBundleProxyRequest(req, res) {
@@ -7804,10 +8030,7 @@ function handleAppApiRequest(req, res) {
7804
8030
  proxyRes.pipe(res);
7805
8031
  }
7806
8032
  );
7807
- proxyReq.on("error", (err) => {
7808
- res.statusCode = 502;
7809
- res.end(`app proxy error: ${err.message}`);
7810
- });
8033
+ proxyReq.on("error", () => req.socket.destroy());
7811
8034
  req.pipe(proxyReq);
7812
8035
  return true;
7813
8036
  }
@@ -7944,7 +8167,7 @@ async function dispatchPlaneCommand(plane, type, msg, mutationOptions) {
7944
8167
  }
7945
8168
 
7946
8169
  // cli/commands/box/checkout-plane.ts
7947
- var import_node_child_process4 = require("node:child_process");
8170
+ var import_node_child_process3 = require("node:child_process");
7948
8171
  var import_node_fs11 = __toESM(require("node:fs"), 1);
7949
8172
  var import_node_path11 = __toESM(require("node:path"), 1);
7950
8173
  var CheckoutRequiresGitError = class extends Error {
@@ -7986,7 +8209,7 @@ var CheckoutFilePlane = class {
7986
8209
  }
7987
8210
  listFiles() {
7988
8211
  const args = this.trackedOnly ? ["ls-files", "-z"] : ["ls-files", "-co", "--exclude-standard", "-z"];
7989
- const stdout = (0, import_node_child_process4.execFileSync)("git", args, {
8212
+ const stdout = (0, import_node_child_process3.execFileSync)("git", args, {
7990
8213
  cwd: this.root,
7991
8214
  encoding: "utf8",
7992
8215
  maxBuffer: 64 * 1024 * 1024,
@@ -9084,16 +9307,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
9084
9307
  this.portFallbackCount = Math.max(1, opts.portFallbackCount ?? 10);
9085
9308
  this.openUrlHandler = opts.openUrl;
9086
9309
  this.agentHost = new AgentHost({
9087
- getExcludePorts: opts.agentScanExcludes,
9088
- resolveCliLease: (simId) => {
9089
- const sim = this.sims.get(simId);
9090
- const lease = sim ? this.getActiveLease(sim) : null;
9091
- return lease ? {
9092
- kind: lease.kind,
9093
- cliIdentityKey: lease.cliIdentityKey,
9094
- expiresAt: lease.expiresAt
9095
- } : null;
9096
- }
9310
+ getExcludePorts: opts.agentScanExcludes
9097
9311
  });
9098
9312
  this.contrastOrigin = opts.contrastOrigin?.replace(/\/$/, "") || null;
9099
9313
  this.simIdleReapTtlMs = opts.simIdleReapTtlMs ?? _SootSimBridgeHost.SIM_IDLE_REAP_TTL_MS;
@@ -9123,6 +9337,7 @@ var SootSimBridgeHost = class _SootSimBridgeHost {
9123
9337
  async startAsync(options) {
9124
9338
  if (this.httpServer || this.wss) return this.effectivePort;
9125
9339
  this.refreshActiveRuntime();
9340
+ this.bootstrapping = !this.activeRuntimeDirPath || !import_fs3.default.existsSync(import_path3.default.join(this.activeRuntimeDirPath, "index.html"));
9126
9341
  for (let attempt = 0; attempt < this.portFallbackCount; attempt++) {
9127
9342
  const candidate = this.preferredPort + attempt;
9128
9343
  try {