rnxsim 0.1.369 → 0.1.371

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 (265) hide show
  1. package/cli/auth.ts +2 -1
  2. package/cli/bin.ts +283 -273
  3. package/cli/commands/app-fonts.ts +3 -2
  4. package/cli/commands/assert.ts +8 -7
  5. package/cli/commands/auth.ts +2 -1
  6. package/cli/commands/box/checkout-plane.ts +109 -29
  7. package/cli/commands/box/cloud-box.ts +294 -0
  8. package/cli/commands/box/plane-share.ts +208 -0
  9. package/cli/commands/box/rnx-command.ts +2 -2
  10. package/cli/commands/box/search-command.ts +157 -0
  11. package/cli/commands/box.ts +269 -13
  12. package/cli/commands/config.ts +2 -1
  13. package/cli/commands/control.ts +43 -34
  14. package/cli/commands/daemon.ts +22 -21
  15. package/cli/commands/debug.ts +17 -15
  16. package/cli/commands/detox.ts +5 -4
  17. package/cli/commands/device.ts +6 -5
  18. package/cli/commands/electron.ts +6 -5
  19. package/cli/commands/flow.ts +12 -11
  20. package/cli/commands/inspect/find.ts +2 -1
  21. package/cli/commands/inspect/keyboard.ts +2 -1
  22. package/cli/commands/inspect/sleep.ts +2 -1
  23. package/cli/commands/inspect/wait-event.ts +4 -3
  24. package/cli/commands/inspect/wait-idle.ts +2 -1
  25. package/cli/commands/inspect/wait-ready.ts +3 -2
  26. package/cli/commands/inspect/wait-selector.ts +4 -3
  27. package/cli/commands/inspect/wait.ts +2 -1
  28. package/cli/commands/inspect.ts +82 -80
  29. package/cli/commands/install-desktop.ts +5 -4
  30. package/cli/commands/login.ts +3 -2
  31. package/cli/commands/mode.ts +2 -1
  32. package/cli/commands/record.ts +38 -37
  33. package/cli/commands/runtime.ts +9 -8
  34. package/cli/commands/screenshot-layers.ts +4 -3
  35. package/cli/commands/screenshot-mode.ts +2 -1
  36. package/cli/commands/screenshot.ts +8 -7
  37. package/cli/commands/screenshots-capture.ts +2 -1
  38. package/cli/commands/serve.ts +5 -4
  39. package/cli/commands/shell-boolean-mode.ts +2 -1
  40. package/cli/commands/skills.ts +11 -10
  41. package/cli/commands/telemetry.ts +2 -1
  42. package/cli/commands/three-mode.ts +2 -1
  43. package/cli/commands/timeline.ts +4 -3
  44. package/cli/commands/upload.ts +14 -13
  45. package/cli/commands/what-happened.ts +2 -1
  46. package/cli/outbound-endpoints.ts +37 -1
  47. package/cli/recording-access.ts +6 -5
  48. package/cli/run-rnx.ts +140 -0
  49. package/dist-cli/bin.js +6 -6
  50. package/dist-cli/chunks/{agent-OUUFWDLF.js → agent-W2D2V7WF.js} +2 -2
  51. package/dist-cli/chunks/{agent-wrapper-7BE7DR4R.js → agent-wrapper-GVNM2Z4S.js} +2 -2
  52. package/dist-cli/chunks/app-fonts-J6R4TTXD.js +4 -0
  53. package/dist-cli/chunks/{assert-I3CMRNS7.js → assert-YN6U4ZTB.js} +11 -11
  54. package/dist-cli/chunks/auth-XGYZAK2P.js +12 -0
  55. package/dist-cli/chunks/{beta-BHSKY4K4.js → beta-WRDMHHWJ.js} +2 -2
  56. package/dist-cli/chunks/{box-3CVWXVTT.js → box-ML5TFUQW.js} +617 -596
  57. package/dist-cli/chunks/{camera-MNK73TYV.js → camera-OBEI4FZA.js} +2 -2
  58. package/dist-cli/chunks/{chunk-JXARZBWS.js → chunk-2FIT64GR.js} +1 -1
  59. package/dist-cli/chunks/{chunk-RYGQ6LZN.js → chunk-2LH4DYPM.js} +2 -2
  60. package/dist-cli/chunks/chunk-2QW3JH6T.js +99 -0
  61. package/dist-cli/chunks/{chunk-2APXX4RN.js → chunk-2W4YBCPX.js} +2 -2
  62. package/dist-cli/chunks/{chunk-X3PEDNLV.js → chunk-42YOIDLR.js} +2 -2
  63. package/dist-cli/chunks/{chunk-6BMM6OCW.js → chunk-47MSXLWK.js} +2 -2
  64. package/dist-cli/chunks/chunk-4OLUFHEZ.js +74 -0
  65. package/dist-cli/chunks/{chunk-WUR4FXWT.js → chunk-5PPVVGLH.js} +25 -25
  66. package/dist-cli/chunks/{chunk-AKJYEX54.js → chunk-6AZKZ5AJ.js} +2 -2
  67. package/dist-cli/chunks/{chunk-2PVVIPQS.js → chunk-6FPXLSBJ.js} +1 -1
  68. package/dist-cli/chunks/{chunk-CCI3DZKE.js → chunk-763GBI7G.js} +2 -2
  69. package/dist-cli/chunks/{chunk-CZ2HCNRK.js → chunk-7ASCDKA3.js} +1 -1
  70. package/dist-cli/chunks/{chunk-ZNNE5MAV.js → chunk-7Q4WHJDH.js} +1 -1
  71. package/dist-cli/chunks/{chunk-IYPMULI7.js → chunk-7TWD4P6J.js} +2 -2
  72. package/dist-cli/chunks/{chunk-45X3D6CI.js → chunk-7YB46LGI.js} +1 -1
  73. package/dist-cli/chunks/{chunk-3454VMHD.js → chunk-ADJSGO55.js} +2 -2
  74. package/dist-cli/chunks/{chunk-F4OTMARL.js → chunk-AGZZWLEL.js} +2 -2
  75. package/dist-cli/chunks/{chunk-UDXQCEJU.js → chunk-AVIH3TAZ.js} +11 -11
  76. package/dist-cli/chunks/{chunk-DCPDMF27.js → chunk-B26ZJUQA.js} +2 -2
  77. package/dist-cli/chunks/{chunk-D6MGELKZ.js → chunk-BNS5LNEU.js} +1 -1
  78. package/dist-cli/chunks/{chunk-FHRG772G.js → chunk-CJQXP4XM.js} +1 -1
  79. package/dist-cli/chunks/{chunk-OROUEG22.js → chunk-DP6RGTNN.js} +2 -2
  80. package/dist-cli/chunks/{chunk-V7I4CTAX.js → chunk-DYFOLOYL.js} +2 -2
  81. package/dist-cli/chunks/chunk-ERBGQEJ5.js +4 -0
  82. package/dist-cli/chunks/{chunk-H2GB2PLY.js → chunk-ESWSXF55.js} +2 -2
  83. package/dist-cli/chunks/{chunk-FGGESBWD.js → chunk-FLNGSIBL.js} +2 -2
  84. package/dist-cli/chunks/{chunk-4SCVGBOX.js → chunk-GQGS7TRK.js} +1 -1
  85. package/dist-cli/chunks/{chunk-WGE2P25F.js → chunk-H4DU457S.js} +1 -1
  86. package/dist-cli/chunks/{chunk-TBK5UXW2.js → chunk-HFY35VAG.js} +2 -2
  87. package/dist-cli/chunks/{chunk-JLOOLA47.js → chunk-HV3WOWQM.js} +1 -1
  88. package/dist-cli/chunks/{chunk-RV76U3EI.js → chunk-HZ365EMS.js} +10 -10
  89. package/dist-cli/chunks/chunk-IPNDXGVA.js +11 -0
  90. package/dist-cli/chunks/{chunk-TAC4CEIX.js → chunk-IX7Z6ZVJ.js} +2 -2
  91. package/dist-cli/chunks/{chunk-UQJY6O72.js → chunk-IYU2EOIU.js} +1 -1
  92. package/dist-cli/chunks/chunk-J6A5W2DI.js +30 -0
  93. package/dist-cli/chunks/chunk-JKRMBNPO.js +28 -0
  94. package/dist-cli/chunks/{chunk-VGJLCFSC.js → chunk-JUTLQ3GX.js} +1 -1
  95. package/dist-cli/chunks/{chunk-5QFJFASW.js → chunk-KHGXEU4L.js} +1 -1
  96. package/dist-cli/chunks/{chunk-A56GQVNF.js → chunk-KL34TRNW.js} +1 -1
  97. package/dist-cli/chunks/chunk-LFANRLWK.js +7 -0
  98. package/dist-cli/chunks/{chunk-NPVEBXU3.js → chunk-MOIQZVRU.js} +2 -2
  99. package/dist-cli/chunks/chunk-MW4E5EED.js +497 -0
  100. package/dist-cli/chunks/{chunk-AJJCYLBC.js → chunk-N3LAMTGS.js} +2 -2
  101. package/dist-cli/chunks/{chunk-QE5MC4V3.js → chunk-NQ4CI2VI.js} +1 -1
  102. package/dist-cli/chunks/{chunk-6F3WOWFC.js → chunk-OF3U4SZO.js} +2 -2
  103. package/dist-cli/chunks/{chunk-DKSZZABN.js → chunk-OF5OBB67.js} +1 -1
  104. package/dist-cli/chunks/{chunk-MXWME7ET.js → chunk-OQPZOEEP.js} +1 -1
  105. package/dist-cli/chunks/{chunk-SSBZA5VZ.js → chunk-OWDJQWJ7.js} +1 -1
  106. package/dist-cli/chunks/{chunk-YLNSLMQM.js → chunk-PTWMX4AA.js} +2 -2
  107. package/dist-cli/chunks/{chunk-TTUZ5MCK.js → chunk-RYJTPZCX.js} +1 -1
  108. package/dist-cli/chunks/{chunk-A72MIIJI.js → chunk-S3AH2O67.js} +1 -1
  109. package/dist-cli/chunks/{chunk-ZW6JXEVX.js → chunk-SH2HXHR2.js} +2 -2
  110. package/dist-cli/chunks/{chunk-EKAEEWC2.js → chunk-SHLJYXIN.js} +1 -1
  111. package/dist-cli/chunks/{chunk-4G4VCT2M.js → chunk-TL22EBF5.js} +2 -2
  112. package/dist-cli/chunks/{chunk-LU3AHE6T.js → chunk-TWNLRDIH.js} +2 -2
  113. package/dist-cli/chunks/{chunk-B5NW4TT3.js → chunk-UHYAY3RU.js} +2 -2
  114. package/dist-cli/chunks/{chunk-N5KDDGV6.js → chunk-UPMR6Y5P.js} +2 -2
  115. package/dist-cli/chunks/chunk-VEFSHPSU.js +24 -0
  116. package/dist-cli/chunks/{chunk-YPBDPVLR.js → chunk-WCB5XPCH.js} +2 -2
  117. package/dist-cli/chunks/{chunk-USHV3QFZ.js → chunk-WHZE63R2.js} +2 -2
  118. package/dist-cli/chunks/{chunk-ZEF57HQO.js → chunk-WLRIPAGJ.js} +1 -1
  119. package/dist-cli/chunks/{chunk-FBMNYSSW.js → chunk-Y7SKGRAV.js} +1 -1
  120. package/dist-cli/chunks/chunk-Z6CQDU74.js +4 -0
  121. package/dist-cli/chunks/{cleanup-H4XFRMZV.js → cleanup-PTJBMC34.js} +2 -2
  122. package/dist-cli/chunks/cli-version-6ZBD6GKP.js +4 -0
  123. package/dist-cli/chunks/{compat-NKGYERLN.js → compat-FJ2HWRAG.js} +2 -2
  124. package/dist-cli/chunks/config-PYGH7JPN.js +13 -0
  125. package/dist-cli/chunks/{connect-WWWAVVCF.js → connect-AJV4PZA4.js} +2 -2
  126. package/dist-cli/chunks/control-667PAYX4.js +4 -0
  127. package/dist-cli/chunks/daemon-EIBOHZYQ.js +4 -0
  128. package/dist-cli/chunks/{debug-RO6GLDIM.js → debug-XF2XUDHN.js} +20 -20
  129. package/dist-cli/chunks/desktop-HEZYNFYW.js +31 -0
  130. package/dist-cli/chunks/detox-JCFZJRNE.js +52 -0
  131. package/dist-cli/chunks/device-KGUVZHFW.js +18 -0
  132. package/dist-cli/chunks/{diagnose-HDRTUPDO.js → diagnose-KVJQ3TOI.js} +2 -2
  133. package/dist-cli/chunks/{disk-cleanup-QP2NYN43.js → disk-cleanup-7HQXJFUX.js} +2 -2
  134. package/dist-cli/chunks/{do-chain-6LP6SV5J.js → do-chain-3FYJNA2N.js} +2 -2
  135. package/dist-cli/chunks/drivers-5S6M5TJB.js +4 -0
  136. package/dist-cli/chunks/{env-4XD5D2G6.js → env-BX6S2SOM.js} +2 -2
  137. package/dist-cli/chunks/{film-I5SIXLS5.js → film-2WZUVYFA.js} +3 -3
  138. package/dist-cli/chunks/flow-6BZSWNHG.js +4 -0
  139. package/dist-cli/chunks/help-OGJBD4NP.js +4 -0
  140. package/dist-cli/chunks/{hidden-runtime-alias-LJV2S4M2.js → hidden-runtime-alias-PGF55OFH.js} +2 -2
  141. package/dist-cli/chunks/{home-paths-RFI2QRC3.js → home-paths-EGRMTSGE.js} +2 -2
  142. package/dist-cli/chunks/inspect-MZVDGAN4.js +4 -0
  143. package/dist-cli/chunks/install-desktop-QB76DNZF.js +4 -0
  144. package/dist-cli/chunks/login-HRFPYPZ3.js +26 -0
  145. package/dist-cli/chunks/{logout-DKXZZ6HU.js → logout-LRH5HZGP.js} +2 -2
  146. package/dist-cli/chunks/{maestro-WKGVVWRA.js → maestro-S53T4PPE.js} +3 -3
  147. package/dist-cli/chunks/{maestro-generate-KCK6RK2C.js → maestro-generate-YKIVKOB3.js} +3 -3
  148. package/dist-cli/chunks/mode-47JKNUNS.js +26 -0
  149. package/dist-cli/chunks/{optional-demo-registry-EUSKBIPP.js → optional-demo-registry-ARZSPS5Y.js} +2 -2
  150. package/dist-cli/chunks/{perf-IMZMA5MT.js → perf-J73OKB4T.js} +2 -2
  151. package/dist-cli/chunks/{permissions-SLWXTLH6.js → permissions-L3FPI56H.js} +2 -2
  152. package/dist-cli/chunks/{platform-HQ735LAE.js → platform-YAROAHRM.js} +3 -3
  153. package/dist-cli/chunks/privacy-JZQCXHKN.js +4 -0
  154. package/dist-cli/chunks/record-QAQMGYOO.js +72 -0
  155. package/dist-cli/chunks/report-issue-4FVZCAFQ.js +29 -0
  156. package/dist-cli/chunks/reset-NYFUFZCG.js +4 -0
  157. package/dist-cli/chunks/resolve-target-SP22ZGFZ.js +4 -0
  158. package/dist-cli/chunks/runtime-WTS2QMXZ.js +4 -0
  159. package/dist-cli/chunks/{screenshot-command-HAJB5FHK.js → screenshot-command-Q5MGGEWY.js} +11 -11
  160. package/dist-cli/chunks/screenshot-layers-4JW7DRL7.js +20 -0
  161. package/dist-cli/chunks/screenshots-capture-X5PMO44E.js +42 -0
  162. package/dist-cli/chunks/{serve-TM46TKFW.js → serve-3JWJ45J3.js} +20 -20
  163. package/dist-cli/chunks/setup-USPDJESI.js +4 -0
  164. package/dist-cli/chunks/{sim-client-LQUZSO45.js → sim-client-RL6DGOJE.js} +2 -2
  165. package/dist-cli/chunks/skills-QNO6GDJB.js +50 -0
  166. package/dist-cli/chunks/{state-RUVM4Z4S.js → state-OGUCSUC3.js} +2 -2
  167. package/dist-cli/chunks/{storage-GQ33UTYN.js → storage-TG7KFQZ3.js} +2 -2
  168. package/dist-cli/chunks/store-ZYMQW2LN.js +4 -0
  169. package/dist-cli/chunks/telemetry-7CSLUUIW.js +4 -0
  170. package/dist-cli/chunks/timeline-R4G5P335.js +24 -0
  171. package/dist-cli/chunks/{upgrade-AZDZLUIT.js → upgrade-RDTXJ6O3.js} +2 -2
  172. package/dist-cli/chunks/upload-PLF2DPMS.js +4 -0
  173. package/dist-cli/chunks/{version-N3ALB75G.js → version-GU2NUZDS.js} +2 -2
  174. package/dist-cli/chunks/{web-XPHFZATA.js → web-7PUVG3GE.js} +2 -2
  175. package/dist-cli/chunks/welcome-4GGUV7FY.js +5 -0
  176. package/dist-cli/chunks/what-happened-OHAAX6F6.js +25 -0
  177. package/dist-cli/chunks/{ws-bridge-RUXOYOU6.js → ws-bridge-Z3O35MNF.js} +2 -2
  178. package/dist-lib/agent-daemon-client.cjs +1 -1
  179. package/dist-lib/agent-events.cjs +1 -1
  180. package/dist-lib/agent-identity.cjs +1 -1
  181. package/dist-lib/agent-sessions.cjs +1 -1
  182. package/dist-lib/attached-projects.cjs +1 -1
  183. package/dist-lib/auth/shared-session.cjs +1 -1
  184. package/dist-lib/backend-origin.cjs +1 -1
  185. package/dist-lib/beta.cjs +1 -1
  186. package/dist-lib/beta.mjs +1 -1
  187. package/dist-lib/bridge-constants.cjs +1 -1
  188. package/dist-lib/bridge-contract-input.cjs +1 -1
  189. package/dist-lib/bridge-contract-input.mjs +1 -1
  190. package/dist-lib/bridge-contract.cjs +1 -1
  191. package/dist-lib/bridge-contract.mjs +1 -1
  192. package/dist-lib/cli-constants.cjs +1 -1
  193. package/dist-lib/config.cjs +1 -1
  194. package/dist-lib/detox/index.cjs +1 -1
  195. package/dist-lib/dev-bundle-resolution.cjs +69 -34
  196. package/dist-lib/home-paths.cjs +1 -1
  197. package/dist-lib/host/bridge-host.cjs +430 -106
  198. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  199. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  200. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  201. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  202. package/dist-lib/host/websocket-proxy.cjs +1 -1
  203. package/dist-lib/index.cjs +1 -1
  204. package/dist-lib/jump-to-source-babel.cjs +1 -1
  205. package/dist-lib/menu.cjs +1 -1
  206. package/dist-lib/menu.mjs +1 -1
  207. package/dist-lib/metro-production-bundle.cjs +1 -1
  208. package/dist-lib/metro-production-bundle.mjs +1 -1
  209. package/dist-lib/metro.cjs +1 -1
  210. package/dist-lib/profiles.cjs +1 -1
  211. package/dist-lib/public-brand.cjs +1 -1
  212. package/dist-lib/react-native-host-modules.cjs +2 -1
  213. package/dist-lib/react-native-host-modules.mjs +2 -1
  214. package/dist-lib/render-mode.cjs +1 -1
  215. package/dist-lib/scripts/dev-server-scanner.cjs +149 -93
  216. package/dist-lib/sdk.cjs +1 -1
  217. package/dist-lib/sdk.mjs +1 -1
  218. package/dist-lib/skills.cjs +757 -353
  219. package/dist-lib/vite.cjs +1 -1
  220. package/package.json +1 -1
  221. package/scripts/dev-server-scanner.ts +275 -92
  222. package/src/dev-bundle-resolution.ts +123 -40
  223. package/src/host/bridge-host.ts +11 -0
  224. package/src/host/plane-host.ts +203 -0
  225. package/src/react-native-host-modules.ts +1 -0
  226. package/dist-cli/chunks/app-fonts-66V7TL26.js +0 -4
  227. package/dist-cli/chunks/auth-3FMWUVJH.js +0 -12
  228. package/dist-cli/chunks/chunk-3NLB5LCN.js +0 -28
  229. package/dist-cli/chunks/chunk-5RWUFIZW.js +0 -24
  230. package/dist-cli/chunks/chunk-COXCR6UU.js +0 -7
  231. package/dist-cli/chunks/chunk-JH5KE2OI.js +0 -4
  232. package/dist-cli/chunks/chunk-ROISJKH3.js +0 -99
  233. package/dist-cli/chunks/chunk-SUHAD6N2.js +0 -30
  234. package/dist-cli/chunks/chunk-TIB3CTRX.js +0 -497
  235. package/dist-cli/chunks/chunk-YFZLDFZK.js +0 -74
  236. package/dist-cli/chunks/cli-version-MJWLDXBW.js +0 -4
  237. package/dist-cli/chunks/config-WK7467KI.js +0 -13
  238. package/dist-cli/chunks/control-ESI7IZNN.js +0 -4
  239. package/dist-cli/chunks/daemon-UCLO27YP.js +0 -4
  240. package/dist-cli/chunks/desktop-FF34Z5X4.js +0 -31
  241. package/dist-cli/chunks/detox-Q3CWY5UX.js +0 -52
  242. package/dist-cli/chunks/device-WN4ZWLES.js +0 -18
  243. package/dist-cli/chunks/drivers-OR2JPAXD.js +0 -4
  244. package/dist-cli/chunks/flow-4GWW5DYT.js +0 -4
  245. package/dist-cli/chunks/help-MIIWOT3C.js +0 -4
  246. package/dist-cli/chunks/inspect-3ZGNJENJ.js +0 -4
  247. package/dist-cli/chunks/install-desktop-BYOJVTLS.js +0 -4
  248. package/dist-cli/chunks/login-GNIFSAFE.js +0 -26
  249. package/dist-cli/chunks/mode-YVMX7OJY.js +0 -26
  250. package/dist-cli/chunks/privacy-EDC7PF7G.js +0 -4
  251. package/dist-cli/chunks/record-N4KTTVDI.js +0 -72
  252. package/dist-cli/chunks/report-issue-PP752HAD.js +0 -29
  253. package/dist-cli/chunks/reset-DRMKGVP4.js +0 -4
  254. package/dist-cli/chunks/resolve-target-7WWI32R3.js +0 -4
  255. package/dist-cli/chunks/runtime-EKJV2ZVI.js +0 -4
  256. package/dist-cli/chunks/screenshot-layers-GOCJVM2G.js +0 -20
  257. package/dist-cli/chunks/screenshots-capture-YYJLYA3V.js +0 -42
  258. package/dist-cli/chunks/setup-FVPVIYE4.js +0 -4
  259. package/dist-cli/chunks/skills-V5TE57FN.js +0 -50
  260. package/dist-cli/chunks/store-IKNBDDKM.js +0 -4
  261. package/dist-cli/chunks/telemetry-2ZVU6WLK.js +0 -4
  262. package/dist-cli/chunks/timeline-LP7DMJYT.js +0 -24
  263. package/dist-cli/chunks/upload-DFQK2Q7X.js +0 -4
  264. package/dist-cli/chunks/welcome-2NFYLWLJ.js +0 -5
  265. package/dist-cli/chunks/what-happened-EJ5PHKT7.js +0 -25
@@ -0,0 +1,208 @@
1
+ // a local box authorizing its own checkout for browser access.
2
+ //
3
+ // the bridge daemon serves the `plane:*` envelope, but it will not open a root
4
+ // nobody has authorized, because a WebSocket to 127.0.0.1 is not subject to
5
+ // CORS and any page the user visits can open one. so the box registers its
6
+ // root and holds the socket for as long as it runs; when the box exits, the
7
+ // registration goes with it and the tab loses reach.
8
+ //
9
+ // THIS NEVER BLOCKS THE BOX. an earlier version awaited the socket before
10
+ // printing the prompt and cost 1450ms of start time on every box, because a
11
+ // daemon built before `plane:*` existed accepts the socket, ignores the
12
+ // message, and never replies, so the client sat on its timeout. a box that
13
+ // cannot share its plane is a box with one capability missing, not a box that
14
+ // starts slowly.
15
+ //
16
+ // IT ALSO REFUSES TO GUESS A PORT. when no lockfile claims a bridge,
17
+ // resolveBridgeWorld reports source 'default' and hands back
18
+ // DEFAULT_SOOTSIM_BRIDGE_PORT, which on a machine running a real daemon is
19
+ // that daemon. registering there would attach a test's or a benchmark's box to
20
+ // another session's daemon, which is exactly what a private SOOTSIM_HOME
21
+ // exists to prevent. so a guessed port is refused by name.
22
+
23
+ import WebSocket from 'ws'
24
+ import { bridgeWsUrl, resolveBridgeWorld } from '../../ws-bridge'
25
+
26
+ const REGISTER_TIMEOUT_MS = 1500
27
+
28
+ export interface PlaneShare {
29
+ /**
30
+ * resolves when the daemon has accepted the root, or with a reason it did
31
+ * not. `holders` is how many boxes now hold this checkout, including this
32
+ * one, so more than one means another box is writing the same tree.
33
+ */
34
+ readonly settled: Promise<{ port: number; holders: number } | { declined: string }>
35
+ close(): void
36
+ }
37
+
38
+ function target(
39
+ explicitPort: number | undefined,
40
+ ): { port: number } | { declined: string } {
41
+ if (explicitPort) return { port: explicitPort }
42
+ const world = resolveBridgeWorld()
43
+ if (world.source === 'default') {
44
+ return {
45
+ declined:
46
+ 'no daemon or dev bridge claims a port, and the default is another session’s daemon on a shared machine',
47
+ }
48
+ }
49
+ return { port: world.bridgePort }
50
+ }
51
+
52
+ /**
53
+ * register `root` with the bridge daemon in the background. returns
54
+ * immediately; the box never waits on it.
55
+ */
56
+ export function sharePlane(root: string, opts: { port?: number } = {}): PlaneShare {
57
+ const chosen = target(opts.port)
58
+ if ('declined' in chosen) {
59
+ return { settled: Promise.resolve(chosen), close: () => {} }
60
+ }
61
+
62
+ const port = chosen.port
63
+ let socket: WebSocket | null = null
64
+ let closed = false
65
+
66
+ const settled = new Promise<{ port: number; holders: number } | { declined: string }>(
67
+ (resolve) => {
68
+ let done = false
69
+ const finish = (
70
+ value: { port: number; holders: number } | { declined: string },
71
+ ) => {
72
+ if (done) return
73
+ done = true
74
+ clearTimeout(timer)
75
+ resolve(value)
76
+ }
77
+
78
+ const timer = setTimeout(() => {
79
+ // an older daemon accepts the socket and ignores plane:* entirely, so
80
+ // silence is the expected shape of "not supported" rather than a fault.
81
+ socket?.close()
82
+ finish({ declined: `bridge on port ${port} did not answer plane:register` })
83
+ }, REGISTER_TIMEOUT_MS)
84
+
85
+ try {
86
+ socket = new WebSocket(bridgeWsUrl(port))
87
+ } catch (error) {
88
+ finish({ declined: error instanceof Error ? error.message : String(error) })
89
+ return
90
+ }
91
+ if (closed) {
92
+ socket.close()
93
+ finish({ declined: 'box exited before the plane registered' })
94
+ return
95
+ }
96
+
97
+ socket.on('error', (error) => finish({ declined: error.message }))
98
+ socket.on('close', () => finish({ declined: `bridge on port ${port} closed` }))
99
+ socket.on('open', () => {
100
+ // node sends no Origin, so this socket is already cli-role. a browser
101
+ // client has to send bridge:hello to be corrected off the provisional
102
+ // sim role; see plans/rnx-cloud/transport-decision.md.
103
+ socket?.send(JSON.stringify({ type: 'plane:register', id: 'register', root }))
104
+ })
105
+ socket.on('message', (data) => {
106
+ let parsed: unknown
107
+ try {
108
+ parsed = JSON.parse(String(data))
109
+ } catch {
110
+ return
111
+ }
112
+ if (typeof parsed !== 'object' || parsed === null) return
113
+ const reply: Record<string, unknown> = { ...parsed }
114
+ if (reply.id !== 'register') return
115
+ if (reply.error) {
116
+ socket?.close()
117
+ finish({ declined: String(reply.error) })
118
+ return
119
+ }
120
+ // the daemon nests its payload under `result`; reading holders off the
121
+ // envelope silently reported 1 forever, which is the exact silence this
122
+ // field exists to remove.
123
+ const result = (reply.result ?? {}) as Record<string, unknown>
124
+ finish({ port, holders: Number(result.holders ?? 1) })
125
+ })
126
+ },
127
+ )
128
+
129
+ return {
130
+ settled,
131
+ close: () => {
132
+ closed = true
133
+ try {
134
+ socket?.close()
135
+ } catch {}
136
+ },
137
+ }
138
+ }
139
+
140
+ /**
141
+ * How many boxes hold `root` right now, without registering anything.
142
+ *
143
+ * `plane:register` cannot answer this: registering is what makes a root
144
+ * reachable over a loopback socket, so a readiness probe that asked with it
145
+ * would both authorize the root and count itself in the answer.
146
+ */
147
+ export async function planeStatus(
148
+ root: string,
149
+ opts: { port?: number } = {},
150
+ ): Promise<{ holders: number; registered: boolean } | { declined: string }> {
151
+ const chosen = target(opts.port)
152
+ if ('declined' in chosen) return chosen
153
+ const port = chosen.port
154
+
155
+ return new Promise((resolve) => {
156
+ let done = false
157
+ let socket: WebSocket | null = null
158
+ const finish = (
159
+ value: { holders: number; registered: boolean } | { declined: string },
160
+ ) => {
161
+ if (done) return
162
+ done = true
163
+ clearTimeout(timer)
164
+ try {
165
+ socket?.close()
166
+ } catch {}
167
+ resolve(value)
168
+ }
169
+ const timer = setTimeout(
170
+ () => finish({ declined: `bridge on port ${port} did not answer plane:status` }),
171
+ REGISTER_TIMEOUT_MS,
172
+ )
173
+
174
+ try {
175
+ socket = new WebSocket(bridgeWsUrl(port))
176
+ } catch (error) {
177
+ finish({ declined: error instanceof Error ? error.message : String(error) })
178
+ return
179
+ }
180
+ socket.on('error', (error) => finish({ declined: error.message }))
181
+ socket.on('close', () => finish({ declined: `bridge on port ${port} closed` }))
182
+ socket.on('open', () => {
183
+ socket?.send(JSON.stringify({ type: 'plane:status', id: 'status', root }))
184
+ })
185
+ socket.on('message', (data) => {
186
+ let parsed: unknown
187
+ try {
188
+ parsed = JSON.parse(String(data))
189
+ } catch {
190
+ return
191
+ }
192
+ if (typeof parsed !== 'object' || parsed === null) return
193
+ const reply: Record<string, unknown> = { ...parsed }
194
+ if (reply.id !== 'status') return
195
+ if (reply.error) {
196
+ finish({ declined: String(reply.error) })
197
+ return
198
+ }
199
+ // the daemon nests its payload under `result`, the same shape
200
+ // plane:register answers in.
201
+ const result = (reply.result ?? {}) as Record<string, unknown>
202
+ finish({
203
+ holders: Number(result.holders ?? 0),
204
+ registered: Boolean(result.registered),
205
+ })
206
+ })
207
+ })
208
+ }
@@ -8,8 +8,8 @@
8
8
 
9
9
  import { spawn } from 'node:child_process'
10
10
  import path from 'node:path'
11
- import { defineCommand, type CustomCommand } from '@contrast/harness'
12
- import { PROJECT_MOUNT } from '@contrast/harness'
11
+ import { defineCommand, type CustomCommand } from '@contrast/harness/shell'
12
+ import { PROJECT_MOUNT } from '@contrast/harness/shell'
13
13
  import { rnxPublicBrand } from '../../../src/public-brand'
14
14
  import { RNX_PLACEMENT_ENV } from '../../ws-bridge'
15
15
 
@@ -0,0 +1,157 @@
1
+ // `rg` inside a local box, run by git rather than by a JavaScript matcher.
2
+ //
3
+ // just-bash implements rg in JavaScript and reads every file body through the
4
+ // plane, so one search of this repo is ~15,000 plane reads. MEASURED in the box
5
+ // shell: 14,093ms and two lines of output, which are its abort message. It does
6
+ // not return results at all, and searching is the most common thing an agent
7
+ // does, so that is the box being unusable on a real project.
8
+ //
9
+ // WHY GIT GREP AND NOT RIPGREP. The first version of this shelled out to `rg`,
10
+ // which turned CI red: the Linux runners have no ripgrep binary, and a box that
11
+ // depends on whatever happens to be installed on the host is not a box. Git is
12
+ // different in kind, not degree: `CheckoutFilePlane` already REFUSES to open a
13
+ // project that is not a git repository and runs `git ls-files` before every
14
+ // single command, so git is a hard, already-enforced requirement. Searching
15
+ // with it adds no dependency and cannot be absent where the box already runs.
16
+ //
17
+ // It also makes the file set correct BY CONSTRUCTION rather than by agreement.
18
+ // The box indexes `git ls-files -co --exclude-standard`, tracked AND
19
+ // untracked-not-ignored, and `git grep --untracked` applies those same rules
20
+ // from the same tool. RAN against a checkout built to expose the difference:
21
+ // tracked, untracked-not-ignored, a dotfile and a dotdir file all match, and
22
+ // gitignored trees do not. Ripgrep needed the plane's path list piped into it
23
+ // to get there, because plain `rg .` silently misses dotfiles and `rg --hidden`
24
+ // searches .git/**.
25
+ //
26
+ // MEASURED on this repo, 14,949 indexed files: 391ms p50 against ripgrep's
27
+ // 260ms and just-bash's 14,093ms. Giving up 130ms to remove a host dependency
28
+ // and an entire class of "works on my machine" is the right trade.
29
+ //
30
+ // It also removes the ARG_MAX chunking the ripgrep version needed. Git walks
31
+ // its own index, so there is one process and one exit code rather than several
32
+ // whose "no match here" had to be merged.
33
+
34
+ import { spawn } from 'node:child_process'
35
+ import { defineCommand, type CustomCommand } from '@contrast/harness/shell'
36
+ import { PROJECT_MOUNT } from '@contrast/harness/shell'
37
+
38
+ // generous next to a 414ms p95: this bounds a wedged child, it does not cut off
39
+ // a large search.
40
+ const SEARCH_DEADLINE_MS = 20_000
41
+
42
+ /**
43
+ * THE DEADLINE SETTLES THIS PROMISE ITSELF rather than only killing the child.
44
+ * A spawned child's answer has been observed going missing while the process
45
+ * stayed alive, with no one able to say which event never arrived, so nothing
46
+ * that waits on a child event can be trusted to settle. A timer in this process
47
+ * cannot be missed, so it is the authority and the kill is a side effect.
48
+ * See docs/testing.md.
49
+ */
50
+ function runGitGrep(args: string[], cwd: string) {
51
+ return new Promise<{ stdout: string; stderr: string; exitCode: number }>((resolve) => {
52
+ const child = spawn('git', args, {
53
+ cwd,
54
+ env: process.env,
55
+ stdio: ['ignore', 'pipe', 'pipe'],
56
+ })
57
+ let stdout = ''
58
+ let stderr = ''
59
+ let settled = false
60
+ const finish = (exitCode: number) => {
61
+ if (settled) return
62
+ settled = true
63
+ clearTimeout(timer)
64
+ resolve({ stdout, stderr, exitCode })
65
+ }
66
+ const timer = setTimeout(() => {
67
+ try {
68
+ child.kill('SIGKILL')
69
+ } catch {}
70
+ stderr += `rg: search timed out after ${SEARCH_DEADLINE_MS}ms\n`
71
+ finish(2)
72
+ }, SEARCH_DEADLINE_MS)
73
+
74
+ child.stdout.on('data', (chunk) => {
75
+ stdout += String(chunk)
76
+ })
77
+ child.stderr.on('data', (chunk) => {
78
+ stderr += String(chunk)
79
+ })
80
+ child.once('error', (error) => {
81
+ stderr += `rg: ${error.message}\n`
82
+ finish(2)
83
+ })
84
+ child.once('close', (code) => finish(code ?? 0))
85
+ })
86
+ }
87
+
88
+ // the rg flags worth honouring, mapped onto git grep's spelling. anything else
89
+ // is REFUSED BY NAME rather than dropped, because a search that silently
90
+ // ignores `-i` returns a confidently wrong answer.
91
+ const FLAG_MAP: Record<string, string> = {
92
+ '-i': '-i',
93
+ '--ignore-case': '-i',
94
+ '-l': '-l',
95
+ '--files-with-matches': '-l',
96
+ '-w': '-w',
97
+ '--word-regexp': '-w',
98
+ '-c': '-c',
99
+ '--count': '-c',
100
+ '-v': '-v',
101
+ '--invert-match': '-v',
102
+ '-F': '-F',
103
+ '--fixed-strings': '-F',
104
+ }
105
+
106
+ export function createSearchCommand(options: { root: string }): CustomCommand {
107
+ return defineCommand('rg', async (args) => {
108
+ const flags: string[] = []
109
+ const bare: string[] = []
110
+ for (const arg of args) {
111
+ if (!arg.startsWith('-')) {
112
+ bare.push(arg)
113
+ continue
114
+ }
115
+ const mapped = FLAG_MAP[arg]
116
+ if (!mapped) {
117
+ return {
118
+ stdout: '',
119
+ stderr: `rg: this box does not support ${arg}\n`,
120
+ exitCode: 2,
121
+ }
122
+ }
123
+ flags.push(mapped)
124
+ }
125
+
126
+ const pattern = bare.shift()
127
+ if (pattern === undefined) {
128
+ return { stdout: '', stderr: 'rg: no pattern given\n', exitCode: 2 }
129
+ }
130
+
131
+ // paths are project-relative to git; strip the shell's mount prefix so
132
+ // `rg x /project/src` and `rg x src` mean the same thing.
133
+ const scopes = bare.map((p) =>
134
+ p.startsWith(`${PROJECT_MOUNT}/`) ? p.slice(PROJECT_MOUNT.length + 1) : p,
135
+ )
136
+
137
+ // --untracked is what makes this the plane's set rather than git's tracked
138
+ // set. -E for extended regex, the closest of git's grammars to ripgrep's.
139
+ // -e so a pattern beginning with a dash is a pattern, not a flag.
140
+ const result = await runGitGrep(
141
+ [
142
+ 'grep',
143
+ '--no-color',
144
+ '-n',
145
+ '-I',
146
+ '--untracked',
147
+ '-E',
148
+ ...flags,
149
+ '-e',
150
+ pattern,
151
+ ...(scopes.length > 0 ? ['--', ...scopes] : []),
152
+ ],
153
+ options.root,
154
+ )
155
+ return result
156
+ })
157
+ }