@quenty/studio-bridge 0.21.0 → 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 (491) hide show
  1. package/CHANGELOG.md +12 -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/linux/linux-studio-launcher.d.ts.map +1 -1
  277. package/dist/src/linux/linux-studio-launcher.js +10 -0
  278. package/dist/src/linux/linux-studio-launcher.js.map +1 -1
  279. package/dist/src/linux/linux-studio-launcher.test.d.ts +2 -0
  280. package/dist/src/linux/linux-studio-launcher.test.d.ts.map +1 -0
  281. package/dist/src/linux/linux-studio-launcher.test.js +68 -0
  282. package/dist/src/linux/linux-studio-launcher.test.js.map +1 -0
  283. package/dist/src/plugin/persistent-plugin-installer.d.ts.map +1 -1
  284. package/dist/src/plugin/persistent-plugin-installer.js +2 -1
  285. package/dist/src/plugin/persistent-plugin-installer.js.map +1 -1
  286. package/dist/src/plugin/persistent-plugin-installer.test.js +3 -0
  287. package/dist/src/plugin/persistent-plugin-installer.test.js.map +1 -1
  288. package/dist/src/plugin/plugin-build-version.d.ts +7 -0
  289. package/dist/src/plugin/plugin-build-version.d.ts.map +1 -0
  290. package/dist/src/plugin/plugin-build-version.js +42 -0
  291. package/dist/src/plugin/plugin-build-version.js.map +1 -0
  292. package/dist/src/plugin/plugin-build-version.test.d.ts +2 -0
  293. package/dist/src/plugin/plugin-build-version.test.d.ts.map +1 -0
  294. package/dist/src/plugin/plugin-build-version.test.js +41 -0
  295. package/dist/src/plugin/plugin-build-version.test.js.map +1 -0
  296. package/dist/src/plugin/plugin-injector.d.ts.map +1 -1
  297. package/dist/src/plugin/plugin-injector.js +7 -1
  298. package/dist/src/plugin/plugin-injector.js.map +1 -1
  299. package/dist/src/process/studio-process-closer.d.ts +35 -0
  300. package/dist/src/process/studio-process-closer.d.ts.map +1 -0
  301. package/dist/src/process/studio-process-closer.js +129 -0
  302. package/dist/src/process/studio-process-closer.js.map +1 -0
  303. package/dist/src/process/studio-process-closer.test.d.ts +2 -0
  304. package/dist/src/process/studio-process-closer.test.d.ts.map +1 -0
  305. package/dist/src/process/studio-process-closer.test.js +80 -0
  306. package/dist/src/process/studio-process-closer.test.js.map +1 -0
  307. package/dist/src/process/studio-settings.d.ts +14 -0
  308. package/dist/src/process/studio-settings.d.ts.map +1 -0
  309. package/dist/src/process/studio-settings.js +50 -0
  310. package/dist/src/process/studio-settings.js.map +1 -0
  311. package/dist/src/process/studio-settings.test.d.ts +2 -0
  312. package/dist/src/process/studio-settings.test.d.ts.map +1 -0
  313. package/dist/src/process/studio-settings.test.js +16 -0
  314. package/dist/src/process/studio-settings.test.js.map +1 -0
  315. package/dist/src/process/studio-window-state.d.ts +13 -0
  316. package/dist/src/process/studio-window-state.d.ts.map +1 -0
  317. package/dist/src/process/studio-window-state.js +48 -0
  318. package/dist/src/process/studio-window-state.js.map +1 -0
  319. package/dist/src/server/studio-bridge-server.d.ts +3 -1
  320. package/dist/src/server/studio-bridge-server.d.ts.map +1 -1
  321. package/dist/src/server/studio-bridge-server.js +12 -4
  322. package/dist/src/server/studio-bridge-server.js.map +1 -1
  323. package/dist/src/server/web-socket-protocol.d.ts +173 -6
  324. package/dist/src/server/web-socket-protocol.d.ts.map +1 -1
  325. package/dist/src/server/web-socket-protocol.js +244 -1
  326. package/dist/src/server/web-socket-protocol.js.map +1 -1
  327. package/dist/src/server/web-socket-protocol.test.js +48 -0
  328. package/dist/src/server/web-socket-protocol.test.js.map +1 -1
  329. package/dist/src/test/e2e/persistent-session.test.js +121 -0
  330. package/dist/src/test/e2e/persistent-session.test.js.map +1 -1
  331. package/dist/src/test/e2e/split-server.test.d.ts +3 -5
  332. package/dist/src/test/e2e/split-server.test.d.ts.map +1 -1
  333. package/dist/src/test/e2e/split-server.test.js +3 -5
  334. package/dist/src/test/e2e/split-server.test.js.map +1 -1
  335. package/dist/tsconfig.tsbuildinfo +1 -1
  336. package/docker/Dockerfile +3 -0
  337. package/package.json +8 -6
  338. package/src/bridge/bridge-connection.test.ts +126 -0
  339. package/src/bridge/bridge-connection.ts +217 -22
  340. package/src/bridge/bridge-session.test.ts +6 -4
  341. package/src/bridge/bridge-session.ts +273 -28
  342. package/src/bridge/index.ts +3 -0
  343. package/src/bridge/internal/bridge-client.test.ts +23 -0
  344. package/src/bridge/internal/bridge-client.ts +25 -7
  345. package/src/bridge/internal/bridge-host.test.ts +38 -0
  346. package/src/bridge/internal/bridge-host.ts +47 -0
  347. package/src/bridge/internal/hand-off.ts +4 -1
  348. package/src/bridge/internal/host-protocol.test.ts +15 -0
  349. package/src/bridge/internal/host-protocol.ts +148 -37
  350. package/src/bridge/internal/instance-state.ts +34 -0
  351. package/src/bridge/internal/transport-client.ts +2 -1
  352. package/src/bridge/internal/transport-server.ts +5 -1
  353. package/src/bridge/types.ts +47 -2
  354. package/src/cli/adapters/cli-command-adapter-handler.test.ts +28 -0
  355. package/src/cli/adapters/cli-command-adapter.ts +150 -18
  356. package/src/cli/adapters/group-builder.test.ts +34 -2
  357. package/src/cli/adapters/group-builder.ts +52 -20
  358. package/src/cli/adapters/help-formatter.test.ts +1 -1
  359. package/src/cli/adapters/help-formatter.ts +4 -3
  360. package/src/cli/adapters/playtest-edit-guard.test.ts +54 -0
  361. package/src/cli/args/global-args.ts +1 -1
  362. package/src/cli/cli-output-context.test.ts +55 -0
  363. package/src/cli/cli-output-context.ts +50 -0
  364. package/src/cli/cli.ts +49 -17
  365. package/src/cli/managed-studio-run.test.ts +71 -0
  366. package/src/cli/managed-studio-run.ts +135 -0
  367. package/src/cli/resolve-session.test.ts +1 -1
  368. package/src/cli/resolve-session.ts +1 -1
  369. package/src/cli/script-executor.ts +99 -51
  370. package/src/commands/console/exec/exec.test.ts +96 -3
  371. package/src/commands/console/exec/exec.ts +59 -6
  372. package/src/commands/console/exec/execute.lua +356 -240
  373. package/src/commands/console/exec/execute.spec.lua +286 -0
  374. package/src/commands/console/logs/logs.test.ts +76 -4
  375. package/src/commands/console/logs/logs.ts +132 -17
  376. package/src/commands/console/logs/query-logs.lua +85 -52
  377. package/src/commands/console/logs/query-logs.spec.lua +132 -0
  378. package/src/commands/explorer/export/export-instances.lua +108 -0
  379. package/src/commands/explorer/export/export-instances.spec.lua +87 -0
  380. package/src/commands/explorer/export/export.test.ts +27 -0
  381. package/src/commands/explorer/export/export.ts +60 -0
  382. package/src/commands/explorer/find/find-instances.lua +174 -0
  383. package/src/commands/explorer/find/find-instances.spec.lua +122 -0
  384. package/src/commands/explorer/find/find.test.ts +53 -0
  385. package/src/commands/explorer/find/find.ts +113 -0
  386. package/src/commands/explorer/grep/grep-scripts.lua +214 -0
  387. package/src/commands/explorer/grep/grep-scripts.spec.lua +130 -0
  388. package/src/commands/explorer/grep/grep.test.ts +45 -0
  389. package/src/commands/explorer/grep/grep.ts +93 -0
  390. package/src/commands/explorer/import/import-instances.lua +106 -0
  391. package/src/commands/explorer/import/import-instances.spec.lua +84 -0
  392. package/src/commands/explorer/import/import.test.ts +60 -0
  393. package/src/commands/explorer/import/import.ts +66 -0
  394. package/src/commands/explorer/query/query-data-model.lua +124 -152
  395. package/src/commands/explorer/query/query-data-model.spec.lua +150 -0
  396. package/src/commands/formatters.test.ts +7 -1
  397. package/src/commands/framework/action-loader.test.ts +28 -0
  398. package/src/commands/framework/action-loader.ts +10 -2
  399. package/src/commands/framework/arg-builder.ts +26 -3
  400. package/src/commands/framework/define-command.ts +32 -3
  401. package/src/commands/framework/index.ts +1 -0
  402. package/src/commands/index.ts +50 -0
  403. package/src/commands/linux/inject-credentials/inject-credentials.ts +1 -3
  404. package/src/commands/linux/setup/setup.ts +1 -3
  405. package/src/commands/linux/status/status.ts +105 -98
  406. package/src/commands/playtest/player/add/add.ts +85 -0
  407. package/src/commands/playtest/player/list/list.ts +57 -0
  408. package/src/commands/playtest/player/playtest-players.test.ts +146 -0
  409. package/src/commands/playtest/player/playtest-players.ts +270 -0
  410. package/src/commands/playtest/player/remove/remove.ts +78 -0
  411. package/src/commands/playtest/playtest-sessions.test.ts +143 -0
  412. package/src/commands/playtest/playtest-sessions.ts +417 -0
  413. package/src/commands/playtest/restart/restart.ts +199 -0
  414. package/src/commands/playtest/run/playtest-runner.luau +264 -0
  415. package/src/commands/playtest/run/playtest-runner.spec.lua +71 -0
  416. package/src/commands/playtest/run/run-playtest-script.lua +139 -0
  417. package/src/commands/playtest/run/run-playtest-script.spec.lua +43 -0
  418. package/src/commands/playtest/run/run.ts +239 -0
  419. package/src/commands/playtest/start/start-playtest.lua +108 -0
  420. package/src/commands/playtest/start/start.ts +138 -0
  421. package/src/commands/playtest/stop/end-playtest.lua +47 -0
  422. package/src/commands/playtest/stop/stop.ts +51 -0
  423. package/src/commands/process/close/close.test.ts +250 -0
  424. package/src/commands/process/close/close.ts +183 -17
  425. package/src/commands/process/info/info.test.ts +198 -44
  426. package/src/commands/process/info/info.ts +204 -15
  427. package/src/commands/process/info/query-state.lua +42 -29
  428. package/src/commands/process/info/query-state.spec.lua +39 -0
  429. package/src/commands/process/launch/launch.test.ts +138 -21
  430. package/src/commands/process/launch/launch.ts +69 -9
  431. package/src/commands/process/list/list.test.ts +1 -0
  432. package/src/commands/process/list/list.ts +14 -6
  433. package/src/commands/process/run/run.ts +26 -5
  434. package/src/commands/serve/serve.test.ts +17 -2
  435. package/src/commands/serve/serve.ts +25 -6
  436. package/src/commands/viewport/screenshot/capture-screenshot.lua +175 -182
  437. package/src/commands/viewport/screenshot/capture-screenshot.spec.lua +129 -0
  438. package/src/commands/viewport/screenshot/screenshot.test.ts +7 -10
  439. package/src/commands/viewport/screenshot/screenshot.ts +6 -1
  440. package/src/deploy/deploy-places.test.ts +127 -0
  441. package/src/deploy/deploy-places.ts +229 -0
  442. package/src/index.ts +11 -0
  443. package/src/linux/linux-studio-launcher.test.ts +89 -0
  444. package/src/linux/linux-studio-launcher.ts +11 -0
  445. package/src/plugin/persistent-plugin-installer.test.ts +4 -0
  446. package/src/plugin/persistent-plugin-installer.ts +2 -1
  447. package/src/plugin/plugin-build-version.test.ts +55 -0
  448. package/src/plugin/plugin-build-version.ts +53 -0
  449. package/src/plugin/plugin-injector.ts +7 -1
  450. package/src/process/studio-process-closer.test.ts +107 -0
  451. package/src/process/studio-process-closer.ts +187 -0
  452. package/src/process/studio-settings.test.ts +18 -0
  453. package/src/process/studio-settings.ts +61 -0
  454. package/src/process/studio-window-state.ts +65 -0
  455. package/src/server/studio-bridge-server.ts +15 -4
  456. package/src/server/web-socket-protocol.test.ts +55 -0
  457. package/src/server/web-socket-protocol.ts +468 -8
  458. package/src/test/e2e/persistent-session.test.ts +137 -0
  459. package/src/test/e2e/split-server.test.ts +3 -5
  460. package/templates/studio-bridge-plugin/default.project.json +3 -0
  461. package/templates/studio-bridge-plugin/src/Shared/ActionRouter.lua +208 -159
  462. package/templates/studio-bridge-plugin/src/Shared/ActionRouter.spec.lua +418 -0
  463. package/templates/studio-bridge-plugin/src/Shared/BuiltInActions.lua +94 -0
  464. package/templates/studio-bridge-plugin/src/Shared/BuiltInActions.spec.lua +166 -0
  465. package/templates/studio-bridge-plugin/src/Shared/DiscoveryStateMachine.lua +118 -137
  466. package/templates/studio-bridge-plugin/src/Shared/DiscoveryStateMachine.spec.lua +228 -0
  467. package/templates/studio-bridge-plugin/src/Shared/MessageBuffer.lua +80 -109
  468. package/templates/studio-bridge-plugin/src/Shared/MessageBuffer.spec.lua +116 -0
  469. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeConnection.lua +233 -0
  470. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeConnection.spec.lua +239 -0
  471. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeLogUtils.lua +53 -0
  472. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeLogUtils.spec.lua +27 -0
  473. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeSessionUtils.lua +164 -0
  474. package/templates/studio-bridge-plugin/src/Shared/StudioBridgeSessionUtils.spec.lua +159 -0
  475. package/templates/studio-bridge-plugin/src/StudioBridgePlugin.server.lua +266 -368
  476. package/templates/studio-bridge-plugin/src/Vendor/png.lua +8 -2
  477. package/test/default.project.json +41 -0
  478. package/test/jest.config.lua +3 -0
  479. package/test/scripts/Server/ServerMain.server.lua +10 -0
  480. package/tsconfig.json +1 -0
  481. package/templates/studio-bridge-plugin/src/Shared/Protocol.lua +0 -132
  482. package/templates/studio-bridge-plugin-test/test/actions.test.lua +0 -580
  483. package/templates/studio-bridge-plugin-test/test/discovery.test.lua +0 -622
  484. package/templates/studio-bridge-plugin-test/test/execute-handler.test.lua +0 -517
  485. package/templates/studio-bridge-plugin-test/test/hash-lifecycle.test.lua +0 -814
  486. package/templates/studio-bridge-plugin-test/test/integration.test.lua +0 -1020
  487. package/templates/studio-bridge-plugin-test/test/new-actions.test.lua +0 -339
  488. package/templates/studio-bridge-plugin-test/test/plugin-entry.test.lua +0 -493
  489. package/templates/studio-bridge-plugin-test/test/protocol.test.lua +0 -506
  490. package/templates/studio-bridge-plugin-test/test/roblox-mocks.lua +0 -108
  491. package/templates/studio-bridge-plugin-test/test/test-runner.lua +0 -112
@@ -19,13 +19,22 @@ import type {
19
19
  ScreenshotResult,
20
20
  LogsResult,
21
21
  DataModelResult,
22
+ ExportInstancesResult,
23
+ ImportInstancesResult,
22
24
  LogOptions,
23
25
  QueryDataModelOptions,
26
+ PlaytestMode,
27
+ PlaytestEndResult,
28
+ EndPlaytestResult,
24
29
  } from './types.js';
25
30
  import { SessionDisconnectedError } from './types.js';
26
31
  import type {
27
32
  PluginMessage,
28
33
  OutputLevel,
34
+ FindInstancesMessage,
35
+ FindInstancesResultMessage,
36
+ GrepScriptsMessage,
37
+ GrepScriptsResultMessage,
29
38
  } from '../server/web-socket-protocol.js';
30
39
  import {
31
40
  loadActionSourcesAsync,
@@ -33,14 +42,64 @@ import {
33
42
  } from '../commands/framework/action-loader.js';
34
43
  import { OutputHelper } from '@quenty/cli-output-helpers';
35
44
 
45
+ const EXECUTE_REPLY_GRACE_MS = 5_000;
46
+
36
47
  const DEFAULT_TIMEOUTS: Record<string, number> = {
37
48
  execute: 120_000,
38
49
  queryState: 5_000,
39
50
  captureScreenshot: 30_000,
40
51
  queryDataModel: 10_000,
52
+ exportInstances: 60_000,
53
+ importInstances: 60_000,
54
+ findInstances: 60_000,
55
+ grepScripts: 60_000,
41
56
  queryLogs: 10_000,
57
+ endPlaytest: 5_000,
42
58
  };
43
59
 
60
+ /** Time a playtest gets to start and stop on top of its script's own timeout. */
61
+ const PLAYTEST_STARTUP_ALLOWANCE_MS = 60_000;
62
+
63
+ export interface RunPlaytestScriptOptions {
64
+ mode: PlaytestMode;
65
+ script: string;
66
+ runnerSource: string;
67
+ timeoutSec: number;
68
+ /** Passed to the script as `...`. */
69
+ args?: string[];
70
+ }
71
+
72
+ /** Map an `execute`-style reply onto an ExecResult. */
73
+ function toExecResult(result: PluginMessage): ExecResult {
74
+ if (result.type === 'scriptComplete') {
75
+ const output = (result.payload.output ?? []).map((entry) => ({
76
+ level: entry.level as OutputLevel,
77
+ body: entry.body,
78
+ }));
79
+ return {
80
+ success: result.payload.success,
81
+ output,
82
+ error: result.payload.error,
83
+ traceback: result.payload.traceback,
84
+ returnValues: result.payload.returnValues,
85
+ };
86
+ }
87
+
88
+ if (result.type === 'error') {
89
+ return {
90
+ success: false,
91
+ output: [],
92
+ error: result.payload?.message ?? 'Unknown plugin error',
93
+ };
94
+ }
95
+
96
+ return {
97
+ success: false,
98
+ output: [],
99
+ error: pluginError('scriptComplete', result).message,
100
+ };
101
+ }
102
+
44
103
  /**
45
104
  * Build a descriptive error from a plugin response that didn't match the
46
105
  * expected type. Extracts error details from error-typed responses.
@@ -94,47 +153,65 @@ export class BridgeSession extends EventEmitter {
94
153
  return this._handle.isConnected;
95
154
  }
96
155
 
97
- async execAsync(code: string, timeout?: number): Promise<ExecResult> {
156
+ async execAsync(
157
+ code: string,
158
+ timeout?: number,
159
+ args?: string[]
160
+ ): Promise<ExecResult> {
98
161
  this._assertConnected();
99
162
  await this._ensureActionsAsync();
100
163
 
164
+ // The plugin cancels the script at the timeout and replies, so wait a
165
+ // little longer than that for the reply
101
166
  const timeoutMs = timeout ?? DEFAULT_TIMEOUTS.execute;
102
167
  const result = await this._handle.sendActionAsync<PluginMessage>(
103
168
  {
104
169
  type: 'execute',
105
170
  sessionId: this._info.sessionId,
106
171
  requestId: randomUUID(),
107
- payload: { script: code },
172
+ payload: {
173
+ script: code,
174
+ timeoutMs,
175
+ ...(args && args.length > 0 ? { args } : {}),
176
+ },
108
177
  },
109
- timeoutMs
178
+ timeoutMs + EXECUTE_REPLY_GRACE_MS
110
179
  );
111
180
 
112
- if (result.type === 'scriptComplete') {
113
- const output = (result.payload.output ?? []).map((entry) => ({
114
- level: entry.level as OutputLevel,
115
- body: entry.body,
116
- }));
117
- return {
118
- success: result.payload.success,
119
- output,
120
- error: result.payload.error,
121
- returnValues: result.payload.returnValues,
122
- };
123
- }
181
+ return toExecResult(result);
182
+ }
124
183
 
125
- if (result.type === 'error') {
126
- return {
127
- success: false,
128
- output: [],
129
- error: result.payload?.message ?? 'Unknown plugin error',
130
- };
131
- }
184
+ /**
185
+ * Run a script in a fresh playtest's server, started from this edit
186
+ * session, and resolve with its result once the playtest has ended. Works
187
+ * without the playtest's own sessions connecting: the plugin plants
188
+ * `runnerSource` in the playtest to run the script and end the playtest.
189
+ */
190
+ async runPlaytestScriptAsync(
191
+ options: RunPlaytestScriptOptions
192
+ ): Promise<ExecResult> {
193
+ this._assertConnected();
194
+ await this._ensureActionsAsync();
132
195
 
133
- return {
134
- success: false,
135
- output: [],
136
- error: pluginError('scriptComplete', result).message,
137
- };
196
+ const result = await this._handle.sendActionAsync<PluginMessage>(
197
+ {
198
+ type: 'runPlaytestScript',
199
+ sessionId: this._info.sessionId,
200
+ requestId: randomUUID(),
201
+ payload: {
202
+ mode: options.mode,
203
+ script: options.script,
204
+ runnerSource: options.runnerSource,
205
+ timeoutSec: options.timeoutSec,
206
+ ...(options.args && options.args.length > 0
207
+ ? { args: options.args }
208
+ : {}),
209
+ },
210
+ },
211
+ options.timeoutSec * 1000 + PLAYTEST_STARTUP_ALLOWANCE_MS
212
+ );
213
+
214
+ return toExecResult(result);
138
215
  }
139
216
 
140
217
  async queryStateAsync(): Promise<StateResult> {
@@ -158,6 +235,8 @@ export class BridgeSession extends EventEmitter {
158
235
  placeId: result.payload.placeId,
159
236
  placeName: result.payload.placeName,
160
237
  gameId: result.payload.gameId,
238
+ viewportSize: result.payload.viewportSize,
239
+ studioVersion: result.payload.studioVersion,
161
240
  };
162
241
  }
163
242
 
@@ -206,6 +285,8 @@ export class BridgeSession extends EventEmitter {
206
285
  direction: options?.direction,
207
286
  levels: options?.levels,
208
287
  includeInternal: options?.includeInternal,
288
+ ...(options?.since !== undefined ? { since: options.since } : {}),
289
+ ...(options?.groupRepeats ? { groupRepeats: true } : {}),
209
290
  },
210
291
  },
211
292
  timeoutMs
@@ -255,6 +336,170 @@ export class BridgeSession extends EventEmitter {
255
336
  throw pluginError('dataModelResult', result);
256
337
  }
257
338
 
339
+ /** Search the DataModel for instances by name, class and tag. */
340
+ async findInstancesAsync(
341
+ options: FindInstancesMessage['payload']
342
+ ): Promise<FindInstancesResultMessage['payload']> {
343
+ this._assertConnected();
344
+ await this._ensureActionsAsync();
345
+
346
+ const result = await this._handle.sendActionAsync<PluginMessage>(
347
+ {
348
+ type: 'findInstances',
349
+ sessionId: this._info.sessionId,
350
+ requestId: randomUUID(),
351
+ payload: options,
352
+ },
353
+ DEFAULT_TIMEOUTS.findInstances
354
+ );
355
+
356
+ if (result.type === 'findInstancesResult') {
357
+ return result.payload;
358
+ }
359
+
360
+ throw pluginError('findInstancesResult', result);
361
+ }
362
+
363
+ /** Search the source of every script for a pattern. */
364
+ async grepScriptsAsync(
365
+ options: GrepScriptsMessage['payload']
366
+ ): Promise<GrepScriptsResultMessage['payload']> {
367
+ this._assertConnected();
368
+ await this._ensureActionsAsync();
369
+
370
+ const result = await this._handle.sendActionAsync<PluginMessage>(
371
+ {
372
+ type: 'grepScripts',
373
+ sessionId: this._info.sessionId,
374
+ requestId: randomUUID(),
375
+ payload: options,
376
+ },
377
+ DEFAULT_TIMEOUTS.grepScripts
378
+ );
379
+
380
+ if (result.type === 'grepScriptsResult') {
381
+ return result.payload;
382
+ }
383
+
384
+ throw pluginError('grepScriptsResult', result);
385
+ }
386
+
387
+ /** Serialize the instances at `paths` into one binary model (.rbxm). */
388
+ async exportInstancesAsync(paths: string[]): Promise<ExportInstancesResult> {
389
+ this._assertConnected();
390
+ await this._ensureActionsAsync();
391
+
392
+ const result = await this._handle.sendActionAsync<PluginMessage>(
393
+ {
394
+ type: 'exportInstances',
395
+ sessionId: this._info.sessionId,
396
+ requestId: randomUUID(),
397
+ payload: { paths },
398
+ },
399
+ DEFAULT_TIMEOUTS.exportInstances
400
+ );
401
+
402
+ if (result.type === 'exportInstancesResult') {
403
+ return { data: result.payload.data, paths: result.payload.paths };
404
+ }
405
+
406
+ throw pluginError('exportInstancesResult', result);
407
+ }
408
+
409
+ /** Insert a binary model (.rbxm), base64-encoded, under `parentPath`. */
410
+ async importInstancesAsync(
411
+ data: string,
412
+ parentPath: string
413
+ ): Promise<ImportInstancesResult> {
414
+ this._assertConnected();
415
+ await this._ensureActionsAsync();
416
+
417
+ const result = await this._handle.sendActionAsync<PluginMessage>(
418
+ {
419
+ type: 'importInstances',
420
+ sessionId: this._info.sessionId,
421
+ requestId: randomUUID(),
422
+ payload: { data, parentPath },
423
+ },
424
+ DEFAULT_TIMEOUTS.importInstances
425
+ );
426
+
427
+ if (result.type === 'importInstancesResult') {
428
+ return { paths: result.payload.paths };
429
+ }
430
+
431
+ throw pluginError('importInstancesResult', result);
432
+ }
433
+
434
+ /**
435
+ * Start a playtest from this edit session. Resolves when the playtest
436
+ * ends, not when it starts: StudioTestService only reports a refusal to
437
+ * start (a playtest already running) by failing the call that would have
438
+ * waited for the end.
439
+ */
440
+ async runPlaytestAsync(
441
+ mode: PlaytestMode,
442
+ playtestId: string,
443
+ bridgePort: number,
444
+ timeoutMs: number,
445
+ players?: number
446
+ ): Promise<PlaytestEndResult> {
447
+ this._assertConnected();
448
+ await this._ensureActionsAsync();
449
+
450
+ const result = await this._handle.sendActionAsync<PluginMessage>(
451
+ {
452
+ type: 'startPlaytest',
453
+ sessionId: this._info.sessionId,
454
+ requestId: randomUUID(),
455
+ payload: {
456
+ mode,
457
+ playtestId,
458
+ bridgePort,
459
+ instanceId: this._info.instanceId,
460
+ ...(players !== undefined ? { players } : {}),
461
+ },
462
+ },
463
+ timeoutMs
464
+ );
465
+
466
+ if (result.type === 'playtestEnded') {
467
+ return {
468
+ playtestId: result.payload.playtestId,
469
+ success: result.payload.success,
470
+ error: result.payload.error,
471
+ };
472
+ }
473
+
474
+ throw pluginError('playtestEnded', result);
475
+ }
476
+
477
+ /** End the playtest this server session belongs to. */
478
+ async endPlaytestAsync(): Promise<EndPlaytestResult> {
479
+ this._assertConnected();
480
+ await this._ensureActionsAsync();
481
+
482
+ const result = await this._handle.sendActionAsync<PluginMessage>(
483
+ {
484
+ type: 'endPlaytest',
485
+ sessionId: this._info.sessionId,
486
+ requestId: randomUUID(),
487
+ payload: {} as Record<string, never>,
488
+ },
489
+ DEFAULT_TIMEOUTS.endPlaytest
490
+ );
491
+
492
+ if (result.type === 'endPlaytestResult') {
493
+ return {
494
+ playtestId: result.payload.playtestId,
495
+ mode: result.payload.mode,
496
+ players: result.payload.players,
497
+ };
498
+ }
499
+
500
+ throw pluginError('endPlaytestResult', result);
501
+ }
502
+
258
503
  private _assertConnected(): void {
259
504
  if (!this._handle.isConnected) {
260
505
  throw new SessionDisconnectedError(this._info.sessionId);
@@ -300,7 +545,7 @@ export class BridgeSession extends EventEmitter {
300
545
  type: 'syncActions',
301
546
  sessionId: this._info.sessionId,
302
547
  requestId: randomUUID(),
303
- payload: { actions },
548
+ payload: { actions, verbose: OutputHelper.isVerbose() },
304
549
  },
305
550
  10_000
306
551
  );
@@ -26,6 +26,9 @@ export type {
26
26
  LogOptions,
27
27
  QueryDataModelOptions,
28
28
  LogFollowOptions,
29
+ PlaytestMode,
30
+ PlaytestEndResult,
31
+ EndPlaytestResult,
29
32
  StudioState,
30
33
  DataModelInstance,
31
34
  OutputLevel,
@@ -316,6 +316,29 @@ describe('BridgeClient', () => {
316
316
  });
317
317
  });
318
318
 
319
+ describe('host loss', () => {
320
+ it('fails a pending request at once when the host drops without replying', async () => {
321
+ host = await createMockHostWithSessions([createSessionInfo()]);
322
+ host.wss.on('connection', (ws) => {
323
+ ws.on('message', (raw) => {
324
+ const msg = decodeHostMessage(raw.toString());
325
+ if (msg?.type === 'host-envelope') {
326
+ ws.terminate();
327
+ }
328
+ });
329
+ });
330
+
331
+ client = new BridgeClient();
332
+ await client.connectAsync(host.port);
333
+
334
+ const started = Date.now();
335
+ await expect(
336
+ client.getSession('session-1')!.queryStateAsync()
337
+ ).rejects.toThrow('The bridge host shut down before replying');
338
+ expect(Date.now() - started).toBeLessThan(2_000);
339
+ });
340
+ });
341
+
319
342
  describe('session events', () => {
320
343
  it('handles session-connected event from host', async () => {
321
344
  host = await createMockHost();
@@ -80,9 +80,13 @@ class RelayedTransportHandle extends EventEmitter implements TransportHandle {
80
80
  }
81
81
  }
82
82
 
83
+ /** Lets the host's own timeout error, which says what timed out, arrive first. */
84
+ const HOST_REPLY_GRACE_MS = 2_000;
85
+
83
86
  export class BridgeClient extends EventEmitter {
84
87
  private _transport = new TransportClient();
85
88
  private _sessions = new Map<string, SessionInfo>();
89
+ private _hostProtocolVersion: number | undefined;
86
90
  private _sessionHandles = new Map<string, RelayedTransportHandle>();
87
91
  private _bridgeSessions = new Map<string, BridgeSession>();
88
92
  private _pendingRequests = new Map<
@@ -111,6 +115,10 @@ export class BridgeClient extends EventEmitter {
111
115
 
112
116
  this._transport.on('disconnected', () => {
113
117
  this._isConnected = false;
118
+ // Replies to these went to the old host, so none will arrive
119
+ this._rejectPendingRequests(
120
+ 'The bridge host shut down before replying. Studio may still have run the request.'
121
+ );
114
122
  this.emit('disconnected');
115
123
 
116
124
  // Trigger failover detection
@@ -134,6 +142,14 @@ export class BridgeClient extends EventEmitter {
134
142
  await this._fetchSessionsAsync();
135
143
  }
136
144
 
145
+ private _rejectPendingRequests(message: string): void {
146
+ for (const [, entry] of this._pendingRequests) {
147
+ clearTimeout(entry.timer);
148
+ entry.reject(new Error(message));
149
+ }
150
+ this._pendingRequests.clear();
151
+ }
152
+
137
153
  /**
138
154
  * Disconnect from the bridge host.
139
155
  */
@@ -144,12 +160,7 @@ export class BridgeClient extends EventEmitter {
144
160
  this._transport.disconnect();
145
161
  this._isConnected = false;
146
162
 
147
- // Cancel all pending requests
148
- for (const [, entry] of this._pendingRequests) {
149
- clearTimeout(entry.timer);
150
- entry.reject(new Error('Client disconnected'));
151
- }
152
- this._pendingRequests.clear();
163
+ this._rejectPendingRequests('Client disconnected');
153
164
 
154
165
  // Mark all handles as disconnected
155
166
  for (const handle of this._sessionHandles.values()) {
@@ -163,6 +174,11 @@ export class BridgeClient extends EventEmitter {
163
174
  /**
164
175
  * List all known sessions.
165
176
  */
177
+ /** The host's HOST_PROTOCOL_VERSION, or undefined for hosts older than it. */
178
+ get hostProtocolVersion(): number | undefined {
179
+ return this._hostProtocolVersion;
180
+ }
181
+
166
182
  listSessions(): SessionInfo[] {
167
183
  return Array.from(this._sessions.values());
168
184
  }
@@ -228,6 +244,7 @@ export class BridgeClient extends EventEmitter {
228
244
  requestId,
229
245
  targetSessionId,
230
246
  action,
247
+ timeoutMs,
231
248
  };
232
249
 
233
250
  return new Promise<PluginMessage>((resolve, reject) => {
@@ -236,7 +253,7 @@ export class BridgeClient extends EventEmitter {
236
253
  reject(
237
254
  new Error(`Request "${requestId}" timed out after ${timeoutMs}ms`)
238
255
  );
239
- }, timeoutMs);
256
+ }, timeoutMs + HOST_REPLY_GRACE_MS);
240
257
 
241
258
  this._pendingRequests.set(requestId, { resolve, reject, timer });
242
259
 
@@ -306,6 +323,7 @@ export class BridgeClient extends EventEmitter {
306
323
  }
307
324
 
308
325
  private _handleListSessionsResponse(msg: ListSessionsResponse): void {
326
+ this._hostProtocolVersion = msg.protocolVersion;
309
327
  const pending = this._pendingRequests.get(msg.requestId);
310
328
  if (pending) {
311
329
  clearTimeout(pending.timer);
@@ -254,6 +254,44 @@ describe('BridgeHost', () => {
254
254
  });
255
255
  });
256
256
 
257
+ describe('client relay', () => {
258
+ it('answers an envelope it cannot decode instead of dropping it', async () => {
259
+ host = new BridgeHost();
260
+ const port = await host.startAsync({ port: 0 });
261
+ const ws = await new Promise<WebSocket>((resolve, reject) => {
262
+ const client = new WebSocket(`ws://localhost:${port}/client`);
263
+ client.on('open', () => resolve(client));
264
+ client.on('error', reject);
265
+ });
266
+ openClients.push(ws);
267
+
268
+ const reply = new Promise<any>((resolve) => {
269
+ ws.once('message', (raw) => resolve(JSON.parse(raw.toString())));
270
+ });
271
+ ws.send(
272
+ JSON.stringify({
273
+ type: 'host-envelope',
274
+ requestId: 'req-1',
275
+ targetSessionId: 'session-1',
276
+ action: {
277
+ type: 'fromTheFuture',
278
+ sessionId: 'session-1',
279
+ payload: {},
280
+ },
281
+ timeoutMs: 1000,
282
+ })
283
+ );
284
+
285
+ const response = await reply;
286
+ expect(response.type).toBe('host-response');
287
+ expect(response.requestId).toBe('req-1');
288
+ expect(response.result.type).toBe('error');
289
+ expect(response.result.payload.message).toContain(
290
+ "could not relay 'fromTheFuture'"
291
+ );
292
+ });
293
+ });
294
+
257
295
  describe('health endpoint', () => {
258
296
  it('responds with valid JSON on /health', async () => {
259
297
  host = new BridgeHost();
@@ -20,6 +20,7 @@ import {
20
20
  decodePluginMessage,
21
21
  encodeMessage,
22
22
  type Capability,
23
+ type PluginContext,
23
24
  type ServerMessage,
24
25
  } from '../../server/web-socket-protocol.js';
25
26
  import { PendingRequestMap } from '../../server/pending-request-map.js';
@@ -42,8 +43,14 @@ export interface PluginSessionInfo {
42
43
  placeName?: string;
43
44
  /** Studio state from register. */
44
45
  state?: string;
46
+ /** DataModel context from register. */
47
+ context?: PluginContext;
45
48
  /** Place file from register. */
46
49
  placeFile?: string;
50
+ /** Launch id from register, for a Studio that `process run` launched. */
51
+ launchId?: string;
52
+ /** Player name from register, for a multiplayer test client. */
53
+ playerName?: string;
47
54
  }
48
55
 
49
56
  const SHUTDOWN_TIMEOUT_MS = 2_000;
@@ -122,6 +129,7 @@ export class BridgeHost extends EventEmitter {
122
129
  const data = typeof raw === 'string' ? raw : raw.toString('utf-8');
123
130
  const msg = decodeHostMessage(data);
124
131
  if (!msg) {
132
+ this._replyUndecodable(ws, data);
125
133
  return;
126
134
  }
127
135
  this.emit('client-message', msg, (reply: HostProtocolMessage) => {
@@ -357,6 +365,42 @@ export class BridgeHost extends EventEmitter {
357
365
  }
358
366
  }
359
367
 
368
+ /**
369
+ * Answers an envelope this host cannot decode with an error, so the client
370
+ * fails fast instead of waiting out its timeout. That is usually a newer
371
+ * client relaying an action this host does not know.
372
+ */
373
+ private _replyUndecodable(ws: WebSocket, data: string): void {
374
+ let parsed: any;
375
+ try {
376
+ parsed = JSON.parse(data);
377
+ } catch {
378
+ return;
379
+ }
380
+ if (
381
+ parsed?.type !== 'host-envelope' ||
382
+ typeof parsed.requestId !== 'string'
383
+ ) {
384
+ return;
385
+ }
386
+
387
+ const actionType = String(parsed.action?.type ?? 'unknown');
388
+ ws.send(
389
+ encodeHostMessage({
390
+ type: 'host-response',
391
+ requestId: parsed.requestId,
392
+ result: {
393
+ type: 'error',
394
+ sessionId: String(parsed.targetSessionId ?? ''),
395
+ payload: {
396
+ code: 'INVALID_PAYLOAD',
397
+ message: `The bridge host could not relay '${actionType}'. It may run an older studio-bridge than this command; restart it (e.g. stop 'studio-bridge serve').`,
398
+ },
399
+ },
400
+ })
401
+ );
402
+ }
403
+
360
404
  private _handlePluginConnection(
361
405
  ws: WebSocket,
362
406
  _request: IncomingMessage
@@ -379,7 +423,10 @@ export class BridgeHost extends EventEmitter {
379
423
  instanceId: msg.payload.instanceId,
380
424
  placeName: msg.payload.placeName,
381
425
  state: msg.payload.state,
426
+ context: msg.payload.context,
382
427
  placeFile: msg.payload.placeFile,
428
+ launchId: msg.payload.launchId,
429
+ playerName: msg.payload.playerName,
383
430
  });
384
431
  };
385
432
 
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { createServer, type Server } from 'net';
14
14
  import { WebSocket } from 'ws';
15
+ import { MAX_MESSAGE_BYTES } from '../../server/web-socket-protocol.js';
15
16
  import { HostUnreachableError } from '../types.js';
16
17
 
17
18
  export type TakeoverState =
@@ -79,7 +80,9 @@ function tryConnectAsClientDefaultAsync(port: number): Promise<boolean> {
79
80
  const CONNECT_TIMEOUT_MS = 2_000;
80
81
 
81
82
  return new Promise<boolean>((resolve) => {
82
- const ws = new WebSocket(`ws://localhost:${port}/client`);
83
+ const ws = new WebSocket(`ws://localhost:${port}/client`, {
84
+ maxPayload: MAX_MESSAGE_BYTES,
85
+ });
83
86
 
84
87
  const timer = setTimeout(() => {
85
88
  ws.removeAllListeners();
@@ -41,6 +41,21 @@ function createSessionInfo(overrides: Partial<SessionInfo> = {}): SessionInfo {
41
41
 
42
42
  describe('host-protocol', () => {
43
43
  describe('HostEnvelope', () => {
44
+ it('keeps payload fields it does not list, for newer clients', () => {
45
+ const decoded = roundTrip({
46
+ type: 'host-envelope',
47
+ requestId: 'req-1',
48
+ targetSessionId: 'session-1',
49
+ action: {
50
+ type: 'execute',
51
+ sessionId: 'session-1',
52
+ payload: { script: 'return ...', futureOption: true },
53
+ },
54
+ } as any) as HostEnvelope;
55
+
56
+ expect((decoded.action.payload as any).futureOption).toBe(true);
57
+ });
58
+
44
59
  it('round-trips correctly', () => {
45
60
  const msg: HostEnvelope = {
46
61
  type: 'host-envelope',