@dimina-kit/devtools 0.4.0-dev.20260831134112 → 0.4.0-dev.20260907055341

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 (275) hide show
  1. package/README.md +52 -729
  2. package/dist/main/app/app.d.ts +27 -7
  3. package/dist/main/app/app.js +285 -681
  4. package/dist/main/app/editor-view.d.ts +21 -0
  5. package/dist/main/app/editor-view.js +71 -0
  6. package/dist/main/app/global-mirrors.d.ts +23 -0
  7. package/dist/main/app/global-mirrors.js +37 -0
  8. package/dist/main/app/host-sidebar-default.d.ts +19 -0
  9. package/dist/main/app/host-sidebar-default.js +44 -0
  10. package/dist/main/app/menu-setup.d.ts +13 -0
  11. package/dist/main/app/menu-setup.js +49 -0
  12. package/dist/main/app/open-project-ipc.d.ts +13 -0
  13. package/dist/main/app/open-project-ipc.js +21 -0
  14. package/dist/main/app/project-list-surface.d.ts +31 -0
  15. package/dist/main/app/project-list-surface.js +20 -0
  16. package/dist/main/app/project-window.d.ts +36 -0
  17. package/dist/main/app/project-window.js +93 -0
  18. package/dist/main/app/servers.d.ts +17 -0
  19. package/dist/main/app/servers.js +60 -0
  20. package/dist/main/app/ui-extension-targets.d.ts +42 -0
  21. package/dist/main/app/ui-extension-targets.js +101 -0
  22. package/dist/main/app/window-close-reveal.harness.d.ts +34 -0
  23. package/dist/main/app/window-close-reveal.harness.js +65 -0
  24. package/dist/main/app/window-events.d.ts +15 -0
  25. package/dist/main/app/window-events.js +45 -0
  26. package/dist/main/app/window-runtime-services.d.ts +41 -0
  27. package/dist/main/app/window-runtime-services.js +178 -0
  28. package/dist/main/app/workbench-modules.d.ts +24 -0
  29. package/dist/main/app/workbench-modules.js +51 -0
  30. package/dist/main/app/workbench-window.d.ts +85 -0
  31. package/dist/main/app/workbench-window.js +310 -0
  32. package/dist/main/index.bundle.js +10669 -9096
  33. package/dist/main/ipc/app.d.ts +4 -1
  34. package/dist/main/ipc/app.js +4 -3
  35. package/dist/main/ipc/device-picker.d.ts +16 -0
  36. package/dist/main/ipc/device-picker.js +27 -0
  37. package/dist/main/ipc/index.d.ts +1 -0
  38. package/dist/main/ipc/index.js +1 -0
  39. package/dist/main/ipc/internal-devtools.d.ts +2 -1
  40. package/dist/main/ipc/internal-devtools.js +4 -3
  41. package/dist/main/ipc/popover.d.ts +4 -1
  42. package/dist/main/ipc/popover.js +25 -9
  43. package/dist/main/ipc/project-create.d.ts +2 -1
  44. package/dist/main/ipc/project-create.js +6 -5
  45. package/dist/main/ipc/project-fs.d.ts +3 -1
  46. package/dist/main/ipc/project-fs.js +17 -17
  47. package/dist/main/ipc/projects.d.ts +3 -3
  48. package/dist/main/ipc/projects.js +13 -12
  49. package/dist/main/ipc/session.d.ts +4 -1
  50. package/dist/main/ipc/session.js +16 -11
  51. package/dist/main/ipc/settings.d.ts +4 -1
  52. package/dist/main/ipc/settings.js +12 -19
  53. package/dist/main/ipc/simulator-module.d.ts +5 -0
  54. package/dist/main/ipc/simulator-module.js +9 -5
  55. package/dist/main/ipc/simulator.d.ts +4 -1
  56. package/dist/main/ipc/simulator.js +12 -18
  57. package/dist/main/ipc/tooltip.d.ts +2 -1
  58. package/dist/main/ipc/tooltip.js +8 -7
  59. package/dist/main/ipc/views.d.ts +4 -1
  60. package/dist/main/ipc/views.js +20 -9
  61. package/dist/main/runtime/devtools-backend-before-quit.harness.d.ts +2 -20
  62. package/dist/main/runtime/devtools-backend-before-quit.harness.js +14 -283
  63. package/dist/main/runtime/devtools-backend.js +14 -3
  64. package/dist/main/runtime/devtools-runtime-mock.harness.d.ts +21 -0
  65. package/dist/main/runtime/devtools-runtime-mock.harness.js +313 -0
  66. package/dist/main/services/app-services.d.ts +62 -0
  67. package/dist/main/services/app-services.js +64 -0
  68. package/dist/main/services/automation/connection-target.d.ts +36 -0
  69. package/dist/main/services/automation/connection-target.js +29 -0
  70. package/dist/main/services/automation/console-bridge.d.ts +28 -0
  71. package/dist/main/services/automation/console-bridge.js +91 -0
  72. package/dist/main/services/automation/handlers/app.js +4 -4
  73. package/dist/main/services/automation/handlers/element.js +55 -21
  74. package/dist/main/services/automation/index.d.ts +12 -1
  75. package/dist/main/services/automation/index.js +54 -133
  76. package/dist/main/services/default-adapter.js +1 -1
  77. package/dist/main/services/fs-bridge-protocol.d.ts +61 -0
  78. package/dist/main/services/fs-bridge-protocol.js +34 -0
  79. package/dist/main/services/fs-watch-sse.d.ts +8 -1
  80. package/dist/main/services/fs-watch-sse.js +19 -5
  81. package/dist/main/services/mcp/connection-owner.d.ts +38 -0
  82. package/dist/main/services/mcp/connection-owner.js +79 -0
  83. package/dist/main/services/mcp/index.d.ts +3 -1
  84. package/dist/main/services/mcp/index.js +4 -3
  85. package/dist/main/services/mcp/opened-project.d.ts +88 -0
  86. package/dist/main/services/mcp/opened-project.js +60 -0
  87. package/dist/main/services/mcp/target-manager.d.ts +71 -23
  88. package/dist/main/services/mcp/target-manager.js +269 -142
  89. package/dist/main/services/mcp/target-selection.d.ts +47 -0
  90. package/dist/main/services/mcp/target-selection.js +71 -0
  91. package/dist/main/services/mcp/tools/context-tools.js +20 -5
  92. package/dist/main/services/mcp/tools/project-tools.d.ts +29 -6
  93. package/dist/main/services/mcp/tools/project-tools.js +37 -25
  94. package/dist/main/services/module.d.ts +13 -5
  95. package/dist/main/services/notifications/renderer-notifier.d.ts +28 -4
  96. package/dist/main/services/notifications/renderer-notifier.js +15 -3
  97. package/dist/main/services/projects/local-provider.js +2 -0
  98. package/dist/main/services/projects/project-repository.d.ts +31 -1
  99. package/dist/main/services/projects/project-repository.js +144 -20
  100. package/dist/main/services/projects/types.d.ts +31 -8
  101. package/dist/main/services/safe-area/index.d.ts +19 -5
  102. package/dist/main/services/safe-area/index.js +38 -13
  103. package/dist/main/services/simulator/ui-extensions.d.ts +8 -0
  104. package/dist/main/services/simulator/ui-extensions.js +16 -6
  105. package/dist/main/services/simulator-storage/index.d.ts +12 -1
  106. package/dist/main/services/simulator-storage/index.js +22 -7
  107. package/dist/main/services/simulator-storage/window-scope.d.ts +20 -0
  108. package/dist/main/services/simulator-storage/window-scope.js +20 -0
  109. package/dist/main/services/views/host-slot-port-channel.d.ts +20 -3
  110. package/dist/main/services/views/host-slot-port-channel.js +33 -5
  111. package/dist/main/services/views/managed-web-contents-view.js +21 -1
  112. package/dist/main/services/views/native-simulator-view.js +13 -16
  113. package/dist/main/services/views/overlay-panels-view.d.ts +13 -0
  114. package/dist/main/services/views/overlay-panels-view.js +31 -0
  115. package/dist/main/services/views/view-manager.d.ts +1 -1
  116. package/dist/main/services/views/view-manager.js +3 -0
  117. package/dist/main/services/window-contexts/context-router.d.ts +36 -0
  118. package/dist/main/services/window-contexts/context-router.js +55 -0
  119. package/dist/main/services/workbench-coi-host.d.ts +52 -0
  120. package/dist/main/services/workbench-coi-host.js +227 -0
  121. package/dist/main/services/workbench-coi-server.js +75 -133
  122. package/dist/main/services/workbench-coi-shutdown.d.ts +40 -0
  123. package/dist/main/services/workbench-coi-shutdown.js +69 -0
  124. package/dist/main/services/workbench-coi-static.d.ts +23 -0
  125. package/dist/main/services/workbench-coi-static.js +87 -0
  126. package/dist/main/services/workbench-context.d.ts +27 -3
  127. package/dist/main/services/workbench-context.js +10 -5
  128. package/dist/main/services/workspace/compile-mode-adapter.d.ts +18 -0
  129. package/dist/main/services/workspace/compile-mode-adapter.js +57 -0
  130. package/dist/main/services/workspace/compile-mode-store.d.ts +25 -0
  131. package/dist/main/services/workspace/compile-mode-store.js +71 -0
  132. package/dist/main/services/workspace/workspace-compile-modes.d.ts +59 -0
  133. package/dist/main/services/workspace/workspace-compile-modes.js +93 -0
  134. package/dist/main/services/workspace/workspace-referer.d.ts +17 -0
  135. package/dist/main/services/workspace/workspace-referer.js +26 -0
  136. package/dist/main/services/workspace/workspace-service.d.ts +4 -0
  137. package/dist/main/services/workspace/workspace-service.js +51 -84
  138. package/dist/main/services/workspace/workspace-thumbnails.d.ts +22 -0
  139. package/dist/main/services/workspace/workspace-thumbnails.js +67 -0
  140. package/dist/main/utils/ipc-context-source.d.ts +40 -0
  141. package/dist/main/utils/ipc-context-source.js +23 -0
  142. package/dist/main/utils/ipc-registry.d.ts +42 -36
  143. package/dist/main/utils/ipc-registry.js +117 -50
  144. package/dist/main/utils/sender-policy.d.ts +9 -0
  145. package/dist/main/utils/sender-policy.js +26 -3
  146. package/dist/main/utils/theme.d.ts +3 -2
  147. package/dist/main/utils/theme.js +3 -2
  148. package/dist/main/windows/main-window/create.d.ts +16 -0
  149. package/dist/main/windows/main-window/create.js +19 -11
  150. package/dist/main/windows/main-window/events.d.ts +1 -1
  151. package/dist/main/windows/main-window/events.js +26 -13
  152. package/dist/main/windows/main-window/index.d.ts +1 -1
  153. package/dist/main/windows/main-window/index.js +1 -1
  154. package/dist/native-host/common/common.js +94 -73
  155. package/dist/native-host/render/render.js +4079 -3171
  156. package/dist/native-host/service/service.js +2 -2
  157. package/dist/preload/runtime/native-host.d.ts +4 -0
  158. package/dist/preload/runtime/temp-files.js +9 -2
  159. package/dist/preload/runtime/wait-for-slot-root.d.ts +7 -4
  160. package/dist/preload/runtime/wait-for-slot-root.js +36 -8
  161. package/dist/preload/windows/host-dialog-runtime.cjs +10 -3
  162. package/dist/preload/windows/host-dialog-runtime.cjs.map +2 -2
  163. package/dist/preload/windows/host-sidebar-runtime.cjs +10 -3
  164. package/dist/preload/windows/host-sidebar-runtime.cjs.map +2 -2
  165. package/dist/preload/windows/host-toolbar-runtime.cjs +10 -3
  166. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  167. package/dist/preload/windows/main.cjs +181 -4
  168. package/dist/preload/windows/main.cjs.map +4 -4
  169. package/dist/preload/windows/simulator.cjs +10 -0
  170. package/dist/preload/windows/simulator.cjs.map +2 -2
  171. package/dist/preload/windows/simulator.js +10 -0
  172. package/dist/renderer/assets/button-CB2MWjHY.js +2 -0
  173. package/dist/renderer/assets/constants-CkoGPQTS.js +2 -0
  174. package/dist/renderer/assets/constants-zQZ2-y21.js +2 -0
  175. package/dist/renderer/assets/{createLucideIcon-Bqi8hpdi.js → createLucideIcon-cZ9FlZZw.js} +2 -2
  176. package/dist/renderer/assets/devicePicker-H2eIO4e1.js +2 -0
  177. package/dist/renderer/assets/dialog-pHzEA8Vx.js +46 -0
  178. package/dist/renderer/assets/dist-B5HADirS.js +2 -0
  179. package/dist/renderer/assets/{dist-DKWdE1TK.js → dist-DIFlRMKe.js} +2 -2
  180. package/dist/renderer/assets/hostSidebarDefault-DRWh6OCt.js +2 -0
  181. package/dist/renderer/assets/index-CEL_XFge.js +2 -0
  182. package/dist/renderer/assets/{input-Ct3alvkq.js → input-CAObw95L.js} +2 -2
  183. package/dist/renderer/assets/ipc-channels-overlays-gIPUFnqc.js +2 -0
  184. package/dist/renderer/assets/jsx-runtime-DICEQH_0.css +1 -0
  185. package/dist/renderer/assets/{jsx-runtime-DGLHmmOq.js → jsx-runtime-DIw5wnYl.js} +1 -1
  186. package/dist/renderer/assets/plus-BQ9u5EbL.js +2 -0
  187. package/dist/renderer/assets/popover-Cw7G2Ctp.js +2 -0
  188. package/dist/renderer/assets/presets-DMZOb9ay.js +2 -0
  189. package/dist/renderer/assets/project-api-YTOr_Yvo.js +2 -0
  190. package/dist/renderer/assets/{projectCreateDialog-Bbjz75bP.js → projectCreateDialog-BmLqiYxn.js} +2 -2
  191. package/dist/renderer/assets/search-BMvuYF48.js +2 -0
  192. package/dist/renderer/assets/select-nFP95TxM.js +2 -0
  193. package/dist/renderer/assets/settings-CnpVfSWt.js +2 -0
  194. package/dist/renderer/assets/settings-api-Ccsf-Nsj.js +2 -0
  195. package/dist/renderer/assets/settings-tab-bar-CAzrv32m.js +2 -0
  196. package/dist/renderer/assets/smartphone-Bb2GdIKy.js +2 -0
  197. package/dist/renderer/assets/tabs-CPKN3Mmq.js +2 -0
  198. package/dist/renderer/assets/{tooltip-B_-_YVbw.js → tooltip-D6AxXRk5.js} +2 -2
  199. package/dist/renderer/assets/types-Bh0hA7El.js +2 -0
  200. package/dist/renderer/assets/updateDialog-CwndJ0Bl.js +2 -0
  201. package/dist/renderer/assets/{utils-kfQov6Pa.js → utils-sEB5OZEQ.js} +2 -2
  202. package/dist/renderer/assets/view-api-B-2zPBR5.js +2 -0
  203. package/dist/renderer/assets/view-ids-BfaDVV5P.js +2 -0
  204. package/dist/renderer/assets/workbench-BLJ0wKsg.js +5 -0
  205. package/dist/renderer/assets/{workbenchSettings-CV4LVA9b.js → workbenchSettings-YD0Ctbm_.js} +3 -3
  206. package/dist/renderer/entries/device-picker/index.html +28 -0
  207. package/dist/renderer/entries/host-sidebar-default/index.html +6 -5
  208. package/dist/renderer/entries/main/index.html +16 -17
  209. package/dist/renderer/entries/popover/index.html +16 -10
  210. package/dist/renderer/entries/project-create-dialog/index.html +12 -12
  211. package/dist/renderer/entries/settings/index.html +10 -10
  212. package/dist/renderer/entries/tooltip/index.html +5 -5
  213. package/dist/renderer/entries/update-dialog/index.html +10 -10
  214. package/dist/renderer/entries/workbench/index.html +49 -0
  215. package/dist/renderer/entries/workbench-settings/index.html +12 -11
  216. package/dist/service-host/host-env-update.cjs +23 -0
  217. package/dist/service-host/preload.cjs +24 -49
  218. package/dist/service-host/service-message-queue.cjs +71 -0
  219. package/dist/service-host/sync-impls/system-info.js +31 -9
  220. package/dist/shared/compile-mode-state.d.ts +73 -0
  221. package/dist/shared/compile-mode-state.js +100 -0
  222. package/dist/shared/compile-modes.d.ts +152 -0
  223. package/dist/shared/compile-modes.js +237 -0
  224. package/dist/shared/ipc-channels-overlays.d.ts +8 -2
  225. package/dist/shared/ipc-channels-overlays.js +19 -2
  226. package/dist/shared/ipc-channels.d.ts +5 -10
  227. package/dist/shared/ipc-channels.js +17 -14
  228. package/dist/shared/ipc-schemas.d.ts +76 -24
  229. package/dist/shared/ipc-schemas.js +55 -10
  230. package/dist/shared/project-window-types.d.ts +42 -0
  231. package/dist/shared/project-window-types.js +2 -0
  232. package/dist/shared/types.d.ts +43 -17
  233. package/dist/shared/view-ids.d.ts +1 -0
  234. package/dist/shared/view-ids.js +3 -1
  235. package/dist/simulator/assets/bridge-channels-B3JAmRfl.js +2 -0
  236. package/dist/simulator/assets/device-shell-ATEhHV2_.css +1 -0
  237. package/dist/simulator/assets/device-shell-pxnZZp10.js +431 -0
  238. package/dist/simulator/assets/simulator-Dwp7lnvu.js +10 -0
  239. package/dist/simulator/assets/simulator-mini-app-DFo7fhi5.js +2 -0
  240. package/dist/simulator/assets/simulator-ui-B2ddJB1R.js +2 -0
  241. package/dist/simulator/simulator.html +3 -3
  242. package/dist/simulator/temp-files.d.ts +6 -0
  243. package/dist/simulator/temp-files.js +46 -4
  244. package/dist/simulator/types.d.ts +7 -0
  245. package/dist/vscode-workbench/assets/__vite-browser-external-JIkWewFf.js +1 -0
  246. package/dist/vscode-workbench/assets/{dist-CMfmHflW.js → dist-CXG-u1VB.js} +3 -3
  247. package/dist/vscode-workbench/assets/{iconv-lite-umd-Bx8CgaZq.js → iconv-lite-umd-Dxx9Qyb4.js} +1 -1
  248. package/dist/vscode-workbench/assets/{index-CdG6YWhW.js → index-6VJMsaWj.js} +460 -460
  249. package/dist/vscode-workbench/assets/{jschardet-M0qgd2lN.js → jschardet-Ba3D16vt.js} +1 -1
  250. package/dist/vscode-workbench/index.html +1 -1
  251. package/package.json +11 -8
  252. package/dist/renderer/assets/button-D5Q2wkXq.js +0 -2
  253. package/dist/renderer/assets/constants-D7DfVqMp.js +0 -2
  254. package/dist/renderer/assets/constants-D8zsejFq.js +0 -2
  255. package/dist/renderer/assets/dialog-e1TecTOY.js +0 -46
  256. package/dist/renderer/assets/hostSidebarDefault-B3yLfy8j.js +0 -2
  257. package/dist/renderer/assets/index-CsPD68N_.js +0 -5
  258. package/dist/renderer/assets/ipc-channels-overlays-LsDzGAtV.js +0 -2
  259. package/dist/renderer/assets/jsx-runtime-2vXHouwr.css +0 -1
  260. package/dist/renderer/assets/popover-Dix5cm_b.js +0 -2
  261. package/dist/renderer/assets/project-api-k7Oq9iaq.js +0 -2
  262. package/dist/renderer/assets/select-acGb0AmJ.js +0 -2
  263. package/dist/renderer/assets/settings-CQrTMr43.js +0 -2
  264. package/dist/renderer/assets/settings-api-CrUQ9rYV.js +0 -2
  265. package/dist/renderer/assets/settings-tab-bar-Cw1kVIf2.js +0 -2
  266. package/dist/renderer/assets/types-CsEKtFyr.js +0 -2
  267. package/dist/renderer/assets/updateDialog-NsF8223A.js +0 -2
  268. package/dist/renderer/assets/view-api-CLn3rcQE.js +0 -2
  269. package/dist/simulator/assets/bridge-channels-zhsumfky.js +0 -2
  270. package/dist/simulator/assets/device-shell-CmtM1QxP.js +0 -2
  271. package/dist/simulator/assets/device-shell-iFZo1igL.css +0 -1
  272. package/dist/simulator/assets/simulator-CIobn3Mh.js +0 -10
  273. package/dist/simulator/assets/simulator-mini-app-CRe6n9br.js +0 -2
  274. package/dist/simulator/assets/simulator-ui-C_cP602X.js +0 -2
  275. package/dist/vscode-workbench/assets/__vite-browser-external-Bc1vizTf.js +0 -1
@@ -20,27 +20,61 @@ elementHandlers['Element.triggerEvent'] = async (ctx, params) => {
20
20
  `);
21
21
  return {};
22
22
  };
23
- elementHandlers['Element.touchstart'] = async (ctx, params) => {
24
- const ref = getElementRef(params);
25
- await evalInElement(ctx, ref, `
26
- if (el) el.dispatchEvent(new TouchEvent('touchstart', { bubbles: true }))
27
- `);
28
- return {};
29
- };
30
- elementHandlers['Element.touchmove'] = async (ctx, params) => {
31
- const ref = getElementRef(params);
32
- await evalInElement(ctx, ref, `
33
- if (el) el.dispatchEvent(new TouchEvent('touchmove', { bubbles: true }))
34
- `);
35
- return {};
36
- };
37
- elementHandlers['Element.touchend'] = async (ctx, params) => {
38
- const ref = getElementRef(params);
39
- await evalInElement(ctx, ref, `
40
- if (el) el.dispatchEvent(new TouchEvent('touchend', { bubbles: true }))
41
- `);
42
- return {};
43
- };
23
+ /**
24
+ * Touch events must carry real touch points: the gesture layer reads coordinates
25
+ * off `touches` / `changedTouches` to decide tap vs. move-cancel, so an event with
26
+ * empty lists reaches it as a touch that happened nowhere. The protocol lets the
27
+ * caller omit points entirely (`touchstart()` with no args), in which case the
28
+ * element's centre — read from live layout, not a fixed offset — stands in for
29
+ * where a finger would land.
30
+ */
31
+ function touchHandler(type) {
32
+ // A finished touch is no longer among the active `touches`, only in `changedTouches`.
33
+ const fallbackTouches = type === 'touchend' ? '[]' : '[{}]';
34
+ return async (ctx, params) => {
35
+ const ref = getElementRef(params);
36
+ const touches = params.touches ?? null;
37
+ // The protocol spells it `changeTouches`; the DOM event field is `changedTouches`.
38
+ const changed = params.changeTouches ?? null;
39
+ await evalInElement(ctx, ref, `
40
+ if (!el) throw new Error('Element not found')
41
+ const rect = el.getBoundingClientRect()
42
+ const centreX = rect.left + rect.width / 2
43
+ const centreY = rect.top + rect.height / 2
44
+ const make = (p, i) => {
45
+ const clientX = p.clientX != null ? p.clientX : (p.pageX != null ? p.pageX - scrollX : centreX)
46
+ const clientY = p.clientY != null ? p.clientY : (p.pageY != null ? p.pageY - scrollY : centreY)
47
+ return new Touch({
48
+ identifier: p.identifier != null ? p.identifier : i,
49
+ target: el,
50
+ clientX,
51
+ clientY,
52
+ pageX: p.pageX != null ? p.pageX : clientX + scrollX,
53
+ pageY: p.pageY != null ? p.pageY : clientY + scrollY,
54
+ screenX: clientX,
55
+ screenY: clientY,
56
+ force: 1,
57
+ })
58
+ }
59
+ const raw = ${JSON.stringify(touches)}
60
+ const rawChanged = ${JSON.stringify(changed)}
61
+ const active = (raw || ${fallbackTouches}).map(make)
62
+ const changedList = (rawChanged || raw || [{}]).map(make)
63
+ el.dispatchEvent(new TouchEvent('${type}', {
64
+ bubbles: true,
65
+ cancelable: true,
66
+ composed: true,
67
+ touches: active,
68
+ targetTouches: active,
69
+ changedTouches: changedList,
70
+ }))
71
+ `);
72
+ return {};
73
+ };
74
+ }
75
+ elementHandlers['Element.touchstart'] = touchHandler('touchstart');
76
+ elementHandlers['Element.touchmove'] = touchHandler('touchmove');
77
+ elementHandlers['Element.touchend'] = touchHandler('touchend');
44
78
  elementHandlers['Element.getDOMProperties'] = async (ctx, params) => {
45
79
  const ref = getElementRef(params);
46
80
  const names = params.names;
@@ -9,11 +9,22 @@
9
9
  * This server handles these messages by delegating to the workbench context
10
10
  * (simulator webContents, IPC handlers, etc.) without modifying dimina upstream.
11
11
  */
12
+ import { type IpcInput } from '../../utils/ipc-context-source.js';
12
13
  import type { WorkbenchContext } from '../workbench-context.js';
13
14
  export interface AutomationServer {
14
15
  close: () => void;
15
16
  port: number;
16
17
  }
17
18
  export declare function getAutomationPort(): number | null;
18
- export declare function startAutomationServer(ctx: WorkbenchContext, port?: number): Promise<AutomationServer>;
19
+ /**
20
+ * `getCtx` rather than a context: the server binds its port at boot, before any
21
+ * project window exists, so there is nothing to capture yet. Each connection
22
+ * then pins the first project window it reaches and keeps driving that one —
23
+ * `getCtx` is how it finds that window, not a per-message lookup.
24
+ *
25
+ * `senders` covers every live window instead, because the port channel is asked
26
+ * by renderers `getCtx` never names: the project list and background project
27
+ * windows all start automation clients of their own.
28
+ */
29
+ export declare function startAutomationServer(getCtx: () => WorkbenchContext, senders: IpcInput<WorkbenchContext>, port?: number): Promise<AutomationServer>;
19
30
  //# sourceMappingURL=index.d.ts.map
@@ -10,8 +10,11 @@
10
10
  * (simulator webContents, IPC handlers, etc.) without modifying dimina upstream.
11
11
  */
12
12
  import { WebSocketServer } from 'ws';
13
- import { AutomationChannel, SimulatorChannel } from '../../../shared/ipc-channels.js';
13
+ import { AutomationChannel } from '../../../shared/ipc-channels.js';
14
14
  import { IpcRegistry } from '../../utils/ipc-registry.js';
15
+ import { toIpcContextSource } from '../../utils/ipc-context-source.js';
16
+ import { createConnectionTarget } from './connection-target.js';
17
+ import { createConsoleBridge } from './console-bridge.js';
15
18
  import { getSimulator } from './exec.js';
16
19
  import { toolHandlers } from './handlers/tool.js';
17
20
  import { appHandlers } from './handlers/app.js';
@@ -28,7 +31,17 @@ let currentPort = null;
28
31
  export function getAutomationPort() {
29
32
  return currentPort;
30
33
  }
31
- export async function startAutomationServer(ctx, port = 0) {
34
+ /**
35
+ * `getCtx` rather than a context: the server binds its port at boot, before any
36
+ * project window exists, so there is nothing to capture yet. Each connection
37
+ * then pins the first project window it reaches and keeps driving that one —
38
+ * `getCtx` is how it finds that window, not a per-message lookup.
39
+ *
40
+ * `senders` covers every live window instead, because the port channel is asked
41
+ * by renderers `getCtx` never names: the project list and background project
42
+ * windows all start automation clients of their own.
43
+ */
44
+ export async function startAutomationServer(getCtx, senders, port = 0) {
32
45
  const wss = new WebSocketServer({ port });
33
46
  const clients = new Set();
34
47
  await new Promise((resolve, reject) => {
@@ -39,125 +52,34 @@ export async function startAutomationServer(ctx, port = 0) {
39
52
  // ws always binds via an http.Server, so address() is AddressInfo here.
40
53
  const resolvedPort = typeof addr === 'object' && addr ? addr.port : port;
41
54
  currentPort = resolvedPort;
42
- // Gate AutomationChannel.GetPort with the workbench sender policy (only
43
- // the main renderer + workbench settings/popover overlays are allowed to
44
- // read the port see createWorkbenchSenderPolicy; the simulator webview
45
- // is intentionally NOT trusted for IPC invokes).
46
- const portIpc = new IpcRegistry(ctx.senderPolicy);
55
+ // Gated per OWNING window: each window trusts its own renderer plus its
56
+ // settings/popover overlays (see createWorkbenchSenderPolicy), and the
57
+ // simulator webview is trusted by none of them. Gating on the active
58
+ // window's policy instead would reject every window that does not currently
59
+ // have focus, starting with the project list.
60
+ const portIpc = new IpcRegistry(toIpcContextSource(senders));
47
61
  portIpc.handle(AutomationChannel.GetPort, () => {
48
62
  return currentPort;
49
63
  });
50
- function broadcast(event) {
51
- const msg = JSON.stringify(event);
52
- for (const ws of clients) {
53
- if (ws.readyState === ws.OPEN)
54
- ws.send(msg);
55
- }
56
- }
57
- // Native-host console forwarding: under native-host the page/service console
58
- // doesn't flow through the simulator guest's `ipc-message-host` channel (there
59
- // is no Worker/MiniApp in the guest). Instead the render-host / service-host
60
- // preloads post each console entry to main, bridge-router routes it to the
61
- // always-on ConsoleForwarder, and we SUBSCRIBE to rebroadcast every entry
62
- // (both layers) as an `App.logAdded` event — same shape the default-arch path
63
- // emits below in `setupConsoleForwarding`. We no longer set `ctx.guestConsole`
64
- // ourselves (the forwarder owns it); subscribing means render + service both
65
- // reach automation while the forwarder also mirrors render into the service
66
- // host's console for the embedded DevTools. Unsubscribed on close().
67
- const consoleSub = ctx.consoleForwarder?.subscribe((entry) => {
68
- const e = (entry ?? {});
69
- broadcast({
70
- method: 'App.logAdded',
71
- params: { type: e.level || 'log', args: e.args || [] },
72
- });
73
- });
74
- // Forward simulator console logs as App.logAdded events.
75
- // Track the polling interval + stop timer + currently-attached sim and
76
- // the named handler so we can fully detach on close() or sim destruction.
77
- // Without this the listener accumulates across create/dispose cycles and
78
- // across simulator rebuilds.
79
- let consoleForwardingSetup = false;
80
- let pollInterval = null;
81
- let pollStopTimer = null;
82
- let attachedSim = null;
83
- let ipcMessageHostHandler = null;
84
- // Handle for the destroyed-teardown owned by the sim's connection (replaces a
85
- // bespoke `sim.once('destroyed')` + manual removeListener). Disposing it on
86
- // graceful close()/detach releases the disposer from the connection segment so
87
- // it can't accumulate across automation create/dispose cycles on the same sim.
88
- let simDestroyedDisposer = null;
89
- function detachConsoleForwarding() {
90
- if (attachedSim) {
91
- if (ipcMessageHostHandler) {
92
- try {
93
- if (!attachedSim.isDestroyed()) {
94
- ;
95
- attachedSim.removeListener('ipc-message-host', ipcMessageHostHandler);
96
- }
97
- }
98
- catch { /* noop */ }
99
- }
100
- }
101
- // Release the connection-owned destroyed disposer (idempotent; safe even if
102
- // the sim is already destroyed — the disposer body only nulls local refs).
103
- try {
104
- simDestroyedDisposer?.dispose();
105
- }
106
- catch { /* noop */ }
107
- attachedSim = null;
108
- ipcMessageHostHandler = null;
109
- simDestroyedDisposer = null;
110
- }
111
- function setupConsoleForwarding() {
112
- if (consoleForwardingSetup)
113
- return;
114
- consoleForwardingSetup = true;
115
- pollInterval = setInterval(() => {
116
- const sim = getSimulator(ctx);
117
- if (!sim)
118
- return;
119
- if (pollInterval) {
120
- clearInterval(pollInterval);
121
- pollInterval = null;
122
- }
123
- const onIpcMessageHost = (_event, channel, data) => {
124
- if (channel === SimulatorChannel.Console) {
125
- const logData = data;
126
- broadcast({
127
- method: 'App.logAdded',
128
- params: { type: logData.level || 'log', args: logData.args || [] },
129
- });
130
- }
131
- };
132
- const onSimDestroyed = () => {
133
- // Sim webContents is dead — drop refs (removeListener on a destroyed
134
- // sender would throw) and allow re-attach to a new simulator.
135
- attachedSim = null;
136
- ipcMessageHostHandler = null;
137
- simDestroyedDisposer = null;
138
- consoleForwardingSetup = false;
139
- };
140
- attachedSim = sim;
141
- ipcMessageHostHandler = onIpcMessageHost;
142
- sim.on('ipc-message-host', onIpcMessageHost);
143
- // Route the destroyed-teardown through the per-webContents connection
144
- // registry: the connection arms a single `wc.once('destroyed')` and runs
145
- // owned disposers on hard-destroy (or soft reset for pool reuse), so we no
146
- // longer manage a bespoke 'destroyed' listener here.
147
- simDestroyedDisposer = ctx.connections.acquire(sim).own(onSimDestroyed);
148
- }, 1000);
149
- // Stop polling after 30s
150
- pollStopTimer = setTimeout(() => {
151
- if (pollInterval) {
152
- clearInterval(pollInterval);
153
- pollInterval = null;
154
- }
155
- pollStopTimer = null;
156
- }, 30000);
157
- }
64
+ // Every connection owns its console bridge, because every connection has its
65
+ // own target window: a single server-wide broadcast would hand one client the
66
+ // logs of a project another client is driving.
67
+ const bridges = new Set();
158
68
  wss.on('connection', (ws) => {
159
69
  clients.add(ws);
160
- setupConsoleForwarding();
70
+ const send = (msg) => {
71
+ if (ws.readyState === ws.OPEN)
72
+ ws.send(JSON.stringify(msg));
73
+ };
74
+ // Pinned, not resolved per message: the window a script drives is settled by
75
+ // the first project window this connection reaches, so a user clicking
76
+ // another project's window can't redirect it (see `createConnectionTarget`).
77
+ const target = createConnectionTarget(getCtx);
78
+ const consoleBridge = createConsoleBridge(() => target.peek(), getSimulator, send);
79
+ bridges.add(consoleBridge);
80
+ // Before any message: a client that only listens for `App.logAdded` never
81
+ // sends one, and still has to receive its target window's console.
82
+ consoleBridge.sync();
161
83
  ws.on('message', async (raw) => {
162
84
  let req;
163
85
  try {
@@ -174,6 +96,9 @@ export async function startAutomationServer(ctx, port = 0) {
174
96
  }
175
97
  else {
176
98
  try {
99
+ // Same target for the command and for the logs it produces.
100
+ const ctx = target.resolve();
101
+ consoleBridge.sync();
177
102
  const result = await handler(ctx, params);
178
103
  response = { id, result };
179
104
  }
@@ -181,27 +106,23 @@ export async function startAutomationServer(ctx, port = 0) {
181
106
  response = { id, error: { message: err.message || String(err) } };
182
107
  }
183
108
  }
184
- if (ws.readyState === ws.OPEN)
185
- ws.send(JSON.stringify(response));
109
+ send(response);
110
+ });
111
+ ws.on('close', () => {
112
+ clients.delete(ws);
113
+ bridges.delete(consoleBridge);
114
+ consoleBridge.dispose();
186
115
  });
187
- ws.on('close', () => clients.delete(ws));
188
116
  });
189
117
  return {
190
118
  close: () => {
191
- if (pollInterval) {
192
- clearInterval(pollInterval);
193
- pollInterval = null;
194
- }
195
- if (pollStopTimer) {
196
- clearTimeout(pollStopTimer);
197
- pollStopTimer = null;
198
- }
199
- detachConsoleForwarding();
200
- // Unsubscribe from the always-on ConsoleForwarder so a late guest console
201
- // entry can't broadcast against a torn-down server. The forwarder keeps
202
- // owning `ctx.guestConsole` (render→service mirroring continues without an
203
- // automation client). No-op if the forwarder was absent.
204
- consoleSub?.dispose();
119
+ // Detaches every connection's simulator listener and unsubscribes it from
120
+ // its window's ConsoleForwarder, so a late guest console entry can't fire
121
+ // against a torn-down server. The forwarder keeps owning `ctx.guestConsole`
122
+ // (render→service mirroring continues without an automation client).
123
+ for (const bridge of bridges)
124
+ bridge.dispose();
125
+ bridges.clear();
205
126
  for (const ws of clients) {
206
127
  try {
207
128
  ws.close();
@@ -5,7 +5,7 @@ export const defaultAdapter = {
5
5
  async openProject(opts) {
6
6
  const diminaKit = await import('@dimina-kit/devkit');
7
7
  const openProjectOpts = {
8
- outputDir: path.join(app.getPath('userData'), 'dimina-fe-output'),
8
+ outputRoot: path.join(app.getPath('userData'), 'dimina-fe-output'),
9
9
  ...opts,
10
10
  simulatorDir,
11
11
  };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The `/__fs` bridge's wire shapes, declared once for the producing side.
3
+ *
4
+ * Every shape here is consumed by browser code in `packages/workbench`
5
+ * (`fs-bridge.ts`'s `FsEntry`, `wal-audit-transport.ts`'s SSE message parse,
6
+ * and through them `@dimina-kit/fs-core/sync/watch-expander`'s stat-diffing).
7
+ * That side cannot import from here — devtools is the application, the
8
+ * workbench is a library it embeds, and the dependency runs one way only. So
9
+ * this module does not give the two ends a shared declaration; it gives the
10
+ * PRODUCING end exactly one, instead of hand-building untyped payloads at each
11
+ * call site. The two ends stay pinned to each other by the round-trip
12
+ * assertions in workbench-coi-server.test.ts and by disk-sync.spec.ts.
13
+ */
14
+ /**
15
+ * The `type` field of every readdir/stat payload. These are wire values, not
16
+ * an internal enum: the browser side compares the raw numbers (watch-expander
17
+ * treats `2` as "recurse into it"), so renumbering them breaks the editor's
18
+ * disk mirror.
19
+ */
20
+ export declare const FS_TYPE_FILE = 1;
21
+ export declare const FS_TYPE_DIR = 2;
22
+ /**
23
+ * One `/__fs/readdir` entry: `[name, type]` for a directory, `[name, type,
24
+ * size, mtimeMs]` for a file. The trailing stats let the sync engine tell a
25
+ * stat-unchanged survivor from an actually-modified file without re-reading
26
+ * its content. A file entry that raced a concurrent delete between readdir and
27
+ * stat has no stats to send and is TRUNCATED to `[name, 1]` rather than padded
28
+ * with the two values: a JSON array cannot hold a hole, so passing them
29
+ * through as `undefined` would put `null` on the wire, which neither the
30
+ * client's `size?: number` type nor any of the comments describing this format
31
+ * admit. Either way the client's `?? NaN` fallback treats the entry as
32
+ * "always changed"; truncating is what keeps the wire and the types honest.
33
+ */
34
+ export type FsBridgeEntry = [name: string, type: number, size?: number, mtimeMs?: number];
35
+ /** The `/__fs/stat` payload. Its `mtime` carries the same value readdir sends
36
+ * as `mtimeMs` — the two endpoints named it differently and no client reads
37
+ * this one, so the names are left as shipped rather than changed blind. */
38
+ export interface FsBridgeStat {
39
+ type: number;
40
+ size: number;
41
+ ctime: number;
42
+ mtime: number;
43
+ }
44
+ /** One `/__fs/watch` SSE message: a batch of changed project-relative paths,
45
+ * or the terminal notice that the watcher is gone and the client must fall
46
+ * back to a full rescan. */
47
+ export type FsWatchMessage = {
48
+ paths: string[];
49
+ } | {
50
+ watcherDead: true;
51
+ };
52
+ /** Serialize one SSE message into an `event-stream` frame. */
53
+ export declare function fsWatchFrame(message: FsWatchMessage): string;
54
+ /** Project one `readdirWithin` entry onto the readdir wire shape. */
55
+ export declare function toFsBridgeEntry(entry: {
56
+ name: string;
57
+ isDirectory: boolean;
58
+ size?: number;
59
+ mtimeMs?: number;
60
+ }): FsBridgeEntry;
61
+ //# sourceMappingURL=fs-bridge-protocol.d.ts.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The `/__fs` bridge's wire shapes, declared once for the producing side.
3
+ *
4
+ * Every shape here is consumed by browser code in `packages/workbench`
5
+ * (`fs-bridge.ts`'s `FsEntry`, `wal-audit-transport.ts`'s SSE message parse,
6
+ * and through them `@dimina-kit/fs-core/sync/watch-expander`'s stat-diffing).
7
+ * That side cannot import from here — devtools is the application, the
8
+ * workbench is a library it embeds, and the dependency runs one way only. So
9
+ * this module does not give the two ends a shared declaration; it gives the
10
+ * PRODUCING end exactly one, instead of hand-building untyped payloads at each
11
+ * call site. The two ends stay pinned to each other by the round-trip
12
+ * assertions in workbench-coi-server.test.ts and by disk-sync.spec.ts.
13
+ */
14
+ /**
15
+ * The `type` field of every readdir/stat payload. These are wire values, not
16
+ * an internal enum: the browser side compares the raw numbers (watch-expander
17
+ * treats `2` as "recurse into it"), so renumbering them breaks the editor's
18
+ * disk mirror.
19
+ */
20
+ export const FS_TYPE_FILE = 1;
21
+ export const FS_TYPE_DIR = 2;
22
+ /** Serialize one SSE message into an `event-stream` frame. */
23
+ export function fsWatchFrame(message) {
24
+ return `data: ${JSON.stringify(message)}\n\n`;
25
+ }
26
+ /** Project one `readdirWithin` entry onto the readdir wire shape. */
27
+ export function toFsBridgeEntry(entry) {
28
+ if (entry.isDirectory)
29
+ return [entry.name, FS_TYPE_DIR];
30
+ if (entry.size === undefined || entry.mtimeMs === undefined)
31
+ return [entry.name, FS_TYPE_FILE];
32
+ return [entry.name, FS_TYPE_FILE, entry.size, entry.mtimeMs];
33
+ }
34
+ //# sourceMappingURL=fs-bridge-protocol.js.map
@@ -28,6 +28,13 @@ declare function shouldReportWatchPath(rel: string): boolean;
28
28
  export declare const __testing: {
29
29
  shouldReportWatchPath: typeof shouldReportWatchPath;
30
30
  };
31
- export declare function handleFsWatchRequest(req: http.IncomingMessage, res: http.ServerResponse, getProjectRoot: () => string): void;
31
+ /**
32
+ * Returns the call that ends this stream from the server side, or `null` when
33
+ * the request was answered with an error instead of a stream. Nothing else can
34
+ * end it: the client is the only party that ever disconnects, so the server
35
+ * that outlives the request needs a handle to close the channel when IT is the
36
+ * one going away.
37
+ */
38
+ export declare function handleFsWatchRequest(req: http.IncomingMessage, res: http.ServerResponse, getProjectRoot: () => string): (() => void) | null;
32
39
  export {};
33
40
  //# sourceMappingURL=fs-watch-sse.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import nodeFs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { SKIP_DIRS } from '../ipc/project-fs.js';
4
+ import { fsWatchFrame } from './fs-bridge-protocol.js';
4
5
  import { jsonRes } from './http-json.js';
5
6
  /** Debounce window for batching change notifications (ms). */
6
7
  const WATCH_DEBOUNCE_MS = 80;
@@ -17,11 +18,18 @@ function shouldReportWatchPath(rel) {
17
18
  return !rel.split('/').some((segment) => SKIP_DIRS.has(segment));
18
19
  }
19
20
  export const __testing = { shouldReportWatchPath };
21
+ /**
22
+ * Returns the call that ends this stream from the server side, or `null` when
23
+ * the request was answered with an error instead of a stream. Nothing else can
24
+ * end it: the client is the only party that ever disconnects, so the server
25
+ * that outlives the request needs a handle to close the channel when IT is the
26
+ * one going away.
27
+ */
20
28
  export function handleFsWatchRequest(req, res, getProjectRoot) {
21
29
  const watchedRoot = getProjectRoot();
22
30
  if (!watchedRoot) {
23
31
  jsonRes(res, 409, { error: 'No active project', code: 'ENOACTIVE' });
24
- return;
32
+ return null;
25
33
  }
26
34
  let watcher;
27
35
  try {
@@ -29,7 +37,7 @@ export function handleFsWatchRequest(req, res, getProjectRoot) {
29
37
  }
30
38
  catch (e) {
31
39
  jsonRes(res, 500, { error: String(e) });
32
- return;
40
+ return null;
33
41
  }
34
42
  res.writeHead(200, {
35
43
  'Content-Type': 'text/event-stream; charset=utf-8',
@@ -55,7 +63,7 @@ export function handleFsWatchRequest(req, res, getProjectRoot) {
55
63
  return;
56
64
  const paths = [...pending];
57
65
  pending = new Set();
58
- res.write(`data: ${JSON.stringify({ paths })}\n\n`);
66
+ res.write(fsWatchFrame({ paths }));
59
67
  }
60
68
  function onChange(_event, filename) {
61
69
  // FSEvents overflow delivers a null filename ("something changed,
@@ -75,7 +83,7 @@ export function handleFsWatchRequest(req, res, getProjectRoot) {
75
83
  watcher.on('error', () => {
76
84
  if (closed)
77
85
  return;
78
- res.write(`data: ${JSON.stringify({ watcherDead: true })}\n\n`);
86
+ res.write(fsWatchFrame({ watcherDead: true }));
79
87
  cleanup();
80
88
  res.end();
81
89
  });
@@ -89,11 +97,17 @@ export function handleFsWatchRequest(req, res, getProjectRoot) {
89
97
  if (closed)
90
98
  return;
91
99
  if (getProjectRoot() !== watchedRoot) {
92
- res.write(`data: ${JSON.stringify({ watcherDead: true })}\n\n`);
100
+ res.write(fsWatchFrame({ watcherDead: true }));
93
101
  cleanup();
94
102
  res.end();
95
103
  }
96
104
  }, WATCH_DEBOUNCE_MS);
97
105
  req.on('close', cleanup);
106
+ return function endStream() {
107
+ if (closed)
108
+ return;
109
+ cleanup();
110
+ res.end();
111
+ };
98
112
  }
99
113
  //# sourceMappingURL=fs-watch-sse.js.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Which project window a CDP target belongs to.
3
+ *
4
+ * MCP keeps one `simulator` and one `workbench` connection for the whole app,
5
+ * so every connection has to record the window it ACTUALLY reached: one
6
+ * recorded as arrived is left alone, one recorded elsewhere keeps retrying
7
+ * until it lands on the window the user is in.
8
+ */
9
+ /** The per-window facts that decide which window a target belongs to. */
10
+ export interface TargetIdentityFacts {
11
+ activeBridgeId: string | null;
12
+ /**
13
+ * The resolved project path this window opened with. Fixed for the life of
14
+ * the window: a window names its project from the moment it exists, so
15
+ * attribution never has to wait for a compile to record anything.
16
+ */
17
+ projectPath: string;
18
+ getAppId: () => string | null;
19
+ }
20
+ export interface TargetBinding {
21
+ owner: object | null;
22
+ onTarget: boolean;
23
+ }
24
+ /** The value of query `key` in a target URL, or null when it carries none. */
25
+ export declare function targetQuery(url: string | undefined, key: string): string | null;
26
+ /**
27
+ * The window a chosen target belongs to, and whether that is the window MCP
28
+ * means to drive right now.
29
+ *
30
+ * A simulator target that names no window it can be attributed to is treated as
31
+ * the wrong window on purpose: it may well be another project's, and keeping the
32
+ * retries running is what moves the connection onto the active window's render
33
+ * guest as soon as that guest exists. The one target that legitimately names no
34
+ * window is the project list, which is the right workbench surface exactly
35
+ * while no project window is open.
36
+ */
37
+ export declare function connectionOwner(kind: 'simulator' | 'workbench', url: string | undefined, intended: object | null, windows: ReadonlyMap<object, TargetIdentityFacts>): TargetBinding;
38
+ //# sourceMappingURL=connection-owner.d.ts.map
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Which project window a CDP target belongs to.
3
+ *
4
+ * MCP keeps one `simulator` and one `workbench` connection for the whole app,
5
+ * so every connection has to record the window it ACTUALLY reached: one
6
+ * recorded as arrived is left alone, one recorded elsewhere keeps retrying
7
+ * until it lands on the window the user is in.
8
+ */
9
+ /** The value of query `key` in a target URL, or null when it carries none. */
10
+ export function targetQuery(url, key) {
11
+ if (!url)
12
+ return null;
13
+ try {
14
+ return new URL(url).searchParams.get(key);
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ function bind(owner, intended) {
21
+ return { owner, onTarget: owner === intended };
22
+ }
23
+ /** The window whose workbench renderer has this target's project open. */
24
+ function workbenchOwner(url, windows) {
25
+ const path = targetQuery(url, 'path');
26
+ if (!path)
27
+ return null;
28
+ for (const [candidate, facts] of windows) {
29
+ if (facts.projectPath === path)
30
+ return candidate;
31
+ }
32
+ return null;
33
+ }
34
+ /**
35
+ * The window this simulator surface belongs to: a render guest names it through
36
+ * its `bridgeId`, the localhost:7788 shell through the `appId` its route
37
+ * carries. Two windows answering to one appId — what two projects declaring the
38
+ * same appId produce — name no single window, so neither is returned.
39
+ */
40
+ function simulatorOwner(url, windows) {
41
+ const bridgeId = targetQuery(url, 'bridgeId');
42
+ if (bridgeId !== null) {
43
+ for (const [candidate, facts] of windows) {
44
+ if (facts.activeBridgeId === bridgeId)
45
+ return candidate;
46
+ }
47
+ return null;
48
+ }
49
+ const appId = targetQuery(url, 'appId');
50
+ if (appId === null)
51
+ return null;
52
+ let shell = null;
53
+ for (const [candidate, facts] of windows) {
54
+ if (facts.getAppId() !== appId)
55
+ continue;
56
+ if (shell)
57
+ return null;
58
+ shell = candidate;
59
+ }
60
+ return shell;
61
+ }
62
+ /**
63
+ * The window a chosen target belongs to, and whether that is the window MCP
64
+ * means to drive right now.
65
+ *
66
+ * A simulator target that names no window it can be attributed to is treated as
67
+ * the wrong window on purpose: it may well be another project's, and keeping the
68
+ * retries running is what moves the connection onto the active window's render
69
+ * guest as soon as that guest exists. The one target that legitimately names no
70
+ * window is the project list, which is the right workbench surface exactly
71
+ * while no project window is open.
72
+ */
73
+ export function connectionOwner(kind, url, intended, windows) {
74
+ const owner = kind === 'workbench' ? workbenchOwner(url, windows) : simulatorOwner(url, windows);
75
+ if (owner)
76
+ return bind(owner, intended);
77
+ return { owner: null, onTarget: kind === 'workbench' && intended === null };
78
+ }
79
+ //# sourceMappingURL=connection-owner.js.map