@quenty/studio-bridge 0.21.1 → 0.22.0

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 (482) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +109 -17
  3. package/build-scripts/transform-add-session-attribute.lua +1 -0
  4. package/deploy.nevermore.json +10 -0
  5. package/dist/src/bridge/bridge-connection.d.ts +26 -2
  6. package/dist/src/bridge/bridge-connection.d.ts.map +1 -1
  7. package/dist/src/bridge/bridge-connection.js +159 -14
  8. package/dist/src/bridge/bridge-connection.js.map +1 -1
  9. package/dist/src/bridge/bridge-connection.test.js +81 -0
  10. package/dist/src/bridge/bridge-connection.test.js.map +1 -1
  11. package/dist/src/bridge/bridge-session.d.ts +35 -2
  12. package/dist/src/bridge/bridge-session.d.ts.map +1 -1
  13. package/dist/src/bridge/bridge-session.js +185 -28
  14. package/dist/src/bridge/bridge-session.js.map +1 -1
  15. package/dist/src/bridge/bridge-session.test.js +6 -4
  16. package/dist/src/bridge/bridge-session.test.js.map +1 -1
  17. package/dist/src/bridge/index.d.ts +1 -1
  18. package/dist/src/bridge/index.d.ts.map +1 -1
  19. package/dist/src/bridge/index.js.map +1 -1
  20. package/dist/src/bridge/internal/bridge-client.d.ts +4 -0
  21. package/dist/src/bridge/internal/bridge-client.d.ts.map +1 -1
  22. package/dist/src/bridge/internal/bridge-client.js +20 -7
  23. package/dist/src/bridge/internal/bridge-client.js.map +1 -1
  24. package/dist/src/bridge/internal/bridge-client.test.js +18 -0
  25. package/dist/src/bridge/internal/bridge-client.test.js.map +1 -1
  26. package/dist/src/bridge/internal/bridge-host.d.ts +13 -1
  27. package/dist/src/bridge/internal/bridge-host.d.ts.map +1 -1
  28. package/dist/src/bridge/internal/bridge-host.js +35 -0
  29. package/dist/src/bridge/internal/bridge-host.js.map +1 -1
  30. package/dist/src/bridge/internal/bridge-host.test.js +31 -0
  31. package/dist/src/bridge/internal/bridge-host.test.js.map +1 -1
  32. package/dist/src/bridge/internal/hand-off.d.ts.map +1 -1
  33. package/dist/src/bridge/internal/hand-off.js +4 -1
  34. package/dist/src/bridge/internal/hand-off.js.map +1 -1
  35. package/dist/src/bridge/internal/host-protocol.d.ts +8 -0
  36. package/dist/src/bridge/internal/host-protocol.d.ts.map +1 -1
  37. package/dist/src/bridge/internal/host-protocol.js +120 -13
  38. package/dist/src/bridge/internal/host-protocol.js.map +1 -1
  39. package/dist/src/bridge/internal/host-protocol.test.js +13 -0
  40. package/dist/src/bridge/internal/host-protocol.test.js.map +1 -1
  41. package/dist/src/bridge/internal/instance-state.d.ts +9 -0
  42. package/dist/src/bridge/internal/instance-state.d.ts.map +1 -0
  43. package/dist/src/bridge/internal/instance-state.js +28 -0
  44. package/dist/src/bridge/internal/instance-state.js.map +1 -0
  45. package/dist/src/bridge/internal/transport-client.d.ts.map +1 -1
  46. package/dist/src/bridge/internal/transport-client.js +2 -1
  47. package/dist/src/bridge/internal/transport-client.js.map +1 -1
  48. package/dist/src/bridge/internal/transport-server.d.ts.map +1 -1
  49. package/dist/src/bridge/internal/transport-server.js +5 -1
  50. package/dist/src/bridge/internal/transport-server.js.map +1 -1
  51. package/dist/src/bridge/types.d.ts +43 -4
  52. package/dist/src/bridge/types.d.ts.map +1 -1
  53. package/dist/src/bridge/types.js.map +1 -1
  54. package/dist/src/cli/adapters/cli-command-adapter-handler.test.js +24 -0
  55. package/dist/src/cli/adapters/cli-command-adapter-handler.test.js.map +1 -1
  56. package/dist/src/cli/adapters/cli-command-adapter.d.ts +6 -0
  57. package/dist/src/cli/adapters/cli-command-adapter.d.ts.map +1 -1
  58. package/dist/src/cli/adapters/cli-command-adapter.js +107 -11
  59. package/dist/src/cli/adapters/cli-command-adapter.js.map +1 -1
  60. package/dist/src/cli/adapters/group-builder.d.ts +3 -0
  61. package/dist/src/cli/adapters/group-builder.d.ts.map +1 -1
  62. package/dist/src/cli/adapters/group-builder.js +34 -15
  63. package/dist/src/cli/adapters/group-builder.js.map +1 -1
  64. package/dist/src/cli/adapters/group-builder.test.js +26 -2
  65. package/dist/src/cli/adapters/group-builder.test.js.map +1 -1
  66. package/dist/src/cli/adapters/help-formatter.d.ts.map +1 -1
  67. package/dist/src/cli/adapters/help-formatter.js +4 -3
  68. package/dist/src/cli/adapters/help-formatter.js.map +1 -1
  69. package/dist/src/cli/adapters/help-formatter.test.js +1 -1
  70. package/dist/src/cli/adapters/help-formatter.test.js.map +1 -1
  71. package/dist/src/cli/adapters/playtest-edit-guard.test.d.ts +2 -0
  72. package/dist/src/cli/adapters/playtest-edit-guard.test.d.ts.map +1 -0
  73. package/dist/src/cli/adapters/playtest-edit-guard.test.js +28 -0
  74. package/dist/src/cli/adapters/playtest-edit-guard.test.js.map +1 -0
  75. package/dist/src/cli/args/global-args.d.ts +1 -1
  76. package/dist/src/cli/args/global-args.d.ts.map +1 -1
  77. package/dist/src/cli/cli-output-context.d.ts +14 -0
  78. package/dist/src/cli/cli-output-context.d.ts.map +1 -0
  79. package/dist/src/cli/cli-output-context.js +39 -0
  80. package/dist/src/cli/cli-output-context.js.map +1 -0
  81. package/dist/src/cli/cli-output-context.test.d.ts +2 -0
  82. package/dist/src/cli/cli-output-context.test.d.ts.map +1 -0
  83. package/dist/src/cli/cli-output-context.test.js +38 -0
  84. package/dist/src/cli/cli-output-context.test.js.map +1 -0
  85. package/dist/src/cli/cli.js +40 -17
  86. package/dist/src/cli/cli.js.map +1 -1
  87. package/dist/src/cli/managed-studio-run.d.ts +21 -0
  88. package/dist/src/cli/managed-studio-run.d.ts.map +1 -0
  89. package/dist/src/cli/managed-studio-run.js +75 -0
  90. package/dist/src/cli/managed-studio-run.js.map +1 -0
  91. package/dist/src/cli/managed-studio-run.test.d.ts +2 -0
  92. package/dist/src/cli/managed-studio-run.test.d.ts.map +1 -0
  93. package/dist/src/cli/managed-studio-run.test.js +52 -0
  94. package/dist/src/cli/managed-studio-run.test.js.map +1 -0
  95. package/dist/src/cli/resolve-session.js +1 -1
  96. package/dist/src/cli/resolve-session.js.map +1 -1
  97. package/dist/src/cli/resolve-session.test.js +1 -1
  98. package/dist/src/cli/resolve-session.test.js.map +1 -1
  99. package/dist/src/cli/script-executor.d.ts +13 -1
  100. package/dist/src/cli/script-executor.d.ts.map +1 -1
  101. package/dist/src/cli/script-executor.js +77 -47
  102. package/dist/src/cli/script-executor.js.map +1 -1
  103. package/dist/src/commands/console/exec/exec.d.ts +17 -0
  104. package/dist/src/commands/console/exec/exec.d.ts.map +1 -1
  105. package/dist/src/commands/console/exec/exec.js +39 -7
  106. package/dist/src/commands/console/exec/exec.js.map +1 -1
  107. package/dist/src/commands/console/exec/exec.test.js +62 -3
  108. package/dist/src/commands/console/exec/exec.test.js.map +1 -1
  109. package/dist/src/commands/console/logs/logs.d.ts +30 -2
  110. package/dist/src/commands/console/logs/logs.d.ts.map +1 -1
  111. package/dist/src/commands/console/logs/logs.js +82 -7
  112. package/dist/src/commands/console/logs/logs.js.map +1 -1
  113. package/dist/src/commands/console/logs/logs.test.js +63 -4
  114. package/dist/src/commands/console/logs/logs.test.js.map +1 -1
  115. package/dist/src/commands/explorer/export/export.d.ts +19 -0
  116. package/dist/src/commands/explorer/export/export.d.ts.map +1 -0
  117. package/dist/src/commands/explorer/export/export.js +37 -0
  118. package/dist/src/commands/explorer/export/export.js.map +1 -0
  119. package/dist/src/commands/explorer/export/export.test.d.ts +2 -0
  120. package/dist/src/commands/explorer/export/export.test.d.ts.map +1 -0
  121. package/dist/src/commands/explorer/export/export.test.js +25 -0
  122. package/dist/src/commands/explorer/export/export.test.js.map +1 -0
  123. package/dist/src/commands/explorer/find/find.d.ts +28 -0
  124. package/dist/src/commands/explorer/find/find.d.ts.map +1 -0
  125. package/dist/src/commands/explorer/find/find.js +75 -0
  126. package/dist/src/commands/explorer/find/find.js.map +1 -0
  127. package/dist/src/commands/explorer/find/find.test.d.ts +2 -0
  128. package/dist/src/commands/explorer/find/find.test.d.ts.map +1 -0
  129. package/dist/src/commands/explorer/find/find.test.js +44 -0
  130. package/dist/src/commands/explorer/find/find.test.js.map +1 -0
  131. package/dist/src/commands/explorer/grep/grep.d.ts +24 -0
  132. package/dist/src/commands/explorer/grep/grep.d.ts.map +1 -0
  133. package/dist/src/commands/explorer/grep/grep.js +63 -0
  134. package/dist/src/commands/explorer/grep/grep.js.map +1 -0
  135. package/dist/src/commands/explorer/grep/grep.test.d.ts +2 -0
  136. package/dist/src/commands/explorer/grep/grep.test.d.ts.map +1 -0
  137. package/dist/src/commands/explorer/grep/grep.test.js +42 -0
  138. package/dist/src/commands/explorer/grep/grep.test.js.map +1 -0
  139. package/dist/src/commands/explorer/import/import.d.ts +21 -0
  140. package/dist/src/commands/explorer/import/import.d.ts.map +1 -0
  141. package/dist/src/commands/explorer/import/import.js +37 -0
  142. package/dist/src/commands/explorer/import/import.js.map +1 -0
  143. package/dist/src/commands/explorer/import/import.test.d.ts +2 -0
  144. package/dist/src/commands/explorer/import/import.test.d.ts.map +1 -0
  145. package/dist/src/commands/explorer/import/import.test.js +45 -0
  146. package/dist/src/commands/explorer/import/import.test.js.map +1 -0
  147. package/dist/src/commands/formatters.test.js +7 -1
  148. package/dist/src/commands/formatters.test.js.map +1 -1
  149. package/dist/src/commands/framework/action-loader.d.ts +5 -1
  150. package/dist/src/commands/framework/action-loader.d.ts.map +1 -1
  151. package/dist/src/commands/framework/action-loader.js +9 -2
  152. package/dist/src/commands/framework/action-loader.js.map +1 -1
  153. package/dist/src/commands/framework/action-loader.test.js +23 -0
  154. package/dist/src/commands/framework/action-loader.test.js.map +1 -1
  155. package/dist/src/commands/framework/arg-builder.d.ts +15 -2
  156. package/dist/src/commands/framework/arg-builder.d.ts.map +1 -1
  157. package/dist/src/commands/framework/arg-builder.js +22 -2
  158. package/dist/src/commands/framework/arg-builder.js.map +1 -1
  159. package/dist/src/commands/framework/define-command.d.ts +19 -3
  160. package/dist/src/commands/framework/define-command.d.ts.map +1 -1
  161. package/dist/src/commands/framework/define-command.js.map +1 -1
  162. package/dist/src/commands/framework/index.d.ts +1 -1
  163. package/dist/src/commands/framework/index.d.ts.map +1 -1
  164. package/dist/src/commands/framework/index.js.map +1 -1
  165. package/dist/src/commands/index.d.ts +11 -0
  166. package/dist/src/commands/index.d.ts.map +1 -1
  167. package/dist/src/commands/index.js +11 -0
  168. package/dist/src/commands/index.js.map +1 -1
  169. package/dist/src/commands/linux/inject-credentials/inject-credentials.d.ts.map +1 -1
  170. package/dist/src/commands/linux/inject-credentials/inject-credentials.js +1 -3
  171. package/dist/src/commands/linux/inject-credentials/inject-credentials.js.map +1 -1
  172. package/dist/src/commands/linux/setup/setup.d.ts.map +1 -1
  173. package/dist/src/commands/linux/setup/setup.js +1 -3
  174. package/dist/src/commands/linux/setup/setup.js.map +1 -1
  175. package/dist/src/commands/linux/status/status.d.ts +9 -1
  176. package/dist/src/commands/linux/status/status.d.ts.map +1 -1
  177. package/dist/src/commands/linux/status/status.js +66 -86
  178. package/dist/src/commands/linux/status/status.js.map +1 -1
  179. package/dist/src/commands/playtest/player/add/add.d.ts +17 -0
  180. package/dist/src/commands/playtest/player/add/add.d.ts.map +1 -0
  181. package/dist/src/commands/playtest/player/add/add.js +45 -0
  182. package/dist/src/commands/playtest/player/add/add.js.map +1 -0
  183. package/dist/src/commands/playtest/player/list/list.d.ts +13 -0
  184. package/dist/src/commands/playtest/player/list/list.d.ts.map +1 -0
  185. package/dist/src/commands/playtest/player/list/list.js +33 -0
  186. package/dist/src/commands/playtest/player/list/list.js.map +1 -0
  187. package/dist/src/commands/playtest/player/playtest-players.d.ts +36 -0
  188. package/dist/src/commands/playtest/player/playtest-players.d.ts.map +1 -0
  189. package/dist/src/commands/playtest/player/playtest-players.js +187 -0
  190. package/dist/src/commands/playtest/player/playtest-players.js.map +1 -0
  191. package/dist/src/commands/playtest/player/playtest-players.test.d.ts +2 -0
  192. package/dist/src/commands/playtest/player/playtest-players.test.d.ts.map +1 -0
  193. package/dist/src/commands/playtest/player/playtest-players.test.js +89 -0
  194. package/dist/src/commands/playtest/player/playtest-players.test.js.map +1 -0
  195. package/dist/src/commands/playtest/player/remove/remove.d.ts +17 -0
  196. package/dist/src/commands/playtest/player/remove/remove.d.ts.map +1 -0
  197. package/dist/src/commands/playtest/player/remove/remove.js +42 -0
  198. package/dist/src/commands/playtest/player/remove/remove.js.map +1 -0
  199. package/dist/src/commands/playtest/playtest-sessions.d.ts +49 -0
  200. package/dist/src/commands/playtest/playtest-sessions.d.ts.map +1 -0
  201. package/dist/src/commands/playtest/playtest-sessions.js +283 -0
  202. package/dist/src/commands/playtest/playtest-sessions.js.map +1 -0
  203. package/dist/src/commands/playtest/playtest-sessions.test.d.ts +2 -0
  204. package/dist/src/commands/playtest/playtest-sessions.test.d.ts.map +1 -0
  205. package/dist/src/commands/playtest/playtest-sessions.test.js +87 -0
  206. package/dist/src/commands/playtest/playtest-sessions.test.js.map +1 -0
  207. package/dist/src/commands/playtest/restart/restart.d.ts +23 -0
  208. package/dist/src/commands/playtest/restart/restart.d.ts.map +1 -0
  209. package/dist/src/commands/playtest/restart/restart.js +105 -0
  210. package/dist/src/commands/playtest/restart/restart.js.map +1 -0
  211. package/dist/src/commands/playtest/run/run.d.ts +47 -0
  212. package/dist/src/commands/playtest/run/run.d.ts.map +1 -0
  213. package/dist/src/commands/playtest/run/run.js +137 -0
  214. package/dist/src/commands/playtest/run/run.js.map +1 -0
  215. package/dist/src/commands/playtest/start/start.d.ts +42 -0
  216. package/dist/src/commands/playtest/start/start.d.ts.map +1 -0
  217. package/dist/src/commands/playtest/start/start.js +70 -0
  218. package/dist/src/commands/playtest/start/start.js.map +1 -0
  219. package/dist/src/commands/playtest/stop/stop.d.ts +15 -0
  220. package/dist/src/commands/playtest/stop/stop.d.ts.map +1 -0
  221. package/dist/src/commands/playtest/stop/stop.js +24 -0
  222. package/dist/src/commands/playtest/stop/stop.js.map +1 -0
  223. package/dist/src/commands/process/close/close.d.ts +19 -10
  224. package/dist/src/commands/process/close/close.d.ts.map +1 -1
  225. package/dist/src/commands/process/close/close.js +118 -15
  226. package/dist/src/commands/process/close/close.js.map +1 -1
  227. package/dist/src/commands/process/close/close.test.d.ts +2 -0
  228. package/dist/src/commands/process/close/close.test.d.ts.map +1 -0
  229. package/dist/src/commands/process/close/close.test.js +190 -0
  230. package/dist/src/commands/process/close/close.test.js.map +1 -0
  231. package/dist/src/commands/process/info/info.d.ts +45 -5
  232. package/dist/src/commands/process/info/info.d.ts.map +1 -1
  233. package/dist/src/commands/process/info/info.js +117 -13
  234. package/dist/src/commands/process/info/info.js.map +1 -1
  235. package/dist/src/commands/process/info/info.test.js +140 -38
  236. package/dist/src/commands/process/info/info.test.js.map +1 -1
  237. package/dist/src/commands/process/launch/launch.d.ts +11 -1
  238. package/dist/src/commands/process/launch/launch.d.ts.map +1 -1
  239. package/dist/src/commands/process/launch/launch.js +37 -7
  240. package/dist/src/commands/process/launch/launch.js.map +1 -1
  241. package/dist/src/commands/process/launch/launch.test.js +99 -20
  242. package/dist/src/commands/process/launch/launch.test.js.map +1 -1
  243. package/dist/src/commands/process/list/list.d.ts +2 -1
  244. package/dist/src/commands/process/list/list.d.ts.map +1 -1
  245. package/dist/src/commands/process/list/list.js +9 -6
  246. package/dist/src/commands/process/list/list.js.map +1 -1
  247. package/dist/src/commands/process/list/list.test.js +1 -0
  248. package/dist/src/commands/process/list/list.test.js.map +1 -1
  249. package/dist/src/commands/process/run/run.d.ts +9 -0
  250. package/dist/src/commands/process/run/run.d.ts.map +1 -1
  251. package/dist/src/commands/process/run/run.js +15 -5
  252. package/dist/src/commands/process/run/run.js.map +1 -1
  253. package/dist/src/commands/serve/serve.d.ts +2 -0
  254. package/dist/src/commands/serve/serve.d.ts.map +1 -1
  255. package/dist/src/commands/serve/serve.js +17 -4
  256. package/dist/src/commands/serve/serve.js.map +1 -1
  257. package/dist/src/commands/serve/serve.test.js +9 -2
  258. package/dist/src/commands/serve/serve.test.js.map +1 -1
  259. package/dist/src/commands/viewport/screenshot/screenshot.d.ts.map +1 -1
  260. package/dist/src/commands/viewport/screenshot/screenshot.js +4 -1
  261. package/dist/src/commands/viewport/screenshot/screenshot.js.map +1 -1
  262. package/dist/src/commands/viewport/screenshot/screenshot.test.js +5 -9
  263. package/dist/src/commands/viewport/screenshot/screenshot.test.js.map +1 -1
  264. package/dist/src/deploy/deploy-places.d.ts +39 -0
  265. package/dist/src/deploy/deploy-places.d.ts.map +1 -0
  266. package/dist/src/deploy/deploy-places.js +162 -0
  267. package/dist/src/deploy/deploy-places.js.map +1 -0
  268. package/dist/src/deploy/deploy-places.test.d.ts +2 -0
  269. package/dist/src/deploy/deploy-places.test.d.ts.map +1 -0
  270. package/dist/src/deploy/deploy-places.test.js +96 -0
  271. package/dist/src/deploy/deploy-places.test.js.map +1 -0
  272. package/dist/src/index.d.ts +2 -2
  273. package/dist/src/index.d.ts.map +1 -1
  274. package/dist/src/index.js +1 -1
  275. package/dist/src/index.js.map +1 -1
  276. package/dist/src/plugin/persistent-plugin-installer.d.ts.map +1 -1
  277. package/dist/src/plugin/persistent-plugin-installer.js +2 -1
  278. package/dist/src/plugin/persistent-plugin-installer.js.map +1 -1
  279. package/dist/src/plugin/persistent-plugin-installer.test.js +3 -0
  280. package/dist/src/plugin/persistent-plugin-installer.test.js.map +1 -1
  281. package/dist/src/plugin/plugin-build-version.d.ts +7 -0
  282. package/dist/src/plugin/plugin-build-version.d.ts.map +1 -0
  283. package/dist/src/plugin/plugin-build-version.js +42 -0
  284. package/dist/src/plugin/plugin-build-version.js.map +1 -0
  285. package/dist/src/plugin/plugin-build-version.test.d.ts +2 -0
  286. package/dist/src/plugin/plugin-build-version.test.d.ts.map +1 -0
  287. package/dist/src/plugin/plugin-build-version.test.js +41 -0
  288. package/dist/src/plugin/plugin-build-version.test.js.map +1 -0
  289. package/dist/src/plugin/plugin-injector.d.ts.map +1 -1
  290. package/dist/src/plugin/plugin-injector.js +7 -1
  291. package/dist/src/plugin/plugin-injector.js.map +1 -1
  292. package/dist/src/process/studio-process-closer.d.ts +35 -0
  293. package/dist/src/process/studio-process-closer.d.ts.map +1 -0
  294. package/dist/src/process/studio-process-closer.js +129 -0
  295. package/dist/src/process/studio-process-closer.js.map +1 -0
  296. package/dist/src/process/studio-process-closer.test.d.ts +2 -0
  297. package/dist/src/process/studio-process-closer.test.d.ts.map +1 -0
  298. package/dist/src/process/studio-process-closer.test.js +80 -0
  299. package/dist/src/process/studio-process-closer.test.js.map +1 -0
  300. package/dist/src/process/studio-settings.d.ts +14 -0
  301. package/dist/src/process/studio-settings.d.ts.map +1 -0
  302. package/dist/src/process/studio-settings.js +50 -0
  303. package/dist/src/process/studio-settings.js.map +1 -0
  304. package/dist/src/process/studio-settings.test.d.ts +2 -0
  305. package/dist/src/process/studio-settings.test.d.ts.map +1 -0
  306. package/dist/src/process/studio-settings.test.js +16 -0
  307. package/dist/src/process/studio-settings.test.js.map +1 -0
  308. package/dist/src/process/studio-window-state.d.ts +13 -0
  309. package/dist/src/process/studio-window-state.d.ts.map +1 -0
  310. package/dist/src/process/studio-window-state.js +48 -0
  311. package/dist/src/process/studio-window-state.js.map +1 -0
  312. package/dist/src/server/studio-bridge-server.d.ts +3 -1
  313. package/dist/src/server/studio-bridge-server.d.ts.map +1 -1
  314. package/dist/src/server/studio-bridge-server.js +12 -4
  315. package/dist/src/server/studio-bridge-server.js.map +1 -1
  316. package/dist/src/server/web-socket-protocol.d.ts +173 -6
  317. package/dist/src/server/web-socket-protocol.d.ts.map +1 -1
  318. package/dist/src/server/web-socket-protocol.js +244 -1
  319. package/dist/src/server/web-socket-protocol.js.map +1 -1
  320. package/dist/src/server/web-socket-protocol.test.js +48 -0
  321. package/dist/src/server/web-socket-protocol.test.js.map +1 -1
  322. package/dist/src/test/e2e/persistent-session.test.js +121 -0
  323. package/dist/src/test/e2e/persistent-session.test.js.map +1 -1
  324. package/dist/src/test/e2e/split-server.test.d.ts +3 -5
  325. package/dist/src/test/e2e/split-server.test.d.ts.map +1 -1
  326. package/dist/src/test/e2e/split-server.test.js +3 -5
  327. package/dist/src/test/e2e/split-server.test.js.map +1 -1
  328. package/dist/tsconfig.tsbuildinfo +1 -1
  329. package/docker/Dockerfile +3 -0
  330. package/package.json +8 -6
  331. package/src/bridge/bridge-connection.test.ts +126 -0
  332. package/src/bridge/bridge-connection.ts +217 -22
  333. package/src/bridge/bridge-session.test.ts +6 -4
  334. package/src/bridge/bridge-session.ts +273 -28
  335. package/src/bridge/index.ts +3 -0
  336. package/src/bridge/internal/bridge-client.test.ts +23 -0
  337. package/src/bridge/internal/bridge-client.ts +25 -7
  338. package/src/bridge/internal/bridge-host.test.ts +38 -0
  339. package/src/bridge/internal/bridge-host.ts +47 -0
  340. package/src/bridge/internal/hand-off.ts +4 -1
  341. package/src/bridge/internal/host-protocol.test.ts +15 -0
  342. package/src/bridge/internal/host-protocol.ts +148 -37
  343. package/src/bridge/internal/instance-state.ts +34 -0
  344. package/src/bridge/internal/transport-client.ts +2 -1
  345. package/src/bridge/internal/transport-server.ts +5 -1
  346. package/src/bridge/types.ts +47 -2
  347. package/src/cli/adapters/cli-command-adapter-handler.test.ts +28 -0
  348. package/src/cli/adapters/cli-command-adapter.ts +150 -18
  349. package/src/cli/adapters/group-builder.test.ts +34 -2
  350. package/src/cli/adapters/group-builder.ts +52 -20
  351. package/src/cli/adapters/help-formatter.test.ts +1 -1
  352. package/src/cli/adapters/help-formatter.ts +4 -3
  353. package/src/cli/adapters/playtest-edit-guard.test.ts +54 -0
  354. package/src/cli/args/global-args.ts +1 -1
  355. package/src/cli/cli-output-context.test.ts +55 -0
  356. package/src/cli/cli-output-context.ts +50 -0
  357. package/src/cli/cli.ts +49 -17
  358. package/src/cli/managed-studio-run.test.ts +71 -0
  359. package/src/cli/managed-studio-run.ts +135 -0
  360. package/src/cli/resolve-session.test.ts +1 -1
  361. package/src/cli/resolve-session.ts +1 -1
  362. package/src/cli/script-executor.ts +99 -51
  363. package/src/commands/console/exec/exec.test.ts +96 -3
  364. package/src/commands/console/exec/exec.ts +59 -6
  365. package/src/commands/console/exec/execute.lua +356 -240
  366. package/src/commands/console/exec/execute.spec.lua +286 -0
  367. package/src/commands/console/logs/logs.test.ts +76 -4
  368. package/src/commands/console/logs/logs.ts +132 -17
  369. package/src/commands/console/logs/query-logs.lua +85 -52
  370. package/src/commands/console/logs/query-logs.spec.lua +132 -0
  371. package/src/commands/explorer/export/export-instances.lua +108 -0
  372. package/src/commands/explorer/export/export-instances.spec.lua +87 -0
  373. package/src/commands/explorer/export/export.test.ts +27 -0
  374. package/src/commands/explorer/export/export.ts +60 -0
  375. package/src/commands/explorer/find/find-instances.lua +174 -0
  376. package/src/commands/explorer/find/find-instances.spec.lua +122 -0
  377. package/src/commands/explorer/find/find.test.ts +53 -0
  378. package/src/commands/explorer/find/find.ts +113 -0
  379. package/src/commands/explorer/grep/grep-scripts.lua +214 -0
  380. package/src/commands/explorer/grep/grep-scripts.spec.lua +130 -0
  381. package/src/commands/explorer/grep/grep.test.ts +45 -0
  382. package/src/commands/explorer/grep/grep.ts +93 -0
  383. package/src/commands/explorer/import/import-instances.lua +106 -0
  384. package/src/commands/explorer/import/import-instances.spec.lua +84 -0
  385. package/src/commands/explorer/import/import.test.ts +60 -0
  386. package/src/commands/explorer/import/import.ts +66 -0
  387. package/src/commands/explorer/query/query-data-model.lua +124 -152
  388. package/src/commands/explorer/query/query-data-model.spec.lua +150 -0
  389. package/src/commands/formatters.test.ts +7 -1
  390. package/src/commands/framework/action-loader.test.ts +28 -0
  391. package/src/commands/framework/action-loader.ts +10 -2
  392. package/src/commands/framework/arg-builder.ts +26 -3
  393. package/src/commands/framework/define-command.ts +32 -3
  394. package/src/commands/framework/index.ts +1 -0
  395. package/src/commands/index.ts +50 -0
  396. package/src/commands/linux/inject-credentials/inject-credentials.ts +1 -3
  397. package/src/commands/linux/setup/setup.ts +1 -3
  398. package/src/commands/linux/status/status.ts +105 -98
  399. package/src/commands/playtest/player/add/add.ts +85 -0
  400. package/src/commands/playtest/player/list/list.ts +57 -0
  401. package/src/commands/playtest/player/playtest-players.test.ts +146 -0
  402. package/src/commands/playtest/player/playtest-players.ts +270 -0
  403. package/src/commands/playtest/player/remove/remove.ts +78 -0
  404. package/src/commands/playtest/playtest-sessions.test.ts +143 -0
  405. package/src/commands/playtest/playtest-sessions.ts +417 -0
  406. package/src/commands/playtest/restart/restart.ts +199 -0
  407. package/src/commands/playtest/run/playtest-runner.luau +264 -0
  408. package/src/commands/playtest/run/playtest-runner.spec.lua +71 -0
  409. package/src/commands/playtest/run/run-playtest-script.lua +139 -0
  410. package/src/commands/playtest/run/run-playtest-script.spec.lua +43 -0
  411. package/src/commands/playtest/run/run.ts +239 -0
  412. package/src/commands/playtest/start/start-playtest.lua +108 -0
  413. package/src/commands/playtest/start/start.ts +138 -0
  414. package/src/commands/playtest/stop/end-playtest.lua +47 -0
  415. package/src/commands/playtest/stop/stop.ts +51 -0
  416. package/src/commands/process/close/close.test.ts +250 -0
  417. package/src/commands/process/close/close.ts +183 -17
  418. package/src/commands/process/info/info.test.ts +198 -44
  419. package/src/commands/process/info/info.ts +204 -15
  420. package/src/commands/process/info/query-state.lua +42 -29
  421. package/src/commands/process/info/query-state.spec.lua +39 -0
  422. package/src/commands/process/launch/launch.test.ts +138 -21
  423. package/src/commands/process/launch/launch.ts +69 -9
  424. package/src/commands/process/list/list.test.ts +1 -0
  425. package/src/commands/process/list/list.ts +14 -6
  426. package/src/commands/process/run/run.ts +26 -5
  427. package/src/commands/serve/serve.test.ts +17 -2
  428. package/src/commands/serve/serve.ts +25 -6
  429. package/src/commands/viewport/screenshot/capture-screenshot.lua +175 -182
  430. package/src/commands/viewport/screenshot/capture-screenshot.spec.lua +129 -0
  431. package/src/commands/viewport/screenshot/screenshot.test.ts +7 -10
  432. package/src/commands/viewport/screenshot/screenshot.ts +6 -1
  433. package/src/deploy/deploy-places.test.ts +127 -0
  434. package/src/deploy/deploy-places.ts +229 -0
  435. package/src/index.ts +11 -0
  436. package/src/plugin/persistent-plugin-installer.test.ts +4 -0
  437. package/src/plugin/persistent-plugin-installer.ts +2 -1
  438. package/src/plugin/plugin-build-version.test.ts +55 -0
  439. package/src/plugin/plugin-build-version.ts +53 -0
  440. package/src/plugin/plugin-injector.ts +7 -1
  441. package/src/process/studio-process-closer.test.ts +107 -0
  442. package/src/process/studio-process-closer.ts +187 -0
  443. package/src/process/studio-settings.test.ts +18 -0
  444. package/src/process/studio-settings.ts +61 -0
  445. package/src/process/studio-window-state.ts +65 -0
  446. package/src/server/studio-bridge-server.ts +15 -4
  447. package/src/server/web-socket-protocol.test.ts +55 -0
  448. package/src/server/web-socket-protocol.ts +468 -8
  449. package/src/test/e2e/persistent-session.test.ts +137 -0
  450. package/src/test/e2e/split-server.test.ts +3 -5
  451. package/templates/studio-bridge-plugin/default.project.json +3 -0
  452. package/templates/studio-bridge-plugin/src/Shared/ActionRouter.lua +208 -159
  453. package/templates/studio-bridge-plugin/src/Shared/ActionRouter.spec.lua +418 -0
  454. package/templates/studio-bridge-plugin/src/Shared/BuiltInActions.lua +94 -0
  455. package/templates/studio-bridge-plugin/src/Shared/BuiltInActions.spec.lua +166 -0
  456. package/templates/studio-bridge-plugin/src/Shared/DiscoveryStateMachine.lua +118 -137
  457. package/templates/studio-bridge-plugin/src/Shared/DiscoveryStateMachine.spec.lua +228 -0
  458. package/templates/studio-bridge-plugin/src/Shared/MessageBuffer.lua +80 -109
  459. package/templates/studio-bridge-plugin/src/Shared/MessageBuffer.spec.lua +116 -0
  460. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeConnection.lua +233 -0
  461. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeConnection.spec.lua +239 -0
  462. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeLogUtils.lua +53 -0
  463. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeLogUtils.spec.lua +27 -0
  464. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeSessionUtils.lua +164 -0
  465. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeSessionUtils.spec.lua +159 -0
  466. package/templates/studio-bridge-plugin/src/StudioBridgePlugin.server.lua +266 -368
  467. package/templates/studio-bridge-plugin/src/Vendor/png.lua +8 -2
  468. package/test/default.project.json +41 -0
  469. package/test/jest.config.lua +3 -0
  470. package/test/scripts/Server/ServerMain.server.lua +10 -0
  471. package/tsconfig.json +1 -0
  472. package/templates/studio-bridge-plugin/src/Shared/Protocol.lua +0 -132
  473. package/templates/studio-bridge-plugin-test/test/actions.test.lua +0 -580
  474. package/templates/studio-bridge-plugin-test/test/discovery.test.lua +0 -622
  475. package/templates/studio-bridge-plugin-test/test/execute-handler.test.lua +0 -517
  476. package/templates/studio-bridge-plugin-test/test/hash-lifecycle.test.lua +0 -814
  477. package/templates/studio-bridge-plugin-test/test/integration.test.lua +0 -1020
  478. package/templates/studio-bridge-plugin-test/test/new-actions.test.lua +0 -339
  479. package/templates/studio-bridge-plugin-test/test/plugin-entry.test.lua +0 -493
  480. package/templates/studio-bridge-plugin-test/test/protocol.test.lua +0 -506
  481. package/templates/studio-bridge-plugin-test/test/roblox-mocks.lua +0 -108
  482. package/templates/studio-bridge-plugin-test/test/test-runner.lua +0 -112
package/docker/Dockerfile CHANGED
@@ -62,6 +62,9 @@ RUN mkdir -p /home/studio/.aftman/bin \
62
62
  # --- Build studio-bridge from source (via named build context "workspace") ---
63
63
  COPY --from=workspace --chown=studio:studio package.json pnpm-workspace.yaml pnpm-lock.yaml tsconfig.json /home/studio/build/
64
64
  COPY --from=workspace --chown=studio:studio tools/ /home/studio/build/tools/
65
+ # The Luau test runner studio-bridge's plugin specs depend on
66
+ COPY --from=workspace --chown=studio:studio src/loader/ /home/studio/build/src/loader/
67
+ COPY --from=workspace --chown=studio:studio src/nevermore-test-runner/ /home/studio/build/src/nevermore-test-runner/
65
68
  WORKDIR /home/studio/build
66
69
  RUN pnpm install --frozen-lockfile --filter "@quenty/studio-bridge..." \
67
70
  && pnpm -r --filter "@quenty/studio-bridge..." run build
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/studio-bridge",
3
- "version": "0.21.1",
3
+ "version": "0.22.0",
4
4
  "description": "WebSocket-based bridge for running Luau scripts in Roblox Studio",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -15,15 +15,18 @@
15
15
  "studio-bridge": "./dist/src/cli/cli.js"
16
16
  },
17
17
  "dependencies": {
18
- "@quenty/cli-output-helpers": "1.18.0",
19
- "@quenty/nevermore-cli-helpers": "1.19.0",
20
- "@quenty/nevermore-template-helpers": "1.20.0",
18
+ "@quenty/cli-output-helpers": "1.19.0",
19
+ "@quenty/nevermore-cli-helpers": "1.20.0",
20
+ "@quenty/nevermore-deploy": "1.1.1",
21
+ "@quenty/nevermore-template-helpers": "1.21.0",
21
22
  "execa": "^9.6.1",
22
23
  "ws": "^8.18.0",
23
24
  "yargs": "^18.0.0",
24
25
  "zod": "^4.3.6"
25
26
  },
26
27
  "devDependencies": {
28
+ "@quenty/nevermore-test-runner": "1.6.3",
29
+ "@quentystudios/jest-lua": "3.10.0-quenty.2",
27
30
  "@types/node": "^18.11.4",
28
31
  "@types/ws": "^8.5.13",
29
32
  "@types/yargs": "^17.0.13",
@@ -36,8 +39,7 @@
36
39
  "build:clean": "tsc --build --clean",
37
40
  "test": "vitest run",
38
41
  "test:watch": "vitest",
39
- "test:plugin": "lune run templates/studio-bridge-plugin-test/test/test-runner",
40
42
  "test:integration": "node dist/test/integration/smoke-test.js"
41
43
  },
42
- "gitHead": "e209b95fe15e7490d4d74c32497382d1ef9f74fc"
44
+ "gitHead": "8ccad9d519f633a4af6659664374605cf2df7ecf"
43
45
  }
@@ -25,6 +25,7 @@ async function performRegisterHandshake(
25
25
  placeName?: string;
26
26
  state?: string;
27
27
  context?: string;
28
+ playerName?: string;
28
29
  capabilities?: string[];
29
30
  }
30
31
  ): Promise<{ ws: WebSocket }> {
@@ -39,6 +40,8 @@ async function performRegisterHandshake(
39
40
  instanceId: options?.instanceId ?? 'inst-1',
40
41
  placeName: options?.placeName ?? 'TestPlace',
41
42
  state: options?.state ?? 'Edit',
43
+ context: options?.context,
44
+ playerName: options?.playerName,
42
45
  capabilities: options?.capabilities ?? ['execute', 'queryState'],
43
46
  },
44
47
  })
@@ -656,6 +659,129 @@ describe('BridgeConnection', () => {
656
659
  });
657
660
  });
658
661
 
662
+ describe('target matching', () => {
663
+ async function connectPlaytestAsync(): Promise<BridgeConnection> {
664
+ const conn = await BridgeConnection.connectAsync({
665
+ port: 0,
666
+ keepAlive: true,
667
+ });
668
+ connections.push(conn);
669
+
670
+ const plugins: Array<[string, string, string, string]> = [
671
+ ['test-place-3f9a-edit', 'test-place-3f9a', 'edit', 'test-place.rbxl'],
672
+ ['test-place-3f9a-server', 'test-place-3f9a', 'server', 'Game'],
673
+ ['test-place-3f9a-client', 'test-place-3f9a', 'client', 'Game'],
674
+ ['other-map-77c1-edit', 'other-map-77c1', 'edit', 'Other Map'],
675
+ ];
676
+ for (const [sessionId, instanceId, context, placeName] of plugins) {
677
+ const { ws } = await performRegisterHandshake(conn.port, sessionId, {
678
+ instanceId,
679
+ context,
680
+ placeName,
681
+ });
682
+ openClients.push(ws);
683
+ }
684
+ await new Promise((r) => setTimeout(r, 50));
685
+ return conn;
686
+ }
687
+
688
+ it('resolves an instance id prefix to its edit session', async () => {
689
+ const conn = await connectPlaytestAsync();
690
+
691
+ const session = await conn.resolveSessionAsync('test-place');
692
+ expect(session.info.sessionId).toBe('test-place-3f9a-edit');
693
+ });
694
+
695
+ it('applies --context within the targeted instance', async () => {
696
+ const conn = await connectPlaytestAsync();
697
+
698
+ const session = await conn.resolveSessionAsync(
699
+ 'test-place-3f9a-edit',
700
+ 'server'
701
+ );
702
+ expect(session.info.sessionId).toBe('test-place-3f9a-server');
703
+ });
704
+
705
+ it('refuses a prefix that matches several instances', async () => {
706
+ const conn = await connectPlaytestAsync();
707
+ const { ws } = await performRegisterHandshake(
708
+ conn.port,
709
+ 'test-place-b2c4-edit',
710
+ { instanceId: 'test-place-b2c4', context: 'edit' }
711
+ );
712
+ openClients.push(ws);
713
+ await new Promise((r) => setTimeout(r, 50));
714
+
715
+ await expect(conn.resolveSessionAsync('test-place')).rejects.toThrow(
716
+ /matches several Studio instances/
717
+ );
718
+ await expect(conn.resolveSessionAsync('missing')).rejects.toThrow(
719
+ SessionNotFoundError
720
+ );
721
+ });
722
+
723
+ it('resolves a prefix that matches one session to that session', async () => {
724
+ const conn = await connectPlaytestAsync();
725
+
726
+ const session = await conn.resolveSessionAsync('test-place-3f9a-ser');
727
+ expect(session.info.sessionId).toBe('test-place-3f9a-server');
728
+ });
729
+
730
+ it('refuses --context when a multiplayer test has several clients', async () => {
731
+ const conn = await connectPlaytestAsync();
732
+ const { ws } = await performRegisterHandshake(
733
+ conn.port,
734
+ 'test-place-3f9a-client-player2',
735
+ {
736
+ instanceId: 'test-place-3f9a',
737
+ context: 'client',
738
+ playerName: 'Player2',
739
+ }
740
+ );
741
+ openClients.push(ws);
742
+ await new Promise((r) => setTimeout(r, 50));
743
+
744
+ await expect(
745
+ conn.resolveSessionAsync('test-place', 'client')
746
+ ).rejects.toThrow(/several client sessions/);
747
+ await expect(
748
+ conn.resolveSessionAsync('test-place-3f9a-cl')
749
+ ).rejects.toThrow(/'test-place-3f9a-cl' matches several sessions/);
750
+ const session = await conn.resolveSessionAsync(
751
+ 'test-place-3f9a-client-player2',
752
+ 'client'
753
+ );
754
+ expect(session.info.playerName).toBe('Player2');
755
+ });
756
+
757
+ it('filters listed sessions by target and context', async () => {
758
+ const conn = await connectPlaytestAsync();
759
+
760
+ expect(conn.findSessions('test-place').map((s) => s.sessionId)).toEqual([
761
+ 'test-place-3f9a-edit',
762
+ 'test-place-3f9a-server',
763
+ 'test-place-3f9a-client',
764
+ ]);
765
+ expect(
766
+ conn.findSessions(undefined, 'edit').map((s) => s.sessionId)
767
+ ).toEqual(['test-place-3f9a-edit', 'other-map-77c1-edit']);
768
+ expect(
769
+ conn.findSessions('other-map-77c1-edit').map((s) => s.sessionId)
770
+ ).toEqual(['other-map-77c1-edit']);
771
+ });
772
+
773
+ it('reports a running playtest as the state of its instance', async () => {
774
+ const conn = await connectPlaytestAsync();
775
+
776
+ const byId = new Map(conn.listSessions().map((s) => [s.sessionId, s]));
777
+ expect(byId.get('test-place-3f9a-edit')?.state).toBe('Play');
778
+ expect(byId.get('test-place-3f9a-server')?.placeName).toBe(
779
+ 'test-place.rbxl'
780
+ );
781
+ expect(byId.get('other-map-77c1-edit')?.state).toBe('Edit');
782
+ });
783
+ });
784
+
659
785
  describe('waitForSession', () => {
660
786
  it('resolves immediately when sessions already exist', async () => {
661
787
  const conn = await BridgeConnection.connectAsync({
@@ -18,19 +18,25 @@ import {
18
18
  getDefaultRemoteHost,
19
19
  } from './internal/environment-detection.js';
20
20
  import { BridgeSession } from './bridge-session.js';
21
- import type {
22
- HostProtocolMessage,
23
- ListSessionsRequest,
24
- ListInstancesRequest,
21
+ import {
22
+ HOST_PROTOCOL_VERSION,
23
+ type HostEnvelope,
24
+ type HostProtocolMessage,
25
+ type ListSessionsRequest,
26
+ type ListInstancesRequest,
25
27
  } from './internal/host-protocol.js';
26
28
  import type { SessionInfo, SessionContext, InstanceInfo } from './types.js';
27
29
  import { SessionNotFoundError, ContextNotFoundError } from './types.js';
28
- import type { ServerMessage } from '../server/web-socket-protocol.js';
30
+ import { annotateInstanceState } from './internal/instance-state.js';
31
+ import type {
32
+ PluginMessage,
33
+ ServerMessage,
34
+ } from '../server/web-socket-protocol.js';
29
35
  import { OutputHelper } from '@quenty/cli-output-helpers';
30
36
  export interface BridgeConnectionOptions {
31
37
  /** Port for the bridge host. Default: 38741. */
32
38
  port?: number;
33
- /** Max time to wait for initial connection setup. Default: 30_000ms. */
39
+ /** Max time to wait for the first Studio session to connect. Default: 5_000ms. */
34
40
  timeoutMs?: number;
35
41
  /** Keep the host alive even when idle. Default: false. */
36
42
  keepAlive?: boolean;
@@ -49,6 +55,8 @@ export interface BridgeConnectionOptions {
49
55
  const DEFAULT_PORT = 38741;
50
56
  const DEFAULT_TIMEOUT_MS = 30_000;
51
57
  const IDLE_EXIT_GRACE_MS = 5_000;
58
+ /** How long shutdown waits on relayed actions; a started playtest's request lasts until it ends. */
59
+ const RELAY_DRAIN_TIMEOUT_MS = 10_000;
52
60
 
53
61
  /**
54
62
  * Transport handle that relays messages to a plugin's WebSocket via BridgeHost.
@@ -93,9 +101,12 @@ class HostTransportHandle extends EventEmitter {
93
101
 
94
102
  export class BridgeConnection extends EventEmitter {
95
103
  private _role: 'host' | 'client';
104
+ private _isRemote = false;
96
105
  private _isConnected: boolean = false;
97
106
  private _keepAlive: boolean;
98
107
  private _hasSettled: boolean = false;
108
+ private _firstSessionTimeoutMs: number | undefined;
109
+ private _pendingRelays = new Set<Promise<void>>();
99
110
 
100
111
  // Host mode internals
101
112
  private _host: BridgeHost | undefined;
@@ -105,6 +116,7 @@ export class BridgeConnection extends EventEmitter {
105
116
 
106
117
  // Client mode internals
107
118
  private _client: BridgeClient | undefined;
119
+ private _clientPort = 0;
108
120
 
109
121
  // Idle exit
110
122
  private _idleTimer: ReturnType<typeof setTimeout> | undefined;
@@ -169,6 +181,7 @@ export class BridgeConnection extends EventEmitter {
169
181
  ),
170
182
  ]);
171
183
 
184
+ autoConn._isRemote = true;
172
185
  return autoConn;
173
186
  } catch {
174
187
  console.warn(
@@ -186,6 +199,8 @@ export class BridgeConnection extends EventEmitter {
186
199
  });
187
200
 
188
201
  const conn = new BridgeConnection(detection.role, keepAlive);
202
+ conn._isRemote = parsedRemoteHost !== undefined;
203
+ conn._firstSessionTimeoutMs = options?.timeoutMs;
189
204
  OutputHelper.verbose(
190
205
  `[bridge] Role: ${detection.role}, port: ${detection.port}`
191
206
  );
@@ -225,7 +240,9 @@ export class BridgeConnection extends EventEmitter {
225
240
  return conn;
226
241
  }
227
242
 
228
- async disconnectAsync(): Promise<void> {
243
+ async disconnectAsync(
244
+ options: { relayDrainTimeoutMs?: number } = {}
245
+ ): Promise<void> {
229
246
  if (!this._isConnected) {
230
247
  return;
231
248
  }
@@ -244,6 +261,24 @@ export class BridgeConnection extends EventEmitter {
244
261
  this._isConnected = false;
245
262
 
246
263
  if (this._role === 'host' && this._host) {
264
+ // Shutting down drops the plugins, so let actions relayed for clients finish
265
+ if (this._pendingRelays.size > 0) {
266
+ OutputHelper.verbose(
267
+ `[bridge] Waiting for ${this._pendingRelays.size} relayed action(s) before shutting down`
268
+ );
269
+ let drainTimer: ReturnType<typeof setTimeout> | undefined;
270
+ await Promise.race([
271
+ Promise.allSettled([...this._pendingRelays]),
272
+ new Promise<void>((resolve) => {
273
+ drainTimer = setTimeout(
274
+ resolve,
275
+ options.relayDrainTimeoutMs ?? RELAY_DRAIN_TIMEOUT_MS
276
+ );
277
+ }),
278
+ ]);
279
+ clearTimeout(drainTimer);
280
+ }
281
+
247
282
  for (const handle of this._hostHandles.values()) {
248
283
  handle.markDisconnected();
249
284
  }
@@ -270,27 +305,64 @@ export class BridgeConnection extends EventEmitter {
270
305
  return this._isConnected;
271
306
  }
272
307
 
308
+ /**
309
+ * True when this is a client relaying through a host from an older
310
+ * studio-bridge, which may not pass on options or actions it predates.
311
+ */
312
+ get isHostOutdated(): boolean {
313
+ return (
314
+ this._role === 'client' &&
315
+ (this._client?.hostProtocolVersion ?? 1) < HOST_PROTOCOL_VERSION
316
+ );
317
+ }
318
+
273
319
  /** The actual port the bridge is bound to (host) or connected to (client). */
320
+ /** Whether the bridge host, and so Studio, runs on another machine. */
321
+ get isRemote(): boolean {
322
+ return this._isRemote;
323
+ }
324
+
274
325
  get port(): number {
275
- if (this._role === 'host' && this._host) {
326
+ if (this._host) {
276
327
  return this._host.port;
277
328
  }
278
- return 0;
329
+ return this._clientPort;
279
330
  }
280
331
 
281
332
  /** List all currently connected Studio sessions. */
282
333
  listSessions(): SessionInfo[] {
283
334
  if (this._role === 'host' && this._tracker) {
284
- return this._tracker.listSessions();
335
+ return annotateInstanceState(this._tracker.listSessions());
285
336
  }
286
337
 
287
338
  if (this._role === 'client' && this._client) {
288
- return this._client.listSessions();
339
+ return annotateInstanceState(this._client.listSessions());
289
340
  }
290
341
 
291
342
  return [];
292
343
  }
293
344
 
345
+ /**
346
+ * Sessions matching `--target` and `--context`. A target matches a
347
+ * session id or an instance id exactly, or else as a prefix.
348
+ */
349
+ findSessions(target?: string, context?: SessionContext): SessionInfo[] {
350
+ let sessions = this.listSessions();
351
+ if (target) {
352
+ const exact = sessions.find((s) => s.sessionId === target);
353
+ if (exact && !context) return [exact];
354
+
355
+ const instanceIds = exact
356
+ ? [exact.instanceId]
357
+ : this._matchInstanceIds(target);
358
+ sessions = sessions.filter((s) => instanceIds.includes(s.instanceId));
359
+ }
360
+ if (context) {
361
+ sessions = sessions.filter((s) => s.context === context);
362
+ }
363
+ return sessions;
364
+ }
365
+
294
366
  /**
295
367
  * List unique Studio instances. Each instance groups 1-3 context sessions
296
368
  * that share the same instanceId.
@@ -320,6 +392,31 @@ export class BridgeConnection extends EventEmitter {
320
392
  return undefined;
321
393
  }
322
394
 
395
+ /**
396
+ * Instances whose id, or one of whose session ids, is `target`. Falls
397
+ * back to ids that start with `target` when nothing matches exactly.
398
+ */
399
+ private _matchInstanceIds(target: string): string[] {
400
+ const sessions = this.listSessions();
401
+ const exact = new Set(
402
+ sessions
403
+ .filter((s) => s.instanceId === target || s.sessionId === target)
404
+ .map((s) => s.instanceId)
405
+ );
406
+ if (exact.size > 0) return [...exact];
407
+
408
+ return [
409
+ ...new Set(
410
+ sessions
411
+ .filter(
412
+ (s) =>
413
+ s.instanceId.startsWith(target) || s.sessionId.startsWith(target)
414
+ )
415
+ .map((s) => s.instanceId)
416
+ ),
417
+ ];
418
+ }
419
+
323
420
  /**
324
421
  * Resolve a session for command execution. Instance-aware: groups sessions
325
422
  * by instanceId and auto-selects context within an instance.
@@ -345,13 +442,50 @@ export class BridgeConnection extends EventEmitter {
345
442
 
346
443
  if (sessionId) {
347
444
  const session = this.getSession(sessionId);
348
- if (session) {
445
+ if (session && (!context || session.info.context === context)) {
349
446
  return session;
350
447
  }
351
- throw new SessionNotFoundError(
352
- `Session '${sessionId}' not found`,
353
- sessionId
448
+
449
+ const prefixed = this.listSessions().filter(
450
+ (s) =>
451
+ s.sessionId.startsWith(sessionId) &&
452
+ (!context || s.context === context)
453
+ );
454
+ const onlyPrefixed =
455
+ prefixed.length === 1 ? this.getSession(prefixed[0].sessionId) : null;
456
+ if (onlyPrefixed) {
457
+ return onlyPrefixed;
458
+ }
459
+ const namesInstance = prefixed.some((s) =>
460
+ s.instanceId.startsWith(sessionId)
354
461
  );
462
+ if (!session && prefixed.length > 1 && !namesInstance) {
463
+ throw new SessionNotFoundError(
464
+ `'${sessionId}' matches several sessions. Use a longer --target:\n${prefixed
465
+ .map((s) => ` - ${s.sessionId}`)
466
+ .join('\n')}`,
467
+ sessionId
468
+ );
469
+ }
470
+
471
+ const instanceIds = session
472
+ ? [session.info.instanceId]
473
+ : this._matchInstanceIds(sessionId);
474
+ if (instanceIds.length === 0) {
475
+ throw new SessionNotFoundError(
476
+ `Session '${sessionId}' not found`,
477
+ sessionId
478
+ );
479
+ }
480
+ if (instanceIds.length > 1) {
481
+ throw new SessionNotFoundError(
482
+ `'${sessionId}' matches several Studio instances: ${instanceIds.join(
483
+ ', '
484
+ )}. Use a longer --target.`,
485
+ sessionId
486
+ );
487
+ }
488
+ return this._resolveByInstance(instanceIds[0], context);
355
489
  }
356
490
 
357
491
  if (instanceId) {
@@ -368,13 +502,13 @@ export class BridgeConnection extends EventEmitter {
368
502
  return this._resolveByInstance(instances[0].instanceId, context);
369
503
  }
370
504
 
371
- // Multiple instances: list session IDs so the user can copy one into --session
505
+ // Multiple instances: list session IDs so the user can copy one into --target
372
506
  const sessionList = this.listSessions()
373
507
  .map((s) => ` - ${s.sessionId}: ${s.placeName} (${s.context})`)
374
508
  .join('\n');
375
509
 
376
510
  throw new SessionNotFoundError(
377
- `Multiple Studio instances connected. Use --session <id> to select one.\n${sessionList}`
511
+ `Multiple Studio instances connected. Use --target <id> to select one.\n${sessionList}`
378
512
  );
379
513
  }
380
514
 
@@ -429,7 +563,9 @@ export class BridgeConnection extends EventEmitter {
429
563
  OutputHelper.verbose(
430
564
  '[bridge] No sessions yet — waiting for plugins to discover us'
431
565
  );
432
- await this.waitForSessionsToSettleAsync();
566
+ await this.waitForSessionsToSettleAsync({
567
+ firstSessionTimeout: this._firstSessionTimeoutMs,
568
+ });
433
569
  this._hasSettled = true;
434
570
  const settled = this.listSessions();
435
571
  OutputHelper.verbose(
@@ -461,7 +597,7 @@ export class BridgeConnection extends EventEmitter {
461
597
 
462
598
  const firstTimeout = options?.firstSessionTimeout ?? 5_000;
463
599
  const settleMs = options?.settleMs ?? 4_000;
464
- const maxMs = options?.maxMs ?? 15_000;
600
+ const maxMs = options?.maxMs ?? Math.max(15_000, firstTimeout + settleMs);
465
601
 
466
602
  // Wait for the first session
467
603
  try {
@@ -538,7 +674,7 @@ export class BridgeConnection extends EventEmitter {
538
674
 
539
675
  this._host.on('plugin-connected', (info) => {
540
676
  const state = info.state ?? 'Edit';
541
- const context = BridgeConnection._deriveContext(state);
677
+ const context = info.context ?? BridgeConnection._deriveContext(state);
542
678
 
543
679
  const sessionInfo: SessionInfo = {
544
680
  sessionId: info.sessionId,
@@ -548,7 +684,9 @@ export class BridgeConnection extends EventEmitter {
548
684
  pluginVersion: info.pluginVersion ?? '',
549
685
  capabilities: info.capabilities,
550
686
  connectedAt: new Date(),
551
- origin: 'user',
687
+ origin: info.launchId ? 'managed' : 'user',
688
+ launchId: info.launchId,
689
+ playerName: info.playerName,
552
690
  context,
553
691
  instanceId: info.instanceId ?? info.sessionId,
554
692
  placeId: 0,
@@ -578,6 +716,7 @@ export class BridgeConnection extends EventEmitter {
578
716
  type: 'list-sessions-response',
579
717
  requestId: req.requestId,
580
718
  sessions: this._tracker!.listSessions(),
719
+ protocolVersion: HOST_PROTOCOL_VERSION,
581
720
  });
582
721
  } else if (msg.type === 'list-instances') {
583
722
  const req = msg as ListInstancesRequest;
@@ -586,6 +725,10 @@ export class BridgeConnection extends EventEmitter {
586
725
  requestId: req.requestId,
587
726
  instances: this._tracker!.listInstances(),
588
727
  });
728
+ } else if (msg.type === 'host-envelope') {
729
+ const relay = this._relayEnvelopeAsync(msg as HostEnvelope, reply);
730
+ this._pendingRelays.add(relay);
731
+ void relay.finally(() => this._pendingRelays.delete(relay));
589
732
  }
590
733
  }
591
734
  );
@@ -597,6 +740,49 @@ export class BridgeConnection extends EventEmitter {
597
740
  // when sessions are removed (going from >0 to 0).
598
741
  }
599
742
 
743
+ private async _relayEnvelopeAsync(
744
+ envelope: HostEnvelope,
745
+ reply: (m: HostProtocolMessage) => void
746
+ ): Promise<void> {
747
+ const sessionId = envelope.targetSessionId;
748
+ const handle = this._hostHandles.get(sessionId);
749
+
750
+ if (envelope.timeoutMs === undefined) {
751
+ handle?.sendMessage(envelope.action);
752
+ return;
753
+ }
754
+
755
+ let result: PluginMessage;
756
+ if (!handle) {
757
+ result = {
758
+ type: 'error',
759
+ sessionId,
760
+ payload: {
761
+ code: 'SESSION_MISMATCH',
762
+ message: `Session '${sessionId}' is not connected to this host`,
763
+ },
764
+ };
765
+ } else {
766
+ try {
767
+ result = await handle.sendActionAsync<PluginMessage>(
768
+ envelope.action,
769
+ envelope.timeoutMs
770
+ );
771
+ } catch (err) {
772
+ result = {
773
+ type: 'error',
774
+ sessionId,
775
+ payload: {
776
+ code: 'INTERNAL_ERROR',
777
+ message: err instanceof Error ? err.message : String(err),
778
+ },
779
+ };
780
+ }
781
+ }
782
+
783
+ reply({ type: 'host-response', requestId: envelope.requestId, result });
784
+ }
785
+
600
786
  private async _initClientAsync(
601
787
  port: number,
602
788
  remoteHost?: string
@@ -621,6 +807,7 @@ export class BridgeConnection extends EventEmitter {
621
807
 
622
808
  const host = remoteHost ? remoteHost.split(':')[0] : undefined;
623
809
  await this._client.connectAsync(port, host);
810
+ this._clientPort = port;
624
811
  this._isConnected = true;
625
812
  }
626
813
 
@@ -646,7 +833,15 @@ export class BridgeConnection extends EventEmitter {
646
833
 
647
834
  // 5a: Context specified
648
835
  if (context) {
649
- const match = sessions.find((s) => s.context === context);
836
+ const matches = sessions.filter((s) => s.context === context);
837
+ if (matches.length > 1) {
838
+ throw new SessionNotFoundError(
839
+ `Instance '${instanceId}' has several ${context} sessions. Use --target <session id> to pick one:\n${matches
840
+ .map((s) => ` - ${s.sessionId}`)
841
+ .join('\n')}`
842
+ );
843
+ }
844
+ const match = matches[0];
650
845
  if (match) {
651
846
  const session = this.getSession(match.sessionId);
652
847
  if (session) {
@@ -140,12 +140,13 @@ describe('BridgeSession', () => {
140
140
  const [msg, timeout] = handle.sendActionAsync.mock.calls[0];
141
141
  expect((msg as ServerMessage).type).toBe('execute');
142
142
  expect((msg as any).payload.script).toBe('print("hello")');
143
- expect(timeout).toBe(120_000);
143
+ expect((msg as any).payload.timeoutMs).toBe(120_000);
144
+ expect(timeout).toBe(125_000);
144
145
 
145
146
  expect(result.success).toBe(true);
146
147
  });
147
148
 
148
- it('uses custom timeout when provided', async () => {
149
+ it('has the plugin cancel the script at a custom timeout, then waits for its reply', async () => {
149
150
  const handle = new MockTransportHandle();
150
151
  handle.sendActionAsync.mockResolvedValueOnce({
151
152
  type: 'scriptComplete',
@@ -156,8 +157,9 @@ describe('BridgeSession', () => {
156
157
  const session = new BridgeSession(createSessionInfo(), handle);
157
158
  await session.execAsync('print("hello")', 5_000);
158
159
 
159
- const [, timeout] = handle.sendActionAsync.mock.calls[0];
160
- expect(timeout).toBe(5_000);
160
+ const [msg, timeout] = handle.sendActionAsync.mock.calls[0];
161
+ expect((msg as any).payload.timeoutMs).toBe(5_000);
162
+ expect(timeout).toBe(10_000);
161
163
  });
162
164
 
163
165
  it('returns error info from error response', async () => {