@weppy/roblox-mcp 2.0.6 → 2.0.7

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.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Weppy Roblox MCP — MCP server that lets AI coding agents control a live Roblox Studio session with 21 tools, 140+ actions, bidirectional sync, automated playtest, and multi-place support",
9
- "version": "2.0.6"
9
+ "version": "2.0.7"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "weppy-roblox-mcp",
14
14
  "source": "./plugins/weppy-roblox-mcp",
15
15
  "description": "Weppy Roblox MCP — MCP server that lets AI coding agents control a live Roblox Studio session with 21 tools, 140+ actions, bidirectional sync, automated playtest, and multi-place support",
16
- "version": "2.0.6",
16
+ "version": "2.0.7",
17
17
  "author": {
18
18
  "name": "hope1026"
19
19
  },
package/CHANGELOG.md CHANGED
@@ -18,6 +18,15 @@ All notable changes to this project will be documented in this file.
18
18
 
19
19
 
20
20
 
21
+
22
+ ## [2.0.7] - 2026-03-25
23
+
24
+ ### What's New
25
+
26
+ - Published Weppy Roblox Explorer to VS Code Marketplace and Open VSX Registry. Install directly from your editor's extension marketplace.
27
+ - Updated installation guide to reflect new marketplace links for Roblox Explorer.
28
+
29
+
21
30
  ## [2.0.6] - 2026-03-25
22
31
 
23
32
  ### Improved
package/README.md CHANGED
@@ -115,6 +115,7 @@ The MCP server provides a web dashboard where you can check connection status, t
115
115
 
116
116
  View the full instance tree of your Roblox Studio place directly inside VSCode. Navigate services, open synced scripts and property files, and track sync status — all without switching to Studio.
117
117
  Roblox Explorer is a companion VSCode extension for sync data generated by Weppy Roblox MCP. Tree browsing works from synced files, and live sync state or direction indicators are enhanced when the local MCP server is running.
118
+ Install from [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer) or [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer).
118
119
 
119
120
  ![Roblox Explorer — Studio instance tree displayed in VSCode sidebar](docs/assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
120
121
 
@@ -76,6 +76,10 @@ This optional extension requires the Roblox MCP setup above, because it reads `r
76
76
 
77
77
  👉 [Roblox Explorer Installation Guide](roblox-explorer.md)
78
78
 
79
+ Direct listings:
80
+ - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)
81
+ - [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)
82
+
79
83
  ---
80
84
 
81
85
  ## After Installation
@@ -1,9 +1,9 @@
1
- # Weepy Roblox Explorer (VSCode Extension)
1
+ # Weppy Roblox Explorer (VSCode Extension)
2
2
 
3
3
  Roblox Studio Explorer-like tree view for VSCode. Browse synced instance trees, open scripts directly, and track sync status — all inside your editor.
4
4
  This is a companion extension for Weppy Roblox MCP, not a standalone Roblox integration.
5
5
 
6
- ![Weepy Roblox Explorer](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
6
+ ![Weppy Roblox Explorer](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
7
7
 
8
8
  ## Requirements
9
9
 
@@ -13,9 +13,12 @@ This is a companion extension for Weppy Roblox MCP, not a standalone Roblox inte
13
13
 
14
14
  ## Installation
15
15
 
16
- Search for **Weepy Roblox Explorer** in the VSCode Extensions sidebar (`Ctrl+Shift+X` / `Cmd+Shift+X`) and click **Install**.
16
+ Search for **Weppy Roblox Explorer** in the VSCode Extensions sidebar (`Ctrl+Shift+X` / `Cmd+Shift+X`) and click **Install**.
17
17
 
18
- Or install directly from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer).
18
+ Or install directly from:
19
+
20
+ - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)
21
+ - [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)
19
22
 
20
23
  Core tree browsing works from synced files on disk. Live sync state, direction indicators, and Explorer telemetry forwarding are available when the local MCP server is reachable.
21
24
 
@@ -43,11 +46,11 @@ Core tree browsing works from synced files on disk. Live sync state, direction i
43
46
 
44
47
  | Command | Description |
45
48
  |---------|-------------|
46
- | `Weepy Roblox Explorer: Refresh` | Manually refresh the instance tree |
47
- | `Weepy Roblox Explorer: Search Instances` | Search instances across all services |
48
- | `Weepy Roblox Explorer: Open Backing File` | Open the file backing a selected instance |
49
- | `Weepy Roblox Explorer: Copy Instance Path` | Copy the full instance path (e.g. `game.Workspace.Part`) |
50
- | `Weepy Roblox Explorer: Reveal in Explorer` | Show the backing file in the default VSCode explorer |
49
+ | `Weppy Roblox Explorer: Refresh` | Manually refresh the instance tree |
50
+ | `Weppy Roblox Explorer: Search Instances` | Search instances across all services |
51
+ | `Weppy Roblox Explorer: Open Backing File` | Open the file backing a selected instance |
52
+ | `Weppy Roblox Explorer: Copy Instance Path` | Copy the full instance path (e.g. `game.Workspace.Part`) |
53
+ | `Weppy Roblox Explorer: Reveal in Explorer` | Show the backing file in the default VSCode explorer |
51
54
 
52
55
  ## Related
53
56
 
@@ -22,10 +22,10 @@ Default local path is `roblox-project-sync/place_{placeId}/explorer`.
22
22
 
23
23
  ### Browse sync data in VSCode
24
24
 
25
- Install the [Weepy Roblox Explorer](../installation/roblox-explorer.md) extension to browse the synced instance tree in VSCode, just like in Roblox Studio.
25
+ Install the [Weppy Roblox Explorer](../installation/roblox-explorer.md) extension to browse the synced instance tree in VSCode, just like in Roblox Studio.
26
26
  Explorer reads the sync files generated here, and it can show additional live sync state or direction details when the local MCP server is running.
27
27
 
28
- ![Weepy Roblox Explorer — browse synced instance tree in VSCode](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
28
+ ![Weppy Roblox Explorer — browse synced instance tree in VSCode](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
29
29
 
30
30
  - Service/instance tree with Roblox class icons
31
31
  - Click a script to open it for editing
package/docs/es/README.md CHANGED
@@ -106,6 +106,7 @@ El Dashboard web proporcionado por el servidor MCP permite consultar en tiempo r
106
106
 
107
107
  Visualiza el arbol completo de instancias de tu lugar en Roblox Studio directamente dentro de VSCode. Navega los servicios, abre scripts y archivos de propiedades sincronizados, y rastrea el estado de sincronizacion — todo sin cambiar a Studio.
108
108
  Roblox Explorer es una extension complementaria de VSCode para los datos de sync generados por Weppy Roblox MCP. El arbol base funciona con los archivos sincronizados en disco, y los indicadores en vivo de estado sync o direction se enriquecen cuando el servidor MCP local esta en ejecucion.
109
+ Instálalo desde [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer) o [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer).
109
110
 
110
111
  ![Roblox Explorer — arbol de instancias de Studio mostrado en la barra lateral de VSCode](../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
111
112
 
@@ -71,6 +71,10 @@ Esta extensión opcional requiere completar antes la instalación de Roblox MCP,
71
71
 
72
72
  👉 [Guía de instalación de Roblox Explorer](roblox-explorer.md)
73
73
 
74
+ Instalación directa:
75
+ - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)
76
+ - [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)
77
+
74
78
  ---
75
79
 
76
80
  ## Después de la Instalación
@@ -1,9 +1,9 @@
1
- # Weepy Roblox Explorer (Extensión VSCode)
1
+ # Weppy Roblox Explorer (Extensión VSCode)
2
2
 
3
3
  Vista de árbol de instancias similar al Explorer de Roblox Studio, dentro de VSCode. Navega instancias sincronizadas, abre scripts directamente y consulta el estado de sincronización sin salir del editor.
4
4
  Esta es una extensión complementaria para Weppy Roblox MCP, no una integración de Roblox independiente.
5
5
 
6
- ![Weepy Roblox Explorer](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
6
+ ![Weppy Roblox Explorer](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
7
7
 
8
8
  ## Requisitos
9
9
 
@@ -13,9 +13,12 @@ Esta es una extensión complementaria para Weppy Roblox MCP, no una integración
13
13
 
14
14
  ## Instalación
15
15
 
16
- Busca **Weepy Roblox Explorer** en la barra lateral de Extensiones de VSCode (`Ctrl+Shift+X` / `Cmd+Shift+X`) y haz clic en **Install**.
16
+ Busca **Weppy Roblox Explorer** en la barra lateral de Extensiones de VSCode (`Ctrl+Shift+X` / `Cmd+Shift+X`) y haz clic en **Install**.
17
17
 
18
- O instala directamente desde el [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer).
18
+ O instala directamente desde estos marketplaces:
19
+
20
+ - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)
21
+ - [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)
19
22
 
20
23
  La navegación básica del árbol funciona con los archivos sincronizados en disco. El estado sync en vivo, los indicadores de direction y el reenvío de telemetría del Explorer están disponibles cuando el servidor MCP local es accesible.
21
24
 
@@ -43,11 +46,11 @@ La navegación básica del árbol funciona con los archivos sincronizados en dis
43
46
 
44
47
  | Comando | Descripción |
45
48
  |---------|-------------|
46
- | `Weepy Roblox Explorer: Refresh` | Actualizar manualmente el árbol de instancias |
47
- | `Weepy Roblox Explorer: Search Instances` | Buscar instancias en todos los servicios |
48
- | `Weepy Roblox Explorer: Open Backing File` | Abrir el archivo de respaldo de una instancia seleccionada |
49
- | `Weepy Roblox Explorer: Copy Instance Path` | Copiar la ruta completa de la instancia (ej. `game.Workspace.Part`) |
50
- | `Weepy Roblox Explorer: Reveal in Explorer` | Mostrar el archivo en el explorador predeterminado de VSCode |
49
+ | `Weppy Roblox Explorer: Refresh` | Actualizar manualmente el árbol de instancias |
50
+ | `Weppy Roblox Explorer: Search Instances` | Buscar instancias en todos los servicios |
51
+ | `Weppy Roblox Explorer: Open Backing File` | Abrir el archivo de respaldo de una instancia seleccionada |
52
+ | `Weppy Roblox Explorer: Copy Instance Path` | Copiar la ruta completa de la instancia (ej. `game.Workspace.Part`) |
53
+ | `Weppy Roblox Explorer: Reveal in Explorer` | Mostrar el archivo en el explorador predeterminado de VSCode |
51
54
 
52
55
  ## Relacionado
53
56
 
@@ -22,10 +22,10 @@ La ruta local por defecto es `roblox-project-sync/place_{placeId}/explorer`.
22
22
 
23
23
  ### Explorar datos sincronizados en VSCode
24
24
 
25
- Instala la extension [Weepy Roblox Explorer](../installation/roblox-explorer.md) para explorar el arbol de instancias sincronizado en VSCode, igual que en Roblox Studio.
25
+ Instala la extension [Weppy Roblox Explorer](../installation/roblox-explorer.md) para explorar el arbol de instancias sincronizado en VSCode, igual que en Roblox Studio.
26
26
  Explorer lee los archivos sync generados aqui, y puede mostrar ademas estado sync en vivo e informacion de direction cuando el servidor MCP local esta en ejecucion.
27
27
 
28
- ![Weepy Roblox Explorer — explorar el arbol de instancias sincronizado en VSCode](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
28
+ ![Weppy Roblox Explorer — explorar el arbol de instancias sincronizado en VSCode](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
29
29
 
30
30
  - Arbol de servicios/instancias con iconos de clases Roblox
31
31
  - Haz clic en un script para abrirlo y editarlo
package/docs/id/README.md CHANGED
@@ -106,6 +106,7 @@ Dashboard berbasis web yang disediakan server MCP memungkinkan Anda melihat stat
106
106
 
107
107
  Lihat seluruh pohon instance dari place Roblox Studio langsung di dalam VSCode. Navigasi service, buka script dan file properti yang tersinkron, dan pantau status sinkronisasi — semua tanpa beralih ke Studio.
108
108
  Roblox Explorer adalah ekstensi pendamping VSCode untuk data sync yang dihasilkan oleh Weppy Roblox MCP. Penjelajahan tree dasar bekerja dari file yang sudah tersinkron di disk, dan indikator live untuk status sync atau direction menjadi lebih lengkap saat server MCP lokal sedang berjalan.
109
+ Instal dari [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer) atau [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer).
109
110
 
110
111
  ![Roblox Explorer — pohon instance Studio ditampilkan di sidebar VSCode](../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
111
112
 
@@ -76,6 +76,10 @@ Ekstensi opsional ini mengharuskan setup Roblox MCP di atas sudah selesai lebih
76
76
 
77
77
  👉 [Panduan Instalasi Roblox Explorer](roblox-explorer.md)
78
78
 
79
+ Instal langsung:
80
+ - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)
81
+ - [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)
82
+
79
83
  ---
80
84
 
81
85
  ## Setelah Instalasi Selesai
@@ -1,9 +1,9 @@
1
- # Weepy Roblox Explorer (Ekstensi VSCode)
1
+ # Weppy Roblox Explorer (Ekstensi VSCode)
2
2
 
3
3
  Tampilan pohon instansi seperti Explorer Roblox Studio, langsung di VSCode. Jelajahi instansi yang tersinkronisasi, buka skrip langsung, dan pantau status sinkronisasi tanpa meninggalkan editor.
4
4
  Ini adalah ekstensi pendamping untuk Weppy Roblox MCP, bukan integrasi Roblox yang berdiri sendiri.
5
5
 
6
- ![Weepy Roblox Explorer](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
6
+ ![Weppy Roblox Explorer](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
7
7
 
8
8
  ## Persyaratan
9
9
 
@@ -13,9 +13,12 @@ Ini adalah ekstensi pendamping untuk Weppy Roblox MCP, bukan integrasi Roblox ya
13
13
 
14
14
  ## Cara Instalasi
15
15
 
16
- Cari **Weepy Roblox Explorer** di sidebar Ekstensi VSCode (`Ctrl+Shift+X` / `Cmd+Shift+X`) dan klik **Install**.
16
+ Cari **Weppy Roblox Explorer** di sidebar Ekstensi VSCode (`Ctrl+Shift+X` / `Cmd+Shift+X`) dan klik **Install**.
17
17
 
18
- Atau instal langsung dari [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer).
18
+ Atau instal langsung dari marketplace berikut:
19
+
20
+ - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)
21
+ - [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)
19
22
 
20
23
  Penjelajahan tree dasar bekerja dari file sync di disk. Status sync live, indikator direction, dan pengiriman telemetri Explorer tersedia saat server MCP lokal dapat dijangkau.
21
24
 
@@ -43,11 +46,11 @@ Penjelajahan tree dasar bekerja dari file sync di disk. Status sync live, indika
43
46
 
44
47
  | Perintah | Deskripsi |
45
48
  |----------|-----------|
46
- | `Weepy Roblox Explorer: Refresh` | Perbarui pohon instansi secara manual |
47
- | `Weepy Roblox Explorer: Search Instances` | Cari instansi di semua layanan |
48
- | `Weepy Roblox Explorer: Open Backing File` | Buka file pendukung instansi yang dipilih |
49
- | `Weepy Roblox Explorer: Copy Instance Path` | Salin jalur instansi lengkap (contoh: `game.Workspace.Part`) |
50
- | `Weepy Roblox Explorer: Reveal in Explorer` | Tampilkan file di explorer VSCode default |
49
+ | `Weppy Roblox Explorer: Refresh` | Perbarui pohon instansi secara manual |
50
+ | `Weppy Roblox Explorer: Search Instances` | Cari instansi di semua layanan |
51
+ | `Weppy Roblox Explorer: Open Backing File` | Buka file pendukung instansi yang dipilih |
52
+ | `Weppy Roblox Explorer: Copy Instance Path` | Salin jalur instansi lengkap (contoh: `game.Workspace.Part`) |
53
+ | `Weppy Roblox Explorer: Reveal in Explorer` | Tampilkan file di explorer VSCode default |
51
54
 
52
55
  ## Terkait
53
56
 
@@ -22,10 +22,10 @@ Path lokal default adalah `roblox-project-sync/place_{placeId}/explorer`.
22
22
 
23
23
  ### Jelajahi data sync di VSCode
24
24
 
25
- Instal ekstensi [Weepy Roblox Explorer](../installation/roblox-explorer.md) untuk menjelajahi tree instance yang sudah tersinkron di VSCode, seperti di Roblox Studio.
25
+ Instal ekstensi [Weppy Roblox Explorer](../installation/roblox-explorer.md) untuk menjelajahi tree instance yang sudah tersinkron di VSCode, seperti di Roblox Studio.
26
26
  Explorer membaca file sync yang dihasilkan di sini, dan juga bisa menampilkan status sync live serta informasi direction saat server MCP lokal sedang berjalan.
27
27
 
28
- ![Weepy Roblox Explorer — jelajahi tree instance yang tersinkron di VSCode](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
28
+ ![Weppy Roblox Explorer — jelajahi tree instance yang tersinkron di VSCode](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
29
29
 
30
30
  - Tree service/instance dengan ikon kelas Roblox
31
31
  - Klik script untuk membukanya dan mengedit
package/docs/ja/README.md CHANGED
@@ -106,6 +106,7 @@ MCPサーバーが提供するWebダッシュボードで、接続状態、ツ
106
106
 
107
107
  Roblox Studioのインスタンスツリー全体をVSCode内で直接確認できます。サービスを探索し、同期されたスクリプトやプロパティファイルを開き、同期状態を追跡 — Studioに切り替える必要はありません。
108
108
  Roblox Explorerは、Weppy Roblox MCPが生成するsyncデータを扱うcompanion VSCode拡張です。基本のツリー閲覧は同期済みファイルだけで動作し、ローカルMCPサーバーが動作中ならlive sync状態やdirection表示がより正確に反映されます。
109
+ [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer) または [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer) からインストールできます。
109
110
 
110
111
  ![Roblox Explorer — VSCodeサイドバーに表示されるStudioインスタンスツリー](../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
111
112
 
@@ -76,6 +76,10 @@ GitHubからプラグインファイルをダウンロードして、Roblox Stud
76
76
 
77
77
  👉 [Roblox Explorerインストールガイド](roblox-explorer.md)
78
78
 
79
+ 直接インストール:
80
+ - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)
81
+ - [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)
82
+
79
83
  ---
80
84
 
81
85
  ## インストール後
@@ -1,9 +1,9 @@
1
- # Weepy Roblox Explorer (VSCode拡張)
1
+ # Weppy Roblox Explorer (VSCode拡張)
2
2
 
3
3
  Roblox StudioのExplorerと同じインスタンスツリーをVSCodeで表示できる拡張機能です。同期済みインスタンスの探索、スクリプトの直接開封、Sync状態の確認をエディタ内で行えます。
4
4
  この拡張はWeppy Roblox MCP用のcompanion拡張であり、単体のRoblox連携ツールではありません。
5
5
 
6
- ![Weepy Roblox Explorer](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
6
+ ![Weppy Roblox Explorer](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
7
7
 
8
8
  ## 要件
9
9
 
@@ -13,9 +13,12 @@ Roblox StudioのExplorerと同じインスタンスツリーをVSCodeで表示
13
13
 
14
14
  ## インストール方法
15
15
 
16
- VSCode Extensionsサイドバー(`Ctrl+Shift+X` / `Cmd+Shift+X`)で **Weepy Roblox Explorer** を検索し、**Install** をクリックします。
16
+ VSCode Extensionsサイドバー(`Ctrl+Shift+X` / `Cmd+Shift+X`)で **Weppy Roblox Explorer** を検索し、**Install** をクリックします。
17
17
 
18
- または [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer) から直接インストールできます。
18
+ または次のマーケットプレイスから直接インストールできます。
19
+
20
+ - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)
21
+ - [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)
19
22
 
20
23
  基本のツリー閲覧はディスク上のsyncファイルだけで動作します。ローカルMCPサーバーに接続できる場合は、live sync状態、direction表示、Explorerテレメトリ転送も利用できます。
21
24
 
@@ -43,11 +46,11 @@ VSCode Extensionsサイドバー(`Ctrl+Shift+X` / `Cmd+Shift+X`)で **Weepy
43
46
 
44
47
  | コマンド | 説明 |
45
48
  |---------|------|
46
- | `Weepy Roblox Explorer: Refresh` | インスタンスツリーを手動で更新 |
47
- | `Weepy Roblox Explorer: Search Instances` | 全サービスでインスタンスを検索 |
48
- | `Weepy Roblox Explorer: Open Backing File` | 選択したインスタンスのファイルを開く |
49
- | `Weepy Roblox Explorer: Copy Instance Path` | 完全なインスタンスパスをコピー(例: `game.Workspace.Part`) |
50
- | `Weepy Roblox Explorer: Reveal in Explorer` | デフォルトのVSCodeエクスプローラーでファイルを表示 |
49
+ | `Weppy Roblox Explorer: Refresh` | インスタンスツリーを手動で更新 |
50
+ | `Weppy Roblox Explorer: Search Instances` | 全サービスでインスタンスを検索 |
51
+ | `Weppy Roblox Explorer: Open Backing File` | 選択したインスタンスのファイルを開く |
52
+ | `Weppy Roblox Explorer: Copy Instance Path` | 完全なインスタンスパスをコピー(例: `game.Workspace.Part`) |
53
+ | `Weppy Roblox Explorer: Reveal in Explorer` | デフォルトのVSCodeエクスプローラーでファイルを表示 |
51
54
 
52
55
  ## 関連ドキュメント
53
56
 
@@ -22,10 +22,10 @@ Syncがない場合、AIはチャットに貼られた断片だけを見て判
22
22
 
23
23
  ### VSCodeで同期データを閲覧する
24
24
 
25
- [Weepy Roblox Explorer](../installation/roblox-explorer.md) 拡張機能をインストールすると、Roblox Studioと同じようにVSCode上で同期済みインスタンスツリーを閲覧できます。
25
+ [Weppy Roblox Explorer](../installation/roblox-explorer.md) 拡張機能をインストールすると、Roblox Studioと同じようにVSCode上で同期済みインスタンスツリーを閲覧できます。
26
26
  Explorerはここで生成されたsyncファイルを読み取り、ローカルMCPサーバーが動作中なら追加でlive sync状態とdirection情報も表示できます。
27
27
 
28
- ![Weepy Roblox Explorer — VSCodeで同期済みインスタンスツリーを閲覧](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
28
+ ![Weppy Roblox Explorer — VSCodeで同期済みインスタンスツリーを閲覧](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
29
29
 
30
30
  - Robloxクラスアイコン付きのサービス/インスタンスツリー
31
31
  - スクリプトをクリックして編集用に開く
package/docs/ko/README.md CHANGED
@@ -106,6 +106,7 @@ MCP 서버가 제공하는 웹 대시보드에서 연결 상태, 도구 실행
106
106
 
107
107
  Roblox Studio의 전체 인스턴스 트리를 VSCode 안에서 바로 확인할 수 있습니다. 서비스를 탐색하고, 동기화된 스크립트와 속성 파일을 열고, 동기화 상태를 추적하세요 — Studio로 전환할 필요 없이.
108
108
  Roblox Explorer는 Weppy Roblox MCP가 생성한 sync 데이터를 위한 companion VSCode 확장입니다. 기본 트리 탐색은 동기화된 파일만으로 동작하고, 로컬 MCP 서버가 실행 중이면 실시간 sync 상태와 direction 표시가 더 정확하게 반영됩니다.
109
+ 설치는 [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer) 또는 [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)에서 할 수 있습니다.
109
110
 
110
111
  ![Roblox Explorer — VSCode 사이드바에 표시되는 Studio 인스턴스 트리](../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
111
112
 
@@ -76,6 +76,10 @@ GitHub에서 플러그인 파일을 다운로드한 뒤, Roblox Studio의 Plugin
76
76
 
77
77
  👉 [Roblox Explorer 설치 가이드](roblox-explorer.md)
78
78
 
79
+ 바로 설치:
80
+ - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)
81
+ - [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)
82
+
79
83
  ---
80
84
 
81
85
  ## 설치 완료 후
@@ -1,9 +1,9 @@
1
- # Weepy Roblox Explorer (VSCode 확장)
1
+ # Weppy Roblox Explorer (VSCode 확장)
2
2
 
3
3
  Roblox Studio의 Explorer와 동일한 인스턴스 트리를 VSCode에서 볼 수 있는 확장입니다. 동기화된 인스턴스를 탐색하고, 스크립트를 바로 열고, Sync 상태를 에디터 안에서 확인할 수 있습니다.
4
4
  이 확장은 Weppy Roblox MCP용 companion 확장이며, 단독 Roblox 연동 도구는 아닙니다.
5
5
 
6
- ![Weepy Roblox Explorer](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
6
+ ![Weppy Roblox Explorer](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
7
7
 
8
8
  ## 요구 사항
9
9
 
@@ -13,9 +13,12 @@ Roblox Studio의 Explorer와 동일한 인스턴스 트리를 VSCode에서 볼
13
13
 
14
14
  ## 설치 방법
15
15
 
16
- VSCode Extensions 사이드바(`Ctrl+Shift+X` / `Cmd+Shift+X`)에서 **Weepy Roblox Explorer**를 검색하고 **Install**을 클릭합니다.
16
+ VSCode Extensions 사이드바(`Ctrl+Shift+X` / `Cmd+Shift+X`)에서 **Weppy Roblox Explorer**를 검색하고 **Install**을 클릭합니다.
17
17
 
18
- 또는 [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)에서 바로 설치할 수 있습니다.
18
+ 또는 아래 마켓플레이스에서 바로 설치할 수 있습니다.
19
+
20
+ - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)
21
+ - [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)
19
22
 
20
23
  기본 트리 탐색은 디스크에 있는 sync 파일만으로 동작합니다. 로컬 MCP 서버에 연결되면 실시간 sync 상태, direction 표시, Explorer 텔레메트리 전달 기능을 함께 사용할 수 있습니다.
21
24
 
@@ -43,11 +46,11 @@ VSCode Extensions 사이드바(`Ctrl+Shift+X` / `Cmd+Shift+X`)에서 **Weepy Rob
43
46
 
44
47
  | 명령어 | 설명 |
45
48
  |--------|------|
46
- | `Weepy Roblox Explorer: Refresh` | 인스턴스 트리 수동 새로고침 |
47
- | `Weepy Roblox Explorer: Search Instances` | 모든 서비스에서 인스턴스 검색 |
48
- | `Weepy Roblox Explorer: Open Backing File` | 선택한 인스턴스의 파일 열기 |
49
- | `Weepy Roblox Explorer: Copy Instance Path` | 전체 인스턴스 경로 복사 (예: `game.Workspace.Part`) |
50
- | `Weepy Roblox Explorer: Reveal in Explorer` | 기본 VSCode 탐색기에서 파일 표시 |
49
+ | `Weppy Roblox Explorer: Refresh` | 인스턴스 트리 수동 새로고침 |
50
+ | `Weppy Roblox Explorer: Search Instances` | 모든 서비스에서 인스턴스 검색 |
51
+ | `Weppy Roblox Explorer: Open Backing File` | 선택한 인스턴스의 파일 열기 |
52
+ | `Weppy Roblox Explorer: Copy Instance Path` | 전체 인스턴스 경로 복사 (예: `game.Workspace.Part`) |
53
+ | `Weppy Roblox Explorer: Reveal in Explorer` | 기본 VSCode 탐색기에서 파일 표시 |
51
54
 
52
55
  ## 관련 문서
53
56
 
@@ -22,10 +22,10 @@ Sync가 없으면 AI는 대화에 붙여 넣은 코드 일부만 보고 판단
22
22
 
23
23
  ### VSCode에서 Sync 데이터 탐색
24
24
 
25
- [Weepy Roblox Explorer](../installation/roblox-explorer.md) 확장을 설치하면, 동기화된 인스턴스 트리를 Roblox Studio와 동일한 형태로 VSCode에서 탐색할 수 있습니다.
25
+ [Weppy Roblox Explorer](../installation/roblox-explorer.md) 확장을 설치하면, 동기화된 인스턴스 트리를 Roblox Studio와 동일한 형태로 VSCode에서 탐색할 수 있습니다.
26
26
  Explorer는 여기서 생성된 sync 파일을 읽고, 로컬 MCP 서버가 실행 중이면 추가로 실시간 sync 상태와 direction 정보를 표시할 수 있습니다.
27
27
 
28
- ![Weepy Roblox Explorer — 동기화된 인스턴스 트리를 VSCode에서 탐색하는 모습](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
28
+ ![Weppy Roblox Explorer — 동기화된 인스턴스 트리를 VSCode에서 탐색하는 모습](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
29
29
 
30
30
  - 서비스/인스턴스 트리를 Roblox 클래스 아이콘과 함께 표시
31
31
  - 스크립트 파일을 클릭하면 바로 편집 가능
@@ -106,6 +106,7 @@ No dashboard web fornecido pelo servidor MCP, acompanhe em tempo real o status d
106
106
 
107
107
  Visualize a arvore completa de instancias do seu lugar no Roblox Studio diretamente dentro do VSCode. Navegue pelos servicos, abra scripts e arquivos de propriedades sincronizados, e acompanhe o status de sincronizacao — tudo sem trocar para o Studio.
108
108
  Roblox Explorer e uma extensao complementar do VSCode para os dados de sync gerados pelo Weppy Roblox MCP. A navegacao basica da arvore funciona a partir dos arquivos sincronizados em disco, e os indicadores ao vivo de status sync ou direction ficam mais completos quando o servidor MCP local esta em execucao.
109
+ Instale pela [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer) ou pela [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer).
109
110
 
110
111
  ![Roblox Explorer — arvore de instancias do Studio exibida na barra lateral do VSCode](../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
111
112
 
@@ -76,6 +76,10 @@ Esta extensao opcional exige que a instalacao do Roblox MCP acima ja esteja conc
76
76
 
77
77
  👉 [Guia de instalação do Roblox Explorer](roblox-explorer.md)
78
78
 
79
+ Instalação direta:
80
+ - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)
81
+ - [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)
82
+
79
83
  ---
80
84
 
81
85
  ## Depois da instalação
@@ -1,9 +1,9 @@
1
- # Weepy Roblox Explorer (Extensão VSCode)
1
+ # Weppy Roblox Explorer (Extensão VSCode)
2
2
 
3
3
  Visualização em árvore de instâncias similar ao Explorer do Roblox Studio, dentro do VSCode. Navegue por instâncias sincronizadas, abra scripts diretamente e acompanhe o status de sincronização sem sair do editor.
4
4
  Esta é uma extensao complementar do Weppy Roblox MCP, e não uma integração Roblox independente.
5
5
 
6
- ![Weepy Roblox Explorer](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
6
+ ![Weppy Roblox Explorer](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
7
7
 
8
8
  ## Requisitos
9
9
 
@@ -13,9 +13,12 @@ Esta é uma extensao complementar do Weppy Roblox MCP, e não uma integração R
13
13
 
14
14
  ## Instalação
15
15
 
16
- Pesquise **Weepy Roblox Explorer** na barra lateral de Extensões do VSCode (`Ctrl+Shift+X` / `Cmd+Shift+X`) e clique em **Install**.
16
+ Pesquise **Weppy Roblox Explorer** na barra lateral de Extensões do VSCode (`Ctrl+Shift+X` / `Cmd+Shift+X`) e clique em **Install**.
17
17
 
18
- Ou instale diretamente pelo [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer).
18
+ Ou instale diretamente por um destes marketplaces:
19
+
20
+ - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer)
21
+ - [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer)
19
22
 
20
23
  A navegação basica da arvore funciona a partir dos arquivos sincronizados em disco. Status sync ao vivo, indicadores de direction e envio de telemetria do Explorer ficam disponiveis quando o servidor MCP local está acessivel.
21
24
 
@@ -43,11 +46,11 @@ A navegação basica da arvore funciona a partir dos arquivos sincronizados em d
43
46
 
44
47
  | Comando | Descrição |
45
48
  |---------|-----------|
46
- | `Weepy Roblox Explorer: Refresh` | Atualizar manualmente a árvore de instâncias |
47
- | `Weepy Roblox Explorer: Search Instances` | Pesquisar instâncias em todos os serviços |
48
- | `Weepy Roblox Explorer: Open Backing File` | Abrir o arquivo de suporte de uma instância selecionada |
49
- | `Weepy Roblox Explorer: Copy Instance Path` | Copiar o caminho completo da instância (ex. `game.Workspace.Part`) |
50
- | `Weepy Roblox Explorer: Reveal in Explorer` | Mostrar o arquivo no explorador padrão do VSCode |
49
+ | `Weppy Roblox Explorer: Refresh` | Atualizar manualmente a árvore de instâncias |
50
+ | `Weppy Roblox Explorer: Search Instances` | Pesquisar instâncias em todos os serviços |
51
+ | `Weppy Roblox Explorer: Open Backing File` | Abrir o arquivo de suporte de uma instância selecionada |
52
+ | `Weppy Roblox Explorer: Copy Instance Path` | Copiar o caminho completo da instância (ex. `game.Workspace.Part`) |
53
+ | `Weppy Roblox Explorer: Reveal in Explorer` | Mostrar o arquivo no explorador padrão do VSCode |
51
54
 
52
55
  ## Relacionado
53
56
 
@@ -22,10 +22,10 @@ O caminho local padrao e `roblox-project-sync/place_{placeId}/explorer`.
22
22
 
23
23
  ### Explorar dados sincronizados no VSCode
24
24
 
25
- Instale a extensao [Weepy Roblox Explorer](../installation/roblox-explorer.md) para explorar a arvore de instancias sincronizada no VSCode, assim como no Roblox Studio.
25
+ Instale a extensao [Weppy Roblox Explorer](../installation/roblox-explorer.md) para explorar a arvore de instancias sincronizada no VSCode, assim como no Roblox Studio.
26
26
  O Explorer le os arquivos de sync gerados aqui e tambem pode mostrar status sync ao vivo e informacoes de direction quando o servidor MCP local estiver em execucao.
27
27
 
28
- ![Weepy Roblox Explorer — explorar a arvore de instancias sincronizada no VSCode](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
28
+ ![Weppy Roblox Explorer — explorar a arvore de instancias sincronizada no VSCode](../../assets/screenshots/roblox-explorer/roblox-explorer-screen.png)
29
29
 
30
30
  - Arvore de servicos/instancias com icones de classes Roblox
31
31
  - Clique em um script para abri-lo e editar
package/llms-full.txt CHANGED
@@ -201,7 +201,7 @@ In the agent pane, click ⋯ → MCP Servers → Manage MCP Servers → View raw
201
201
 
202
202
  Browse synced instance trees inside VSCode with Roblox class icons.
203
203
 
204
- Search for **Weepy Roblox Explorer** in the VSCode Extensions sidebar and click Install, or install from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer).
204
+ Search for **Weppy Roblox Explorer** in the VSCode Extensions sidebar and click Install, or install from [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer) or [Open VSX](https://open-vsx.org/extension/weppy/weppy-roblox-explorer).
205
205
 
206
206
  Features:
207
207
  - Instance tree with Roblox class icons (dark/light theme)
package/llms.txt CHANGED
@@ -66,5 +66,7 @@ Or manually:
66
66
 
67
67
  - GitHub: https://github.com/hope1026/weppy-roblox-mcp
68
68
  - npm: https://www.npmjs.com/package/@weppy/roblox-mcp
69
+ - Roblox Explorer (VS Code Marketplace): https://marketplace.visualstudio.com/items?itemName=weppy.weppy-roblox-explorer
70
+ - Roblox Explorer (Open VSX): https://open-vsx.org/extension/weppy/weppy-roblox-explorer
69
71
  - Issues: https://github.com/hope1026/weppy-roblox-mcp/issues
70
72
  - Discussions: https://github.com/hope1026/weppy-roblox-mcp/discussions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weppy/roblox-mcp",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "MCP (Model Context Protocol) server for Roblox Studio integration - enables AI coding agents to interact with Roblox Studio in real-time",
5
5
  "main": "plugins/weppy-roblox-mcp/dist/index.js",
6
6
  "type": "module",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weppy-roblox-mcp",
3
3
  "description": "MCP server for Roblox Studio integration - AI-powered game development with specialized agents and skills",
4
- "version": "2.0.6",
4
+ "version": "2.0.7",
5
5
  "author": {
6
6
  "name": "hope1026"
7
7
  },
@@ -123,7 +123,7 @@ data: ${JSON.stringify(r)}
123
123
  `).filter(v=>v.length>0)}catch(h){if(h.code==="ENOENT"){r.status(200).json({entries:[],total:0,hasMore:!1});return}throw h}let p=[];for(let h=u.length-1;h>=0;h--)try{let v=JSON.parse(u[h]);if(o&&v.direction!==o||s&&v.type!==s)continue;p.push(v)}catch{continue}let d=p.length,m={entries:p.slice(a,a+i),total:d,hasMore:a+i<d};r.status(200).json(m)}getStatusSummary(){if(this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0)return{active:!0,placeId:this.ctx.activeFullSyncPlaceId};for(let[e,r]of this.ctx.places.entries())if(r.state==="syncing")return{active:!0,placeId:e};return{active:!1}}getDirectionForCategory(e){let r;if(this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0&&(r=this.ctx.places.get(this.ctx.activeFullSyncPlaceId)),!r){let i=this.ctx.getDefaultRuntimePlaceId();i!=null&&(r=this.ctx.places.get(i))}if(!r)return"forward";let n=e;return r.directions[n]??"forward"}getStatusDirect(e){let r=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(r==null)return{state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.ctx.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0,applyModes:{...Li}};let n=this.ctx.places.get(r);if(!n)return{state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.ctx.config.getPlaceRoot(r),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0,applyModes:{...Li}};let i=n.fileWatcher?.getPendingCount()??0;return{state:n.state,instanceCount:n.instanceCount,scriptCount:n.scriptCount,lastFullSync:n.lastFullSync,lastIncrementalSync:n.lastIncrementalSync,syncRoot:this.ctx.config.getPlaceRoot(r),activeClientId:n.activeClientId,reverseSyncAvailable:i>0,modifiedFileCount:i,applyModes:{...n.applyModes}}}getConfigDirect(){return this.ctx.config.getConfig()}async getHistoryDirect(e,r){let n=parseInt(e,10),i=this.ctx.places.get(n);i&&await i.writer.flushHistory();let a=Math.min(Math.max(r?.limit??50,1),200),o=Math.max(r?.offset??0,0),s=this.ctx.config.getHistoryPath(n),c=[];try{c=(await Eo.readFile(s,"utf-8")).split(`
124
124
  `).filter(f=>f.length>0)}catch(d){if(d.code==="ENOENT")return{entries:[],total:0,hasMore:!1};throw d}let l=[];for(let d=c.length-1;d>=0;d--)try{l.push(JSON.parse(c[d]))}catch{continue}let u=l.length;return{entries:l.slice(o,o+a),total:u,hasMore:o+a<u}}getDirectionsDirect(e){let r=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(r==null)return{...Mi};let n=this.ctx.places.get(r);return n?{...n.directions}:{...Mi}}getProgressDirect(e){let r=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(r==null)return{state:"idle",isSyncing:!1};let n=this.ctx.places.get(r);if(!n)return{state:"idle",isSyncing:!1};if(!n.syncProgress)return{state:n.state,isSyncing:!1,lastSync:{instanceCount:n.instanceCount,scriptCount:n.scriptCount,completedAt:n.lastFullSync}};let i=n.syncProgress,a=Date.now()-i.syncStartTime,o=i.totalInstances>0?Math.min(100,Math.round(i.processedInstances/i.totalInstances*100)):0,s;if(i.processedInstances>0&&o<100){let l=a/i.processedInstances,u=i.totalInstances-i.processedInstances;s=Math.round(l*u)}let c=a>0?Math.round(i.bytesReceived/a*1e3):0;return{state:n.state,isSyncing:!0,progressPercent:o,currentService:i.currentService,currentChunk:{index:i.currentChunkIndex,total:i.currentTotalChunks},instances:{processed:i.processedInstances,total:i.totalInstances},services:{processed:i.processedServices,total:i.totalServices},elapsedMs:a,estimatedRemainingMs:s,bytesReceived:i.bytesReceived,bytesPerSecond:c}}async readSyncedFile(e,r){let n=parseInt(e,10),i=this.ctx.places.get(n);if(!i)throw new Error(`Place ${e} not found in sync cache`);let a=i.index.resolvePropsPath(r);try{return{content:await Eo.readFile(a,"utf-8"),path:a}}catch(s){if(s.code!=="ENOENT")throw s}for(let s of bo){let c=i.index.resolveScriptPath(r,s,!1);try{return{content:await Eo.readFile(c,"utf-8"),path:c}}catch{continue}}let o=i.index.resolveValuePath(r);try{return{content:await Eo.readFile(o,"utf-8"),path:o}}catch(s){if(s.code!=="ENOENT")throw s}throw new Error(`No synced file found for instance: ${r}`)}async writeSyncedFile(e,r,n){let i=parseInt(e,10),a=this.ctx.places.get(i);if(!a)throw new Error(`Place ${e} not found in sync cache`);await a.writer.writeScript(r,"Script",n,!1)}async executeViaDisk(e,r){let n=this.ctx.getDefaultRuntimePlaceId();if(n==null)throw new Error("No active sync place for disk execution");let i=this.ctx.places.get(n);if(!i)throw new Error(`Place ${n} not found in sync cache`);switch(e){case"set_script_source":{let a=r.scriptType||r.className||"Script";return await i.writer.writeScript(r.path,a,r.source,!1),{success:!0,path:r.path}}case"set_property":return await i.writer.writeProps(r.path,{className:r.className||"Instance",name:$t(r.path),properties:{[r.property]:r.value}}),{success:!0,path:r.path};default:throw new Error(`Disk execution not supported for action: ${e}`)}}};import Pe from"path";import{randomUUID as rQ}from"crypto";import{promises as Wt}from"fs";ue();var bf=class{constructor(e){this.ctx=e}preserveLocalFilesMap=new Map;pendingServiceTrees=new Map;async handleInitStart(e,r,n){if(r.previousPlaceId!==void 0&&r.previousPlaceId!==e&&(g.info("Place promotion detected",{from:r.previousPlaceId,to:e}),await this.ctx.config.promotePlaceRoot(r.previousPlaceId,e,r.placeName),this.ctx.places.get(r.previousPlaceId)&&this.ctx.places.delete(r.previousPlaceId),this.ctx.activeFullSyncPlaceId===r.previousPlaceId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(r.previousPlaceId)),this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0&&this.ctx.activeFullSyncPlaceId!==e){n.status(409).json({error:"Conflict",message:`Place ${this.ctx.activeFullSyncPlaceId} is currently syncing. Only one place can sync at a time.`});return}let i=await this.ctx.getOrCreatePlaceContext(e,r.placeName);if(i.activeClientId&&i.activeClientId!==r.clientId&&i.activeFullSyncSessionId!==null){n.status(409).json({error:"Conflict",message:`Another client (${i.activeClientId}) is currently syncing this place`});return}if(this.ctx.activeFullSyncPlaceId=e,this.ctx.touchRuntimePlace(e),i.activeClientId=r.clientId,i.placeName=r.placeName,r.directions&&typeof r.directions=="object"){let p=r.directions,d=f=>f==="forward"||f==="reverse"||f==="bidirectional";d(p.scripts)&&(i.directions.scripts=p.scripts),d(p.values)&&(i.directions.values=p.values),d(p.containers)&&(i.directions.containers=p.containers),d(p.data)&&(i.directions.data=p.data),d(p.services)&&(i.directions.services=p.services),g.info("Sync directions received",{placeId:e,directions:i.directions})}else i.directions={...Mi};if(r.applyModes&&typeof r.applyModes=="object"){let p=r.applyModes,d=f=>f==="auto"||f==="manual";d(p.scripts)&&(i.applyModes.scripts=p.scripts),d(p.values)&&(i.applyModes.values=p.values),d(p.containers)&&(i.applyModes.containers=p.containers),d(p.data)&&(i.applyModes.data=p.data),d(p.services)&&(i.applyModes.services=p.services)}else i.applyModes={...Li};i.forwardRestoreQueue=[];let a=rQ();i.activeFullSyncSessionId=a,this.pendingServiceTrees.set(a,new Map),i.instanceCount=0,i.scriptCount=0;let o=this.ctx.config.getPlaceRoot(e),s=Pe.join(o,`explorer_tmp_${a}`);await Wt.mkdir(s,{recursive:!0}),i.tmpIndex=new Qn(o,s),i.tmpWriter=new wo(this.ctx.config,i.tmpIndex,e),i.collisionDirMap=new Map;let c=r.preserveLocalFiles;Array.isArray(c)&&c.length>0&&(this.setPreserveLocalFiles(a,c),g.info("PreserveLocalFiles set for sync",{syncId:a,fileCount:c.length}));let l={version:1,placeId:r.placeId,placeName:r.placeName,lastFullSync:null,lastIncrementalSync:null,instanceCount:0,scriptCount:0,syncMode:"mirror"},u=Pe.join(o,".sync-meta.json");await Wt.mkdir(Pe.dirname(u),{recursive:!0}),await this.ctx.atomicWriteFile(u,JSON.stringify(l,null,2)+`
125
125
  `),this.startTTLTimerForPlace(i,a),i.state="initializing",i.index.resetNameCounters(),i.syncProgress={syncStartTime:Date.now(),totalInstances:r.totalInstances,totalServices:r.totalServices,processedInstances:0,processedServices:0,currentService:null,currentChunkIndex:0,currentTotalChunks:0,bytesReceived:0,processedChunks:0},i.writer.appendChangeLog(`FULL_SYNC_START clientId=${r.clientId} placeId=${r.placeId}`),i.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncStart",direction:"forward",path:`place_${r.placeId}`,details:`services:${r.totalServices} instances:${r.totalInstances}`}),g.info("Full sync started",{syncId:a,clientId:r.clientId,placeId:r.placeId,placeName:r.placeName,totalServices:r.totalServices,totalInstances:r.totalInstances}),n.status(200).json({status:"started",syncId:i.activeFullSyncSessionId})}async handleInitChunk(e,r,n){let i=this.ctx.places.get(e);if(!i||!i.activeFullSyncSessionId||!i.tmpIndex||!i.tmpWriter){n.status(400).json({error:"No active sync session",message:"Call sync/init with phase=start first"});return}if(i.activeClientId&&r.clientId&&i.activeClientId!==r.clientId){n.status(409).json({error:"Conflict",message:`This sync session belongs to client ${i.activeClientId}`});return}let a=i.activeFullSyncSessionId,o=this.getOrCreatePendingServiceTree(a,r),s=0,c=i.collisionDirMap;for(let u of r.instances){if(this.ctx.config.isForbiddenPath(u.path))continue;let p=i.tmpIndex.resolveParentDir(u.path),d=c.get(p)??p,{resolved:f,retroactiveRename:m}=i.tmpIndex.registerCollision(u.path,u.siblingIndex??void 0,d);m&&(await ei(i.tmpIndex,d,m.from,m.to),this.rewritePendingEffectivePaths(o,i.tmpIndex.getExplorerRoot(),Pe.join(d,m.from),Pe.join(d,m.to)));let h=i.tmpIndex.resolveChildrenDir(u.path),v=Pe.join(d,f);v!==h&&c.set(h,v);let b=i.tmpIndex.sanitizeName(u.name),w=u;if(d!==p||f!==b){let k=i.tmpIndex.getExplorerRoot(),$=Pe.relative(k,d).split(Pe.sep).filter(A=>A.length>0),O=Je(["game",...$,f]);w={...u,path:O}}let _=await i.tmpWriter.writeInstance(w);i.tmpIndex.setClassName(u.path,u.className,u.siblingIndex),s++,(_.propsWritten||_.valueWritten)&&i.instanceCount++,_.scriptWritten&&i.scriptCount++,o.instances.push({effectivePath:w.path,originalPath:u.path,className:u.className})}let l=this.isLastChunk(o,r.chunkIndex,r.totalChunks);if(l){let u=this.buildServiceTree(i,o);await i.tmpWriter.writeTree(r.serviceName,u);let p=this.pendingServiceTrees.get(a);p?.delete(r.serviceName),p&&p.size===0&&this.pendingServiceTrees.delete(a)}i.syncProgress&&(i.syncProgress.processedInstances+=s,i.syncProgress.currentService=r.serviceName,i.syncProgress.currentChunkIndex=r.chunkIndex,i.syncProgress.currentTotalChunks=r.totalChunks,i.syncProgress.processedChunks++,i.syncProgress.bytesReceived+=JSON.stringify(r).length,l&&i.syncProgress.processedServices++),g.debug("Sync chunk processed",{placeId:e,serviceName:r.serviceName,chunkIndex:r.chunkIndex,totalChunks:r.totalChunks,processed:s}),n.status(200).json({processed:s,service:r.serviceName})}async handleInitComplete(e,r,n){let i=this.ctx.places.get(e);if(!i||!i.activeFullSyncSessionId){n.status(400).json({error:"No active sync session",message:"Call sync/init with phase=start first"});return}if(i.activeClientId&&r.clientId&&i.activeClientId!==r.clientId){n.status(409).json({error:"Conflict",message:`This sync session belongs to client ${i.activeClientId}`});return}let a=i.activeFullSyncSessionId,o=this.ctx.config.getPlaceRoot(e),s=Pe.join(o,"explorer"),c=Pe.join(o,`explorer_tmp_${a}`),l=this.getAndClearPreserveLocalFiles(a),u=new Map,p=[];if(l.length>0){for(let m of l){let h=Pe.resolve(o,m);try{let v=await Wt.readFile(h,"utf-8");u.set(m,v)}catch{p.push(m)}}g.info("Backed up local files for preservation",{placeId:e,requested:l.length,backed:u.size,deleted:p.length})}try{await Wt.rm(s,{recursive:!0,force:!0})}catch{}await Wt.rename(c,s),i.instanceCount=r.instanceCount,i.scriptCount=r.scriptCount,i.lastFullSync=new Date().toISOString();let d={version:1,placeId:i.placeId,placeName:i.placeName,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,instanceCount:i.instanceCount,scriptCount:i.scriptCount,syncMode:"mirror"},f=Pe.join(o,".sync-meta.json");if(await this.ctx.atomicWriteFile(f,JSON.stringify(d,null,2)+`
126
- `),this.clearTTLTimerForPlace(i,a),i.index.clearAllHashes(),i.index.clearClassMappings(),i.tmpIndex){let m=i.tmpIndex.getExplorerRoot(),h=i.index.getExplorerRoot();for(let[v,b]of i.tmpIndex.getAllHashes()){let w=Pe.relative(m,v),_=Pe.resolve(h,w);i.index.updateHashByValue(_,b)}for(let[v,b]of i.tmpIndex.getAllFileHashes()){let w=Pe.relative(m,v),_=Pe.resolve(h,w);i.index.updateFileHashByValue(_,b)}i.index.resetNameCounters(),i.index.mergeNameMappingsFrom(i.tmpIndex)}if(i.tmpIndex=null,i.tmpWriter&&(i.tmpWriter.stopChangeLogFlusher(),i.tmpWriter=null),this.pendingServiceTrees.delete(a),i.collisionDirMap=null,await i.index.saveToDisk(),i.state="syncing",i.activeFullSyncSessionId=null,i.syncProgress=null,this.ctx.touchRuntimePlace(e),this.ctx.activeFullSyncPlaceId=null,await this.ctx.startFileWatcherForPlace(i),i.fileWatcher&&await i.fileWatcher.waitUntilReady(),u.size>0){for(let[m,h]of u){let v=Pe.resolve(o,m);try{await Wt.mkdir(Pe.dirname(v),{recursive:!0}),await Wt.writeFile(v,h,"utf-8")}catch(b){g.warn("Failed to restore preserved file",{path:m,error:b instanceof Error?b.message:String(b)})}}g.info("Restored preserved local files",{placeId:e,count:u.size})}if(p.length>0){let m=0;for(let h of p){let v=Pe.resolve(o,h);try{await Wt.unlink(v),i.index.removeHash(v),m++}catch(b){b.code!=="ENOENT"&&g.warn("Failed to delete preserved-as-deleted file",{path:h,error:b instanceof Error?b.message:String(b)})}}m>0&&(await i.index.saveToDisk(),g.info("Deleted locally-removed files from new sync",{placeId:e,count:m}))}i.writer.appendChangeLog(`FULL_SYNC_COMPLETE instances=${i.instanceCount} scripts=${i.scriptCount}`),i.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncComplete",direction:"forward",path:`place_${e}`,details:`instances:${i.instanceCount} scripts:${i.scriptCount}`}),g.info("Full sync completed",{placeId:e,instanceCount:i.instanceCount,scriptCount:i.scriptCount}),n.status(200).json({status:"completed",instanceCount:i.instanceCount,scriptCount:i.scriptCount,syncRoot:this.ctx.config.getPlaceRoot(e)})}setPreserveLocalFiles(e,r){this.preserveLocalFilesMap.set(e,r)}getAndClearPreserveLocalFiles(e){let r=this.preserveLocalFilesMap.get(e)||[];return this.preserveLocalFilesMap.delete(e),r}clearPreserveLocalFiles(e){this.preserveLocalFilesMap.delete(e)}clearPendingServiceTrees(e){this.pendingServiceTrees.delete(e)}startTTLTimerForPlace(e,r){let n=setTimeout(async()=>{g.warn("Incomplete sync TTL expired, cleaning up",{placeId:e.placeId,syncId:r});let i=this.ctx.config.getPlaceRoot(e.placeId),a=Pe.join(i,`explorer_tmp_${r}`);try{await Wt.rm(a,{recursive:!0,force:!0})}catch(o){g.error("Failed to clean up expired temp dir",o instanceof Error?o:new Error(String(o)))}e.incompleteSyncTimer=null,e.activeFullSyncSessionId===r&&(e.activeFullSyncSessionId=null,e.activeClientId=null,e.state="idle",e.tmpIndex=null,this.pendingServiceTrees.delete(r),e.collisionDirMap=null,e.tmpWriter&&(e.tmpWriter.stopChangeLogFlusher(),e.tmpWriter=null),this.ctx.activeFullSyncPlaceId===e.placeId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(e.placeId))},iD);n&&typeof n=="object"&&"unref"in n&&n.unref(),e.incompleteSyncTimer=n}getOrCreatePendingServiceTree(e,r){let n=this.pendingServiceTrees.get(e);n||(n=new Map,this.pendingServiceTrees.set(e,n));let i=n.get(r.serviceName);if(i)return i;let a={serviceName:r.tree?.name??r.serviceName,serviceClassName:r.tree?.className??r.serviceClassName,zeroBasedChunkIndex:r.chunkIndex===0,instances:[]};return n.set(r.serviceName,a),a}isLastChunk(e,r,n){return n<=1?!0:e.zeroBasedChunkIndex?r>=n-1:r>=n}buildServiceTree(e,r){let n={name:r.serviceName,className:r.serviceClassName,childCount:0,children:[],syncedAt:new Date().toISOString()};for(let i of r.instances){let a=this.resolveEffectiveSegments(e,i.effectivePath),o=ft(i.originalPath);this.upsertTreeNode(n,a,o,i.className)}return this.recomputeTreeChildCounts(n),n.syncedAt=new Date().toISOString(),n}resolveEffectiveSegments(e,r){return e.tmpIndex?ft(r).map(n=>e.tmpIndex.sanitizeName(n)):ft(r)}rewritePendingEffectivePaths(e,r,n,i){let a=Pe.relative(r,n).split(Pe.sep).filter(l=>l.length>0),o=Pe.relative(r,i).split(Pe.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=Je(["game",...a]),c=Je(["game",...o]);for(let l of e.instances){if(l.effectivePath===s){l.effectivePath=c;continue}(l.effectivePath.startsWith(`${s}.`)||l.effectivePath.startsWith(`${s}[`))&&(l.effectivePath=`${c}${l.effectivePath.slice(s.length)}`)}}upsertTreeNode(e,r,n,i){if(r.length<=1)return;let a=e.children;for(let o=1;o<r.length;o++){let s=r[o],c=n[o],l=o===r.length-1,u=a.find(p=>p.name===s);u?l&&(u.className=i,c!==void 0&&c!==s&&(u.originalName=c)):(u={name:s,className:l?i:"Folder",childCount:0,children:[]},c!==void 0&&c!==s&&(u.originalName=c),a.push(u)),u.children||(u.children=[]),a=u.children}}recomputeTreeChildCounts(e){let r=n=>{let i=n.children??[];n.children=i;for(let a of i)r(a);n.childCount=i.length};for(let n of e.children)r(n);e.childCount=e.children.length}clearTTLTimerForPlace(e,r){e.incompleteSyncTimer&&e.activeFullSyncSessionId===r&&(clearTimeout(e.incompleteSyncTimer),e.incompleteSyncTimer=null)}async cleanupStaleTempDirs(){let e=this.ctx.config.getSyncRoot();try{let r=await Wt.readdir(e,{withFileTypes:!0});for(let n of r)if(n.isDirectory()){if(n.name.startsWith("explorer_tmp_")){let i=Pe.join(e,n.name);g.warn("Removing stale temp directory from crashed sync",{dir:n.name});try{await Wt.rm(i,{recursive:!0,force:!0})}catch(a){g.error(`Failed to remove stale temp dir: ${n.name}`,a instanceof Error?a:new Error(String(a)))}}if(n.name.startsWith("place_")){let i=Pe.join(e,n.name);try{let a=await Wt.readdir(i,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=Pe.join(i,o.name);g.warn("Removing stale temp directory from crashed sync",{dir:`${n.name}/${o.name}`});try{await Wt.rm(s,{recursive:!0,force:!0})}catch(c){g.error(`Failed to remove stale temp dir: ${n.name}/${o.name}`,c instanceof Error?c:new Error(String(c)))}}}catch{continue}}}}catch(r){if(r.code==="ENOENT")return;g.warn("Failed to scan for stale temp dirs",{error:r instanceof Error?r.message:String(r)})}}};import Hr from"path";import{promises as uD}from"fs";ue();var xf=class{constructor(e){this.ctx=e}async handleReversePending(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({pending:0,hasConflicts:!1,lastDetected:null});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}let a={pending:i.fileWatcher?.getPendingCount()??0,hasConflicts:!1,lastDetected:i.fileWatcher?.getLastDetected()??null,forwardRestoreNeeded:i.forwardRestoreQueue.length};this.ctx.touchRuntimePlace(n),r.status(200).json(a)}async handleReverseSyncChanges(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({changes:[],count:0});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}if(i.state!=="syncing"){r.status(400).json({error:"Not syncing",message:"Reverse sync is only available when sync is active"});return}let a=i.fileWatcher?.drainPendingChanges()??[],o=a.length>0?await i.reader.buildChangesFromPending(a):[];this.ctx.touchRuntimePlace(n),r.status(200).json({changes:o,count:o.length})}async handleReverseSyncResult(e,r){let n=e.body,i=n.placeId??this.ctx.getDefaultRuntimePlaceId();if(i==null){r.status(400).json({error:"Validation error",message:"placeId is required (in body or via active sync session)"});return}let a=n.appliedFiles??n.appliedPaths;if(!a||!Array.isArray(a)){r.status(400).json({error:"Validation error",message:"appliedFiles (or appliedPaths) must be an array of relative file paths"});return}let o=this.ctx.places.get(i);if(!o){r.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}this.ctx.touchRuntimePlace(i);let s=this.ctx.config.getPlaceRoot(i),c=0,l=[];for(let u of a){let p=Hr.resolve(s,u);if(!gf(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await uD.readFile(p,"utf-8"),f=o.index.computeHash(d);o.index.updateHashByValue(p,f),o.index.updateFileHashByValue(p,f),c++}catch(d){let f=d.code;if(f==="ENOENT"){o.index.removeHash(p),o.index.removeHashesUnder(p);let m=this.resolveInstancePathForAppliedPath(o.index,p);if(m){let h=$t(m),v=Pt(m);v&&h&&await o.writer.removeFromTree(v,h)}c++}else f==="EISDIR"?c++:l.push({path:u,error:d instanceof Error?d.message:String(d)})}}c>0&&await o.index.saveToDisk(),c>0&&o.writer.appendHistory({timestamp:new Date().toISOString(),type:"reverseApply",direction:"reverse",path:`place_${i}`,details:`applied:${c} failed:${l.length}`}),r.status(200).json({updated:c,failed:l.length,errors:l})}async handleResolveConflict(e,r){let n=e.body;if(!n.fsPath||!n.resolution){r.status(400).json({error:"Validation error",message:"fsPath and resolution are required"});return}let{fsPath:i,resolution:a}=n,o=this.ctx.config.getSyncRoot();if(!gf(o,Hr.resolve(o,i))){r.status(403).json({error:"Forbidden",message:"Path is outside the sync root"});return}if(a==="skip"){r.status(200).json({status:"skipped",fsPath:i});return}let s;if(n.placeId&&(s=this.ctx.places.get(n.placeId)),!s){let d=i.match(/^place_(\d+)(?:_[^/]+)?\//);if(d){let f=parseInt(d[1],10);s=this.ctx.places.get(f)}else s=Array.from(this.ctx.places.values())[0]}if(!s){r.status(404).json({error:"No active place context",message:"No sync session is active. Start a sync first."});return}let c=this.ctx.config.getPlaceRoot(s.placeId),l=Hr.resolve(c,i);if(!gf(c,l)){r.status(403).json({error:"Forbidden",message:"Path is outside the place root"});return}let u=s.index,p=s.reader;if(a==="apply-studio"){u.resolveFile(l,"apply-studio"),await u.saveToDisk(),r.status(200).json({status:"resolved",resolution:"apply-studio",fsPath:i});return}if(a==="apply-file"){let d=await uD.readFile(l,"utf-8"),f=u.computeHash(d);u.resolveFile(l,"apply-file",f),await u.saveToDisk();let m=p.getFileType(l),h=p.resolveInstancePathFromFile(l);r.status(200).json({status:"resolved",resolution:"apply-file",fsPath:i,instancePath:h,fileType:m,content:d});return}r.status(400).json({error:"Invalid resolution",message:`Unknown resolution: ${a}`})}async handleReverseRescan(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({added:0});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}if(!i.fileWatcher){r.status(200).json({added:0});return}let a=await i.fileWatcher.rescan();this.ctx.touchRuntimePlace(n),g.info("Reverse rescan completed",{placeId:n,added:a}),r.status(200).json({added:a})}resolveInstancePathForAppliedPath(e,r){let n=e.resolveInstancePathFromFsPath(r);if(n)return n;let i=e.getExplorerRoot(),a=Hr.relative(i,r);if(a.startsWith("..")||a===""||Hr.isAbsolute(a))return null;let o=a.split(Hr.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=Hr.basename(r),c=Hr.dirname(r),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(yo.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=i;for(let f of o){d=Hr.join(d,f);let m=Hr.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return Je(p)}};ue();function pD(t){if(!t||typeof t!="object")return;let e=t;if(Array.isArray(e.instances))for(let r of e.instances)Array.isArray(r.properties)&&r.properties.length===0&&(r.properties={}),Array.isArray(r.attributes)&&r.attributes.length===0&&(r.attributes={});if(Array.isArray(e.changes))for(let r of e.changes)Array.isArray(r.properties)&&r.properties.length===0&&(r.properties={}),Array.isArray(r.attributes)&&r.attributes.length===0&&(r.attributes={})}var Co=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;constructor(e){this.config=new Qd(e),this.apiHandler=new yf(this),this.changeProcessor=new vf(this),this.initHandler=new bf(this),this.reverseHandler=new xf(this),this.places=new Yd({max:3,dispose:(r,n)=>{g.info("Disposing place context (LRU eviction)",{placeId:n}),this.activeFullSyncPlaceId===n&&(this.activeFullSyncPlaceId=null),this.activeRuntimeSyncPlaceId===n&&(this.activeRuntimeSyncPlaceId=null),r.fileWatcher&&(r.fileWatcher.stop().catch(i=>{g.error("Error stopping file watcher during dispose",i)}),r.fileWatcher=null),r.writer.stopChangeLogFlusher(),r.incompleteSyncTimer&&(clearTimeout(r.incompleteSyncTimer),r.incompleteSyncTimer=null),r.index.saveToDisk().catch(i=>{g.error("Error saving index during dispose",i)}),r.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(r.activeFullSyncSessionId),r.tmpWriter&&(r.tmpWriter.stopChangeLogFlusher(),r.tmpWriter=null),r.tmpIndex=null,r.collisionDirMap=null}})}getSyncRoot(){return this.config.getSyncRoot()}async getOrCreatePlaceContext(e,r){let n=this.places.get(e);if(n&&r){let i=this.config.getPlaceRoot(e),a=await this.config.resolvePlaceRoot(e,r);a!==i&&(g.info("Place root migrated, recreating context",{placeId:e,from:i,to:a}),this.places.delete(e),n=void 0)}if(!n){let i=await this.config.resolvePlaceRoot(e,r),a=Lw.join(i,"explorer");await To.mkdir(i,{recursive:!0}),await To.mkdir(a,{recursive:!0});let o=new Qn(i,a);await o.loadFromDisk();let s=new wo(this.config,o,e),c=new rf(this.config,o,i);s.startChangeLogFlusher(),n={placeId:e,placeName:"",index:o,writer:s,reader:c,fileWatcher:null,state:"idle",activeClientId:null,activeFullSyncSessionId:null,instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,tmpIndex:null,tmpWriter:null,incompleteSyncTimer:null,changesSinceLastSave:0,directions:{...Mi},applyModes:{...Li},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null},this.places.set(e,n),g.info("Created new place context",{placeId:e,placeRoot:i})}return n}async handleSyncInit(e,r){try{pD(e.body);let n=oD(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data,a=i.phase==="start"?i.placeId??null:i.phase==="chunk"||i.phase==="complete"?this.activeFullSyncPlaceId:null;if(a==null){r.status(400).json({error:"Missing placeId",message:"placeId is required in start phase or must be set by previous start"});return}switch(i.phase){case"start":await this.initHandler.handleInitStart(a,i,r);break;case"chunk":await this.initHandler.handleInitChunk(a,i,r);break;case"complete":await this.initHandler.handleInitComplete(a,i,r);break}}catch(n){this.sendError(r,n,"handleSyncInit")}}async handleSyncUpdate(e,r){try{pD(e.body);let n=sD(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data,a=i.placeId??this.getDefaultRuntimePlaceId();if(a==null){r.status(400).json({error:"Missing placeId",message:"placeId is required in body or must have an active sync session"});return}let o=await this.getOrCreatePlaceContext(a);if(o.activeFullSyncSessionId!==null||o.state==="initializing"){r.status(409).json({error:"Conflict",message:`Full sync in progress for place ${a}`});return}o.activeClientId=i.clientId,this.touchRuntimePlace(a);let s=nD(o,i.changes);g.info("Sync update received",{placeId:a,clientId:i.clientId,changeCount:s.length,receivedCount:i.changes.length,types:s.map(f=>f.type)});let c=[],l=[],u=0,p=new Map;for(let f of s)try{let m=await this.changeProcessor.processChangeForPlace(o,f,p);m?l.push(m):u++}catch(m){let h="path"in f?f.path:"oldPath"in f?f.oldPath:"unknown";c.push({path:h,error:m instanceof Error?m.message:String(m)})}for(let f of p.values())try{await ff(o,f)}catch(m){c.push({path:f.instancePath,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=aD&&(await o.index.saveToDisk(),o.changesSinceLastSave=0),o.lastIncrementalSync=new Date().toISOString();let d={processed:u,failed:c.length,errors:c,syncedAt:o.lastIncrementalSync};l.length>0&&(d.conflicts=l),r.status(200).json(d)}catch(n){this.sendError(r,n,"handleSyncUpdate")}}getDefaultRuntimePlaceId(){if(this.activeRuntimeSyncPlaceId!==null&&this.activeRuntimeSyncPlaceId!==void 0){if(this.places.has(this.activeRuntimeSyncPlaceId))return this.activeRuntimeSyncPlaceId;this.activeRuntimeSyncPlaceId=null}if(this.activeFullSyncPlaceId!==null&&this.activeFullSyncPlaceId!==void 0&&this.places.has(this.activeFullSyncPlaceId))return this.activeRuntimeSyncPlaceId=this.activeFullSyncPlaceId,this.activeRuntimeSyncPlaceId;for(let[e,r]of this.places.entries())if(r.state==="syncing"||r.state==="initializing")return this.activeRuntimeSyncPlaceId=e,e;return this.activeRuntimeSyncPlaceId=null,null}touchRuntimePlace(e){this.activeRuntimeSyncPlaceId=e}clearRuntimePlaceIfMatch(e){this.activeRuntimeSyncPlaceId===e&&(this.activeRuntimeSyncPlaceId=null)}resolveQueryPlaceId(e,r="runtime"){let n=e.query.placeId;if(n){let i=parseInt(n,10);if(!isNaN(i))return i}return r==="full"?this.activeFullSyncPlaceId:this.getDefaultRuntimePlaceId()}async handleSyncStatus(e,r){try{let n=this.resolveQueryPlaceId(e);if(n==null){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};r.status(200).json(s);return}let i=this.places.get(n);if(!i){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getPlaceRoot(n),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};r.status(200).json(s);return}let a=i.fileWatcher?.getPendingCount()??0;this.touchRuntimePlace(n);let o={state:i.state,instanceCount:i.instanceCount,scriptCount:i.scriptCount,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,syncRoot:this.config.getPlaceRoot(n),activeClientId:i.activeClientId,reverseSyncAvailable:a>0,modifiedFileCount:a,applyModes:i.applyModes,directions:i.directions,fileWatcherActive:i.fileWatcher!==null,forwardOnlyClasses:[...xo]};r.status(200).json(o)}catch(n){this.sendError(r,n,"handleSyncStatus")}}async handleSyncStop(e,r){try{let n=e.body,i=n.placeId??this.getDefaultRuntimePlaceId();if(i==null){r.status(200).json({status:"idle",state:"idle",placeId:null,message:"No active sync place"});return}let a=this.places.get(i);if(!a){r.status(200).json({status:"idle",state:"idle",placeId:i,message:`No sync context for place ${i}`});return}if(n.clientId&&a.activeClientId&&n.clientId!==a.activeClientId&&(a.activeFullSyncSessionId!==null||a.state==="syncing"||a.state==="initializing")){r.status(409).json({error:"Conflict",message:`This sync session belongs to client ${a.activeClientId}`});return}let o=a.activeFullSyncSessionId;if(o&&(this.initHandler.clearPreserveLocalFiles(o),this.initHandler.clearPendingServiceTrees(o)),a.fileWatcher&&(await a.fileWatcher.stop(),a.fileWatcher=null),a.incompleteSyncTimer&&(clearTimeout(a.incompleteSyncTimer),a.incompleteSyncTimer=null),o){let s=this.config.getPlaceRoot(i),c=Lw.join(s,`explorer_tmp_${o}`);await To.rm(c,{recursive:!0,force:!0}).catch(()=>{})}a.tmpWriter&&(a.tmpWriter.stopChangeLogFlusher(),a.tmpWriter=null),a.tmpIndex=null,a.collisionDirMap=null,a.activeFullSyncSessionId=null,a.syncProgress=null,a.state="idle",a.activeClientId=null,a.instanceCount=0,a.scriptCount=0,this.activeFullSyncPlaceId===i&&(this.activeFullSyncPlaceId=null),this.clearRuntimePlaceIfMatch(i),g.info("Sync stopped",{placeId:i,reason:n.reason??"requested"}),r.status(200).json({status:"stopped",state:"idle",placeId:i})}catch(n){this.sendError(r,n,"handleSyncStop")}}async handleSyncConfig(e,r){try{if(e.method==="GET"){r.status(200).json(this.config.getConfig());return}let n=cD(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data;i.maxDepth!==void 0&&this.config.updateConfig({maxDepth:i.maxDepth}),i.maxInstances!==void 0&&this.config.updateConfig({maxInstances:i.maxInstances}),r.status(200).json({status:"updated",config:this.config.getConfig()})}catch(n){this.sendError(r,n,"handleSyncConfig")}}async initialize(){try{await this.config.loadFromMeta(),await this.initHandler.cleanupStaleTempDirs(),g.info("SyncController initialized")}catch(e){g.error("SyncController initialization failed",e instanceof Error?e:new Error(String(e)))}}async shutdown(){try{this.places.clear(),g.info("SyncController shut down")}catch(e){g.error("SyncController shutdown error",e instanceof Error?e:new Error(String(e)))}}async atomicWriteFile(e,r){let n=e+".tmp."+nQ().slice(0,8);try{await To.writeFile(n,r,"utf-8"),await To.rename(n,e)}catch(i){throw await To.unlink(n).catch(()=>{}),i}}async handlePreCheck(e,r){try{await this.apiHandler.handlePreCheck(e,r)}catch(n){this.sendError(r,n,"handlePreCheck")}}async handleSyncDirections(e,r){try{await this.apiHandler.handleSyncDirections(e,r)}catch(n){this.sendError(r,n,"handleSyncDirections")}}async handleForwardRestoreList(e,r){try{await this.apiHandler.handleForwardRestoreList(e,r)}catch(n){this.sendError(r,n,"handleForwardRestoreList")}}async handleReversePending(e,r){try{await this.reverseHandler.handleReversePending(e,r)}catch(n){this.sendError(r,n,"handleReversePending")}}async handleReverseSyncChanges(e,r){try{await this.reverseHandler.handleReverseSyncChanges(e,r)}catch(n){this.sendError(r,n,"handleReverseSyncChanges")}}async handleReverseSyncResult(e,r){try{await this.reverseHandler.handleReverseSyncResult(e,r)}catch(n){this.sendError(r,n,"handleReverseSyncResult")}}async handleResolveConflict(e,r){try{await this.reverseHandler.handleResolveConflict(e,r)}catch(n){this.sendError(r,n,"handleResolveConflict")}}async handleReverseRescan(e,r){try{await this.reverseHandler.handleReverseRescan(e,r)}catch(n){this.sendError(r,n,"handleReverseRescan")}}async handleSyncHistory(e,r){try{await this.apiHandler.handleSyncHistory(e,r)}catch(n){this.sendError(r,n,"handleSyncHistory")}}async startFileWatcherForPlace(e){if(e.state!=="syncing"){g.debug("Skipping file watcher start - place not syncing",{placeId:e.placeId,state:e.state});return}e.fileWatcher&&await e.fileWatcher.stop();let r=Lw.join(this.config.getPlaceRoot(e.placeId),"explorer");e.fileWatcher=new df(r,e.index),e.writer.setOnWriteCallback(n=>{e.fileWatcher?.suppressPath(n)}),e.fileWatcher.setDirectionChecker(n=>{let i=IN(n);return e.directions[i]}),e.fileWatcher.setOnForwardViolation(n=>{e.forwardRestoreQueue.includes(n)||(e.forwardRestoreQueue.push(n),g.info("Forward violation queued for restore",{placeId:e.placeId,relativePath:n,queueSize:e.forwardRestoreQueue.length}))}),await e.fileWatcher.start(),g.info("File watcher started for reverse sync",{placeId:e.placeId})}getStatusSummary(){return this.apiHandler.getStatusSummary()}getDirectionForCategory(e){return this.apiHandler.getDirectionForCategory(e)}getStatusDirect(e){return this.apiHandler.getStatusDirect(e)}getConfigDirect(){return this.apiHandler.getConfigDirect()}async getHistoryDirect(e,r){return this.apiHandler.getHistoryDirect(e,r)}getDirectionsDirect(e){return this.apiHandler.getDirectionsDirect(e)}getProgressDirect(e){return this.apiHandler.getProgressDirect(e)}async readSyncedFile(e,r){return this.apiHandler.readSyncedFile(e,r)}async writeSyncedFile(e,r,n){await this.apiHandler.writeSyncedFile(e,r,n)}async executeViaDisk(e,r){return this.apiHandler.executeViaDisk(e,r)}sendError(e,r,n){let i=r instanceof Error?r.message:String(r);if(i.includes("Path traversal detected")){e.status(403).json({error:"Forbidden",message:i});return}let a=r.code;if(a==="ENOSPC"||a==="EPERM"||a==="EACCES"){e.status(500).json({error:"Disk error",message:i});return}g.error(`SyncController.${n} failed`,r instanceof Error?r:new Error(i)),e.status(500).json({error:"Internal error",message:`${n}: ${i}`})}};import{randomUUID as iQ}from"crypto";function dD(t,e){let r=iQ(),n=r.replace(/-/g,"").substring(0,8).toUpperCase(),i=`${n.substring(0,4)}-${n.substring(4,8)}`;return{config:t,app:e,instanceId:r,sessionId:i,startTime:Date.now(),baseUrl:`http://${t.httpHost}:${t.httpPort}`,commandQueue:new Map,pendingCommands:new Map,globalPendingCommands:[],totalCommandsProcessed:0,pluginClients:new Map,mcpInstances:new Map,sseClients:new Set,cachedSelectionMap:new Map,isClientMode:!1,clientModeHealthTimer:null,clientModeConsecutiveHealthFailures:0,clientModeUpstreamReachable:!0,clientModeLastHealthSuccessAt:null,clientModeLastHealthFailureAt:null,clientModeLastHealthError:null,historyManager:null,analyticsManager:null,licenseState:null,syncController:null,internalActionExecutor:null,activeSyncOwnerInstanceId:null,activeProjectRoot:null,playtestControlCommand:null,aiClientName:"",pluginVersion:"",syncedSessionToken:null}}var fD=ri(uo(),1);ue();function mD(t){let e=fD.default.json({limit:"5mb"});t.app.use((r,n,i)=>{if(r.path.startsWith("/sync/")){i();return}e(r,n,i)}),t.app.use((r,n,i)=>{n.setHeader("Access-Control-Allow-Origin","http://localhost:3002"),n.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),n.setHeader("Access-Control-Allow-Headers","Content-Type"),i()}),t.app.use((r,n,i)=>{g.debug(`${r.method} ${r.path}`,{ip:r.ip}),i()})}import{randomUUID as gl}from"crypto";ue();function aQ(){let t=process.env.WEPPY_ROBLOX_MCP_VERSION?.trim();return t||null}var Ve=aQ()??"2.0.6";Sf();function bD(t){let e=new Set;t.aiClientName&&e.add(t.aiClientName);for(let r of t.mcpInstances.values())r.aiClientName&&e.add(r.aiClientName);return Array.from(e)}function sQ(t){let r=Date.now();return Array.from(t.pluginClients.values()).filter(n=>r-n.lastSeen<1e4)}function xD(t){let r=Date.now();for(let[n,i]of t.mcpInstances)i.lastSeen&&r-i.lastSeen>15e3&&(t.mcpInstances.delete(n),g.debug("Removed stale MCP instance",{instanceId:n,lastSeen:i.lastSeen}))}function _D(t,e,r){let n=e.query.clientId;if(n&&t.pluginClients.has(n)){let i=t.pluginClients.get(n);i.lastSeen=Date.now()}try{let i=e.body;if(!i||!Array.isArray(i.selection)||typeof i.count!="number"){g.warn("Invalid selection update request",{body:i}),r.status(400).json({error:"Invalid request body"});return}let a=n||"unknown",o=Date.now();t.cachedSelectionMap.set(a,{selection:i.selection,count:i.count,timestamp:o,clientId:a}),g.debug("Selection cache updated",{count:i.count,clientId:a,timestamp:o}),r.json({status:"ok",timestamp:o})}catch(i){g.error("Error handling selection update",i),r.status(500).json({error:"Internal server error"})}}function wD(t,e,r){let n=parseInt(e.query.maxAge)||3e4,i=hl(t,n);i?r.json({cached:!0,...i}):r.json({cached:!1,message:"No cached selection available"})}function hl(t,e=3e4,r){if(t.cachedSelectionMap.size===0)return null;let n;if(r)n=t.cachedSelectionMap.get(r);else for(let a of t.cachedSelectionMap.values())(!n||a.timestamp>n.timestamp)&&(n=a);if(!n)return null;let i=Date.now()-n.timestamp;return e===0||i<=e?n:null}function SD(t,e,r){try{let n=e.body;if(!n.clientId){r.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.get(n.clientId),a=Date.now(),o={clientId:n.clientId,projectName:n.projectName,placeName:n.placeName,placeId:n.placeId,pluginVersion:n.pluginVersion,connectedAt:i?.connectedAt||a,lastSeen:a,commandsProcessed:i?.commandsProcessed||0,connectionType:"polling"};t.pluginClients.set(n.clientId,o),t.pendingCommands.has(n.clientId)||t.pendingCommands.set(n.clientId,[]),n.pluginVersion&&(t.pluginVersion=n.pluginVersion),kt(t,"connection",{clientId:n.clientId,placeId:o.projectName,placeName:o.placeName,status:"connected"}),al(t,{timestamp:new Date().toISOString(),type:"plugin",status:"connected",clientId:n.clientId,message:`Plugin connected \u2014 ${n.clientId}`,...o.placeId!==void 0?{placeId:o.placeId}:{},...o.placeName?{placeName:o.placeName}:{}}),t.analyticsManager&&(n.pluginVersion&&t.analyticsManager.setPluginVersion(n.pluginVersion),t.analyticsManager.trackPluginConnected()),g.info("Plugin client registered",{clientId:n.clientId,projectName:n.projectName,placeName:n.placeName,isReconnect:!!i}),r.json({status:"ok",clientId:n.clientId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Ve,connectedAt:o.connectedAt,aiClientNames:bD(t),serverStartTime:t.startTime,mcpInstanceCount:t.mcpInstances.size+1})}catch(n){g.error("Error registering plugin client",n),r.status(500).json({error:"Internal server error"})}}function kD(t,e,r){let n=e.body?.clientId;if(!n){r.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.has(n);t.pluginClients.delete(n),t.pendingCommands.delete(n),i&&(kt(t,"connection",{clientId:n,status:"disconnected"}),al(t,{timestamp:new Date().toISOString(),type:"plugin",status:"disconnected",clientId:n,message:`Plugin disconnected \u2014 ${n}`})),g.info("Plugin client unregistered",{clientId:n,existed:i}),r.json({status:"ok",existed:i})}function $D(t,e,r){try{let n=e.body;if(!n.instanceId){r.status(400).json({error:"Missing instanceId"});return}let i=Date.now(),a={instanceId:n.instanceId,pid:n.pid,connectedAt:i,isServer:!1,lastSeen:i};n.aiClientName&&(a.aiClientName=n.aiClientName),n.cwd&&(a.cwd=n.cwd),"projectRoot"in n&&(a.projectRoot=n.projectRoot),t.mcpInstances.set(n.instanceId,a),kt(t,"mcp_status",{aiClientName:a.aiClientName??"Unknown",instanceId:n.instanceId,status:"registered"}),al(t,{timestamp:new Date().toISOString(),type:"mcp",status:"registered",instanceId:n.instanceId,message:`MCP registered \u2014 ${a.aiClientName??n.instanceId}`,...a.aiClientName?{aiClientName:a.aiClientName}:{}}),g.info("MCP instance registered (client mode)",{instanceId:n.instanceId,pid:n.pid,cwd:n.cwd}),r.json({status:"ok",instanceId:n.instanceId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Ve,mcpInstanceCount:t.mcpInstances.size+1})}catch(n){g.error("Error registering MCP instance",n),r.status(500).json({error:"Internal server error"})}}function PD(t,e,r){let n=e.body?.instanceId;if(!n){r.status(400).json({error:"Missing instanceId"});return}let i=t.mcpInstances.get(n),a=!!i;t.mcpInstances.delete(n),a&&(kt(t,"mcp_status",{aiClientName:i?.aiClientName??"Unknown",instanceId:n,status:"unregistered"}),al(t,{timestamp:new Date().toISOString(),type:"mcp",status:"unregistered",instanceId:n,message:`MCP unregistered \u2014 ${i?.aiClientName??n}`,...i?.aiClientName?{aiClientName:i.aiClientName}:{}})),g.info("MCP instance unregistered",{instanceId:n,existed:a}),r.json({status:"ok",existed:a})}function ID(t,e,r){let{instanceId:n,aiClientName:i}=e.body;if(n&&i){let a=t.mcpInstances.get(n);a&&(a.aiClientName=i)}r.json({status:"ok"})}function ED(t){let r=Date.now();for(let[n,i]of t.pluginClients)r-i.lastSeen>3e4&&(t.pluginClients.delete(n),t.pendingCommands.delete(n));return xD(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Ve,uptime:r-t.startTime,serverStartTime:t.startTime,serverExecutable:process.execPath,serverHost:t.config.httpHost,serverPort:t.config.httpPort,serverPid:process.pid,mcpInstances:[{instanceId:t.instanceId,pid:process.pid,connectedAt:t.startTime,isServer:!0,cwd:process.cwd(),projectRoot:mn(),...t.aiClientName?{aiClientName:t.aiClientName}:{}},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function TD(t,e){e.json(ED(t))}function CD(t,e,r){let n=e.query.instanceId;n&&t.mcpInstances.has(n)&&(t.mcpInstances.get(n).lastSeen=Date.now()),xD(t);let i=sQ(t),a=t.sseClients.size,c={...{status:"online",connectedClients:a+i.length,queuedCommands:t.commandQueue.size,uptime:Date.now()-t.startTime,version:Ve,isClientMode:t.isClientMode,pid:process.pid,sessionId:t.sessionId},instanceId:t.instanceId,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:bD(t),pluginVersion:t.pluginVersion||void 0,sseClients:a,dashboardSseClients:t.dashboardSseClients?.size??0,pollingClients:i.length,pluginClients:i.map(l=>({clientId:l.clientId,projectName:l.projectName,placeName:l.placeName,pluginVersion:l.pluginVersion,lastSeen:Date.now()-l.lastSeen})),...t.isClientMode?{upstream:{reachable:t.clientModeUpstreamReachable,consecutiveFailures:t.clientModeConsecutiveHealthFailures,lastSuccessAt:t.clientModeLastHealthSuccessAt,lastFailureAt:t.clientModeLastHealthFailureAt,lastError:t.clientModeLastHealthError,baseUrl:t.baseUrl}}:{}};r.json(c)}function RD(t,e,r,n){let i=e.ip||e.socket.remoteAddress||"";if(!(i==="127.0.0.1"||i==="::1"||i==="::ffff:127.0.0.1"||i==="localhost")){g.warn("Shutdown request rejected from non-localhost",{ip:i}),r.status(403).json({error:"Forbidden: localhost only"});return}g.info("Shutdown request received, initiating graceful shutdown",{requestedBy:i,uptime:Date.now()-t.startTime}),r.json({status:"shutting_down",message:"Server will shutdown gracefully",pid:process.pid}),setTimeout(async()=>{try{await n(),g.info("Graceful shutdown completed"),process.exit(0)}catch(o){g.error("Error during graceful shutdown",o),process.exit(1)}},100)}async function kf(t){if(t.isClientMode)try{let e=await fetch(`${t.baseUrl}/connection-info`);if(e.ok)return await e.json()}catch(e){g.warn("Failed to fetch connection info from server",{error:e})}return ED(t)}ue();var $f={query_instances:{discriminator:"action",mapping:{get:"get_instance",children:"get_instance_children",find_child:"find_first_child",find_descendant:"find_first_descendant",wait_for_child:"wait_for_child",class_info:"get_class_info",search_name:"search_by_name",search_class:"search_by_class",search_property:"search_by_property",search_tag:"search_by_tag",file_tree:"get_file_tree",project_structure:"get_project_structure",descendants:"get_descendants",ancestors:"get_ancestors"},paramAliases:{search_by_name:{query:"pattern"},search_by_property:{root:"rootPath"},search_by_tag:{root:"rootPath"},get_project_structure:{root:"rootPath"}}},mutate_instances:{discriminator:"action",mapping:{create:"create_instance",create_with_props:"create_instance_with_properties",delete:"delete_instance",clone:"clone_instance",move:"move_instance",rename:"rename_instance",pivot:"pivot_to",create_tree:"create_instance_tree",mass_create:"mass_create_instances",mass_delete:"mass_delete_instances",mass_duplicate:"mass_duplicate",smart_duplicate:"smart_duplicate"},paramAliases:{clone_instance:{path:"sourcePath"}}},manage_properties:{discriminator:"action",mapping:{get:"get_property",set:"set_property",get_all:"get_all_properties",set_multiple:"set_multiple_properties",get_attr:"get_attribute",set_attr:"set_attribute",get_all_attrs:"get_all_attributes",delete_attr:"delete_attribute",add_tag:"add_tag",remove_tag:"remove_tag",check_tag:"has_tag",get_tags:"get_tags",get_tagged:"get_tagged",set_calculated:"set_calculated_property",set_relative:"set_relative_property",mass_set:"mass_set_property",mass_get:"mass_get_property",modify_children:"modify_children"},paramAliases:{get_tagged:{tagName:"tag",root:"rootPath"},set_relative_property:{amount:"value"}}},manage_scripts:{discriminator:"action",mapping:{get_source:"get_script_source",set_source:"set_script_source",create:"create_script",delete:"delete_script",edit_replace:"edit_script_lines",edit_insert:"insert_script_lines",edit_delete:"delete_script_lines",search:"search_in_scripts",replace:"replace_in_scripts",get_dependencies:"get_script_dependencies"},paramAliases:{edit_script_lines:{newLines:"newContent"},insert_script_lines:{lines:"content"},replace_in_scripts:{pattern:"searchPattern"}}},manage_lighting:{discriminator:"action",mapping:{lighting:"set_lighting",atmosphere:"set_atmosphere",sky:"set_sky",terrain_props:"set_terrain",time:"set_time_of_day"}},manage_selection:{discriminator:"action",mapping:{get:"get_selection",set:"set_selection",clear:"clear_selection",cached:"get_cached_selection",context:"get_selection_context",details:"get_selection_details",add:"add_to_selection",remove:"remove_from_selection",watch:"watch_selection"}},manage_camera:{discriminator:"action",mapping:{info:"get_camera_info",focus_path:"focus_camera_path",focus_position:"focus_camera_position",suggest:"get_suggested_camera_view"},paramAliases:{get_suggested_camera_view:{path:"targetPath"}}},manage_tween:{discriminator:"action",mapping:{create:"create_tween",play:"play_tween",pause:"pause_tween",cancel:"cancel_tween"}},manage_audio:{discriminator:"action",mapping:{play:"play_sound",stop:"stop_sound",pause:"pause_sound",resume:"resume_sound",set_listener:"set_listener"}},manage_animation:{discriminator:"action",mapping:{load:"load_animation",play:"play_animation",stop:"stop_animation",get_tracks:"get_animation_tracks"}},manage_physics:{discriminator:"action",mapping:{register_group:"register_collision_group",set_collidable:"set_collidable",get_groups:"get_collision_groups"}},manage_effects:{discriminator:"action",mapping:{emit:"emit_particles",clear:"clear_particles",toggle:"toggle_effect"}},manage_terrain:{discriminator:"action",mapping:{fill_block:"terrain_fill_block",fill_ball:"terrain_fill_ball",fill_cylinder:"terrain_fill_cylinder",fill_wedge:"terrain_fill_wedge",clear_region:"terrain_clear",clear_bounds:"terrain_clear_region",replace_material:"terrain_replace_material",colors_get:"terrain_get_material_color",colors_set:"terrain_set_material_color",read_voxel:"terrain_read_voxel",read_voxels:"terrain_read_voxels",write_voxels:"terrain_write_voxels",generate:"terrain_generate",smooth:"terrain_smooth"}},spatial_query:{discriminator:"action",mapping:{raycast:"raycast",find_ground:"find_ground",check_placement:"check_placement",multi_raycast:"multi_raycast",scan_area:"scan_area",find_flat:"find_flat_areas",find_spawn:"find_spawn_positions",analyze_walkable:"analyze_walkable_area",spatial_map:"get_spatial_map",find_space:"find_empty_space",bounds:"get_bounds",snap_grid:"snap_to_grid",collision:"check_collision"},paramAliases:{get_spatial_map:{path:"rootPath"}}},manage_assets:{discriminator:"action",mapping:{insert:"insert_model",info:"get_asset_info",search:"search_creator_store",search_insert:"search_and_insert_model",insert_free:"insert_free_model",insert_package:"insert_package",export:"export_selection"},paramAliases:{search_creator_store:{maxResults:"limit"}}},manage_sync:{discriminator:"action",mapping:{status:"sync_status",config:"sync_config",history:"sync_history",directions:"sync_directions",read_file:"sync_read_file",write_file:"sync_write_file",progress:"sync_progress"}},workspace_state:{discriminator:"action",mapping:{sync:"sync_workspace_state",snapshot:"get_workspace_snapshot",changes:"get_recent_changes",viewport:"get_viewport_info",clear_history:"clear_change_history",metadata:"get_workspace_metadata",scripts:"get_script_list",selection_info:"get_selection_info",clear_cache:"clear_state_cache"}},manage_logs:{discriminator:"action",mapping:{get:"get_output_logs",clear:"clear_output_logs",errors:"get_recent_errors"},paramAliases:{get_output_logs:{level:"type"}}},system_info:{discriminator:"action",mapping:{ping:"ping",connection:"get_connection_info",usage:"get_usage_status",place_info:"get_place_info",services:"get_services",studio_settings:"get_studio_settings",play:"start_playtest",stop:"stop_playtest",pause:"pause_playtest",resume:"resume_playtest",play_status:"get_play_status",run_test:"run_test"}}};var cQ={get_cached_selection:"internal",sync_status:"internal",sync_config:"internal",sync_history:"internal",sync_directions:"internal",sync_read_file:"internal",sync_write_file:"internal",sync_progress:"internal",get_connection_info:"internal",run_test:"internal"};function Pf(t){return cQ[t]||"plugin"}var If=100,lQ=Object.entries($f).reduce((t,[e,r])=>{for(let n of Object.values(r.mapping))t[n]=e;return t},{});function zD(t){return{toolName:lQ[t]||t,actionName:t}}function jD(t,e,r){g.info("Plugin connected via SSE"),r.setHeader("Content-Type","text/event-stream"),r.setHeader("Cache-Control","no-cache"),r.setHeader("Connection","keep-alive"),t.sseClients.add(r),Ww(r,{event:"command",id:gl(),data:{action:"connected",requestId:gl(),params:{serverVersion:Ve,timestamp:Date.now()}}});let n=setInterval(()=>{Ww(r,{event:"command",id:gl(),data:{action:"keepalive",requestId:gl(),params:{timestamp:Date.now()}}})},3e4);r.on("close",()=>{g.info("Plugin disconnected from SSE"),clearInterval(n),t.sseClients.delete(r)})}function Ww(t,e){let r=JSON.stringify(e.data);t.write(`event: ${e.event}
126
+ `),this.clearTTLTimerForPlace(i,a),i.index.clearAllHashes(),i.index.clearClassMappings(),i.tmpIndex){let m=i.tmpIndex.getExplorerRoot(),h=i.index.getExplorerRoot();for(let[v,b]of i.tmpIndex.getAllHashes()){let w=Pe.relative(m,v),_=Pe.resolve(h,w);i.index.updateHashByValue(_,b)}for(let[v,b]of i.tmpIndex.getAllFileHashes()){let w=Pe.relative(m,v),_=Pe.resolve(h,w);i.index.updateFileHashByValue(_,b)}i.index.resetNameCounters(),i.index.mergeNameMappingsFrom(i.tmpIndex)}if(i.tmpIndex=null,i.tmpWriter&&(i.tmpWriter.stopChangeLogFlusher(),i.tmpWriter=null),this.pendingServiceTrees.delete(a),i.collisionDirMap=null,await i.index.saveToDisk(),i.state="syncing",i.activeFullSyncSessionId=null,i.syncProgress=null,this.ctx.touchRuntimePlace(e),this.ctx.activeFullSyncPlaceId=null,await this.ctx.startFileWatcherForPlace(i),i.fileWatcher&&await i.fileWatcher.waitUntilReady(),u.size>0){for(let[m,h]of u){let v=Pe.resolve(o,m);try{await Wt.mkdir(Pe.dirname(v),{recursive:!0}),await Wt.writeFile(v,h,"utf-8")}catch(b){g.warn("Failed to restore preserved file",{path:m,error:b instanceof Error?b.message:String(b)})}}g.info("Restored preserved local files",{placeId:e,count:u.size})}if(p.length>0){let m=0;for(let h of p){let v=Pe.resolve(o,h);try{await Wt.unlink(v),i.index.removeHash(v),m++}catch(b){b.code!=="ENOENT"&&g.warn("Failed to delete preserved-as-deleted file",{path:h,error:b instanceof Error?b.message:String(b)})}}m>0&&(await i.index.saveToDisk(),g.info("Deleted locally-removed files from new sync",{placeId:e,count:m}))}i.writer.appendChangeLog(`FULL_SYNC_COMPLETE instances=${i.instanceCount} scripts=${i.scriptCount}`),i.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncComplete",direction:"forward",path:`place_${e}`,details:`instances:${i.instanceCount} scripts:${i.scriptCount}`}),g.info("Full sync completed",{placeId:e,instanceCount:i.instanceCount,scriptCount:i.scriptCount}),n.status(200).json({status:"completed",instanceCount:i.instanceCount,scriptCount:i.scriptCount,syncRoot:this.ctx.config.getPlaceRoot(e)})}setPreserveLocalFiles(e,r){this.preserveLocalFilesMap.set(e,r)}getAndClearPreserveLocalFiles(e){let r=this.preserveLocalFilesMap.get(e)||[];return this.preserveLocalFilesMap.delete(e),r}clearPreserveLocalFiles(e){this.preserveLocalFilesMap.delete(e)}clearPendingServiceTrees(e){this.pendingServiceTrees.delete(e)}startTTLTimerForPlace(e,r){let n=setTimeout(async()=>{g.warn("Incomplete sync TTL expired, cleaning up",{placeId:e.placeId,syncId:r});let i=this.ctx.config.getPlaceRoot(e.placeId),a=Pe.join(i,`explorer_tmp_${r}`);try{await Wt.rm(a,{recursive:!0,force:!0})}catch(o){g.error("Failed to clean up expired temp dir",o instanceof Error?o:new Error(String(o)))}e.incompleteSyncTimer=null,e.activeFullSyncSessionId===r&&(e.activeFullSyncSessionId=null,e.activeClientId=null,e.state="idle",e.tmpIndex=null,this.pendingServiceTrees.delete(r),e.collisionDirMap=null,e.tmpWriter&&(e.tmpWriter.stopChangeLogFlusher(),e.tmpWriter=null),this.ctx.activeFullSyncPlaceId===e.placeId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(e.placeId))},iD);n&&typeof n=="object"&&"unref"in n&&n.unref(),e.incompleteSyncTimer=n}getOrCreatePendingServiceTree(e,r){let n=this.pendingServiceTrees.get(e);n||(n=new Map,this.pendingServiceTrees.set(e,n));let i=n.get(r.serviceName);if(i)return i;let a={serviceName:r.tree?.name??r.serviceName,serviceClassName:r.tree?.className??r.serviceClassName,zeroBasedChunkIndex:r.chunkIndex===0,instances:[]};return n.set(r.serviceName,a),a}isLastChunk(e,r,n){return n<=1?!0:e.zeroBasedChunkIndex?r>=n-1:r>=n}buildServiceTree(e,r){let n={name:r.serviceName,className:r.serviceClassName,childCount:0,children:[],syncedAt:new Date().toISOString()};for(let i of r.instances){let a=this.resolveEffectiveSegments(e,i.effectivePath),o=ft(i.originalPath);this.upsertTreeNode(n,a,o,i.className)}return this.recomputeTreeChildCounts(n),n.syncedAt=new Date().toISOString(),n}resolveEffectiveSegments(e,r){return e.tmpIndex?ft(r).map(n=>e.tmpIndex.sanitizeName(n)):ft(r)}rewritePendingEffectivePaths(e,r,n,i){let a=Pe.relative(r,n).split(Pe.sep).filter(l=>l.length>0),o=Pe.relative(r,i).split(Pe.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=Je(["game",...a]),c=Je(["game",...o]);for(let l of e.instances){if(l.effectivePath===s){l.effectivePath=c;continue}(l.effectivePath.startsWith(`${s}.`)||l.effectivePath.startsWith(`${s}[`))&&(l.effectivePath=`${c}${l.effectivePath.slice(s.length)}`)}}upsertTreeNode(e,r,n,i){if(r.length<=1)return;let a=e.children;for(let o=1;o<r.length;o++){let s=r[o],c=n[o],l=o===r.length-1,u=a.find(p=>p.name===s);u?l&&(u.className=i,c!==void 0&&c!==s&&(u.originalName=c)):(u={name:s,className:l?i:"Folder",childCount:0,children:[]},c!==void 0&&c!==s&&(u.originalName=c),a.push(u)),u.children||(u.children=[]),a=u.children}}recomputeTreeChildCounts(e){let r=n=>{let i=n.children??[];n.children=i;for(let a of i)r(a);n.childCount=i.length};for(let n of e.children)r(n);e.childCount=e.children.length}clearTTLTimerForPlace(e,r){e.incompleteSyncTimer&&e.activeFullSyncSessionId===r&&(clearTimeout(e.incompleteSyncTimer),e.incompleteSyncTimer=null)}async cleanupStaleTempDirs(){let e=this.ctx.config.getSyncRoot();try{let r=await Wt.readdir(e,{withFileTypes:!0});for(let n of r)if(n.isDirectory()){if(n.name.startsWith("explorer_tmp_")){let i=Pe.join(e,n.name);g.warn("Removing stale temp directory from crashed sync",{dir:n.name});try{await Wt.rm(i,{recursive:!0,force:!0})}catch(a){g.error(`Failed to remove stale temp dir: ${n.name}`,a instanceof Error?a:new Error(String(a)))}}if(n.name.startsWith("place_")){let i=Pe.join(e,n.name);try{let a=await Wt.readdir(i,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=Pe.join(i,o.name);g.warn("Removing stale temp directory from crashed sync",{dir:`${n.name}/${o.name}`});try{await Wt.rm(s,{recursive:!0,force:!0})}catch(c){g.error(`Failed to remove stale temp dir: ${n.name}/${o.name}`,c instanceof Error?c:new Error(String(c)))}}}catch{continue}}}}catch(r){if(r.code==="ENOENT")return;g.warn("Failed to scan for stale temp dirs",{error:r instanceof Error?r.message:String(r)})}}};import Hr from"path";import{promises as uD}from"fs";ue();var xf=class{constructor(e){this.ctx=e}async handleReversePending(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({pending:0,hasConflicts:!1,lastDetected:null});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}let a={pending:i.fileWatcher?.getPendingCount()??0,hasConflicts:!1,lastDetected:i.fileWatcher?.getLastDetected()??null,forwardRestoreNeeded:i.forwardRestoreQueue.length};this.ctx.touchRuntimePlace(n),r.status(200).json(a)}async handleReverseSyncChanges(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({changes:[],count:0});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}if(i.state!=="syncing"){r.status(400).json({error:"Not syncing",message:"Reverse sync is only available when sync is active"});return}let a=i.fileWatcher?.drainPendingChanges()??[],o=a.length>0?await i.reader.buildChangesFromPending(a):[];this.ctx.touchRuntimePlace(n),r.status(200).json({changes:o,count:o.length})}async handleReverseSyncResult(e,r){let n=e.body,i=n.placeId??this.ctx.getDefaultRuntimePlaceId();if(i==null){r.status(400).json({error:"Validation error",message:"placeId is required (in body or via active sync session)"});return}let a=n.appliedFiles??n.appliedPaths;if(!a||!Array.isArray(a)){r.status(400).json({error:"Validation error",message:"appliedFiles (or appliedPaths) must be an array of relative file paths"});return}let o=this.ctx.places.get(i);if(!o){r.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}this.ctx.touchRuntimePlace(i);let s=this.ctx.config.getPlaceRoot(i),c=0,l=[];for(let u of a){let p=Hr.resolve(s,u);if(!gf(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await uD.readFile(p,"utf-8"),f=o.index.computeHash(d);o.index.updateHashByValue(p,f),o.index.updateFileHashByValue(p,f),c++}catch(d){let f=d.code;if(f==="ENOENT"){o.index.removeHash(p),o.index.removeHashesUnder(p);let m=this.resolveInstancePathForAppliedPath(o.index,p);if(m){let h=$t(m),v=Pt(m);v&&h&&await o.writer.removeFromTree(v,h)}c++}else f==="EISDIR"?c++:l.push({path:u,error:d instanceof Error?d.message:String(d)})}}c>0&&await o.index.saveToDisk(),c>0&&o.writer.appendHistory({timestamp:new Date().toISOString(),type:"reverseApply",direction:"reverse",path:`place_${i}`,details:`applied:${c} failed:${l.length}`}),r.status(200).json({updated:c,failed:l.length,errors:l})}async handleResolveConflict(e,r){let n=e.body;if(!n.fsPath||!n.resolution){r.status(400).json({error:"Validation error",message:"fsPath and resolution are required"});return}let{fsPath:i,resolution:a}=n,o=this.ctx.config.getSyncRoot();if(!gf(o,Hr.resolve(o,i))){r.status(403).json({error:"Forbidden",message:"Path is outside the sync root"});return}if(a==="skip"){r.status(200).json({status:"skipped",fsPath:i});return}let s;if(n.placeId&&(s=this.ctx.places.get(n.placeId)),!s){let d=i.match(/^place_(\d+)(?:_[^/]+)?\//);if(d){let f=parseInt(d[1],10);s=this.ctx.places.get(f)}else s=Array.from(this.ctx.places.values())[0]}if(!s){r.status(404).json({error:"No active place context",message:"No sync session is active. Start a sync first."});return}let c=this.ctx.config.getPlaceRoot(s.placeId),l=Hr.resolve(c,i);if(!gf(c,l)){r.status(403).json({error:"Forbidden",message:"Path is outside the place root"});return}let u=s.index,p=s.reader;if(a==="apply-studio"){u.resolveFile(l,"apply-studio"),await u.saveToDisk(),r.status(200).json({status:"resolved",resolution:"apply-studio",fsPath:i});return}if(a==="apply-file"){let d=await uD.readFile(l,"utf-8"),f=u.computeHash(d);u.resolveFile(l,"apply-file",f),await u.saveToDisk();let m=p.getFileType(l),h=p.resolveInstancePathFromFile(l);r.status(200).json({status:"resolved",resolution:"apply-file",fsPath:i,instancePath:h,fileType:m,content:d});return}r.status(400).json({error:"Invalid resolution",message:`Unknown resolution: ${a}`})}async handleReverseRescan(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({added:0});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}if(!i.fileWatcher){r.status(200).json({added:0});return}let a=await i.fileWatcher.rescan();this.ctx.touchRuntimePlace(n),g.info("Reverse rescan completed",{placeId:n,added:a}),r.status(200).json({added:a})}resolveInstancePathForAppliedPath(e,r){let n=e.resolveInstancePathFromFsPath(r);if(n)return n;let i=e.getExplorerRoot(),a=Hr.relative(i,r);if(a.startsWith("..")||a===""||Hr.isAbsolute(a))return null;let o=a.split(Hr.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=Hr.basename(r),c=Hr.dirname(r),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(yo.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=i;for(let f of o){d=Hr.join(d,f);let m=Hr.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return Je(p)}};ue();function pD(t){if(!t||typeof t!="object")return;let e=t;if(Array.isArray(e.instances))for(let r of e.instances)Array.isArray(r.properties)&&r.properties.length===0&&(r.properties={}),Array.isArray(r.attributes)&&r.attributes.length===0&&(r.attributes={});if(Array.isArray(e.changes))for(let r of e.changes)Array.isArray(r.properties)&&r.properties.length===0&&(r.properties={}),Array.isArray(r.attributes)&&r.attributes.length===0&&(r.attributes={})}var Co=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;constructor(e){this.config=new Qd(e),this.apiHandler=new yf(this),this.changeProcessor=new vf(this),this.initHandler=new bf(this),this.reverseHandler=new xf(this),this.places=new Yd({max:3,dispose:(r,n)=>{g.info("Disposing place context (LRU eviction)",{placeId:n}),this.activeFullSyncPlaceId===n&&(this.activeFullSyncPlaceId=null),this.activeRuntimeSyncPlaceId===n&&(this.activeRuntimeSyncPlaceId=null),r.fileWatcher&&(r.fileWatcher.stop().catch(i=>{g.error("Error stopping file watcher during dispose",i)}),r.fileWatcher=null),r.writer.stopChangeLogFlusher(),r.incompleteSyncTimer&&(clearTimeout(r.incompleteSyncTimer),r.incompleteSyncTimer=null),r.index.saveToDisk().catch(i=>{g.error("Error saving index during dispose",i)}),r.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(r.activeFullSyncSessionId),r.tmpWriter&&(r.tmpWriter.stopChangeLogFlusher(),r.tmpWriter=null),r.tmpIndex=null,r.collisionDirMap=null}})}getSyncRoot(){return this.config.getSyncRoot()}async getOrCreatePlaceContext(e,r){let n=this.places.get(e);if(n&&r){let i=this.config.getPlaceRoot(e),a=await this.config.resolvePlaceRoot(e,r);a!==i&&(g.info("Place root migrated, recreating context",{placeId:e,from:i,to:a}),this.places.delete(e),n=void 0)}if(!n){let i=await this.config.resolvePlaceRoot(e,r),a=Lw.join(i,"explorer");await To.mkdir(i,{recursive:!0}),await To.mkdir(a,{recursive:!0});let o=new Qn(i,a);await o.loadFromDisk();let s=new wo(this.config,o,e),c=new rf(this.config,o,i);s.startChangeLogFlusher(),n={placeId:e,placeName:"",index:o,writer:s,reader:c,fileWatcher:null,state:"idle",activeClientId:null,activeFullSyncSessionId:null,instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,tmpIndex:null,tmpWriter:null,incompleteSyncTimer:null,changesSinceLastSave:0,directions:{...Mi},applyModes:{...Li},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null},this.places.set(e,n),g.info("Created new place context",{placeId:e,placeRoot:i})}return n}async handleSyncInit(e,r){try{pD(e.body);let n=oD(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data,a=i.phase==="start"?i.placeId??null:i.phase==="chunk"||i.phase==="complete"?this.activeFullSyncPlaceId:null;if(a==null){r.status(400).json({error:"Missing placeId",message:"placeId is required in start phase or must be set by previous start"});return}switch(i.phase){case"start":await this.initHandler.handleInitStart(a,i,r);break;case"chunk":await this.initHandler.handleInitChunk(a,i,r);break;case"complete":await this.initHandler.handleInitComplete(a,i,r);break}}catch(n){this.sendError(r,n,"handleSyncInit")}}async handleSyncUpdate(e,r){try{pD(e.body);let n=sD(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data,a=i.placeId??this.getDefaultRuntimePlaceId();if(a==null){r.status(400).json({error:"Missing placeId",message:"placeId is required in body or must have an active sync session"});return}let o=await this.getOrCreatePlaceContext(a);if(o.activeFullSyncSessionId!==null||o.state==="initializing"){r.status(409).json({error:"Conflict",message:`Full sync in progress for place ${a}`});return}o.activeClientId=i.clientId,this.touchRuntimePlace(a);let s=nD(o,i.changes);g.info("Sync update received",{placeId:a,clientId:i.clientId,changeCount:s.length,receivedCount:i.changes.length,types:s.map(f=>f.type)});let c=[],l=[],u=0,p=new Map;for(let f of s)try{let m=await this.changeProcessor.processChangeForPlace(o,f,p);m?l.push(m):u++}catch(m){let h="path"in f?f.path:"oldPath"in f?f.oldPath:"unknown";c.push({path:h,error:m instanceof Error?m.message:String(m)})}for(let f of p.values())try{await ff(o,f)}catch(m){c.push({path:f.instancePath,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=aD&&(await o.index.saveToDisk(),o.changesSinceLastSave=0),o.lastIncrementalSync=new Date().toISOString();let d={processed:u,failed:c.length,errors:c,syncedAt:o.lastIncrementalSync};l.length>0&&(d.conflicts=l),r.status(200).json(d)}catch(n){this.sendError(r,n,"handleSyncUpdate")}}getDefaultRuntimePlaceId(){if(this.activeRuntimeSyncPlaceId!==null&&this.activeRuntimeSyncPlaceId!==void 0){if(this.places.has(this.activeRuntimeSyncPlaceId))return this.activeRuntimeSyncPlaceId;this.activeRuntimeSyncPlaceId=null}if(this.activeFullSyncPlaceId!==null&&this.activeFullSyncPlaceId!==void 0&&this.places.has(this.activeFullSyncPlaceId))return this.activeRuntimeSyncPlaceId=this.activeFullSyncPlaceId,this.activeRuntimeSyncPlaceId;for(let[e,r]of this.places.entries())if(r.state==="syncing"||r.state==="initializing")return this.activeRuntimeSyncPlaceId=e,e;return this.activeRuntimeSyncPlaceId=null,null}touchRuntimePlace(e){this.activeRuntimeSyncPlaceId=e}clearRuntimePlaceIfMatch(e){this.activeRuntimeSyncPlaceId===e&&(this.activeRuntimeSyncPlaceId=null)}resolveQueryPlaceId(e,r="runtime"){let n=e.query.placeId;if(n){let i=parseInt(n,10);if(!isNaN(i))return i}return r==="full"?this.activeFullSyncPlaceId:this.getDefaultRuntimePlaceId()}async handleSyncStatus(e,r){try{let n=this.resolveQueryPlaceId(e);if(n==null){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};r.status(200).json(s);return}let i=this.places.get(n);if(!i){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getPlaceRoot(n),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};r.status(200).json(s);return}let a=i.fileWatcher?.getPendingCount()??0;this.touchRuntimePlace(n);let o={state:i.state,instanceCount:i.instanceCount,scriptCount:i.scriptCount,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,syncRoot:this.config.getPlaceRoot(n),activeClientId:i.activeClientId,reverseSyncAvailable:a>0,modifiedFileCount:a,applyModes:i.applyModes,directions:i.directions,fileWatcherActive:i.fileWatcher!==null,forwardOnlyClasses:[...xo]};r.status(200).json(o)}catch(n){this.sendError(r,n,"handleSyncStatus")}}async handleSyncStop(e,r){try{let n=e.body,i=n.placeId??this.getDefaultRuntimePlaceId();if(i==null){r.status(200).json({status:"idle",state:"idle",placeId:null,message:"No active sync place"});return}let a=this.places.get(i);if(!a){r.status(200).json({status:"idle",state:"idle",placeId:i,message:`No sync context for place ${i}`});return}if(n.clientId&&a.activeClientId&&n.clientId!==a.activeClientId&&(a.activeFullSyncSessionId!==null||a.state==="syncing"||a.state==="initializing")){r.status(409).json({error:"Conflict",message:`This sync session belongs to client ${a.activeClientId}`});return}let o=a.activeFullSyncSessionId;if(o&&(this.initHandler.clearPreserveLocalFiles(o),this.initHandler.clearPendingServiceTrees(o)),a.fileWatcher&&(await a.fileWatcher.stop(),a.fileWatcher=null),a.incompleteSyncTimer&&(clearTimeout(a.incompleteSyncTimer),a.incompleteSyncTimer=null),o){let s=this.config.getPlaceRoot(i),c=Lw.join(s,`explorer_tmp_${o}`);await To.rm(c,{recursive:!0,force:!0}).catch(()=>{})}a.tmpWriter&&(a.tmpWriter.stopChangeLogFlusher(),a.tmpWriter=null),a.tmpIndex=null,a.collisionDirMap=null,a.activeFullSyncSessionId=null,a.syncProgress=null,a.state="idle",a.activeClientId=null,a.instanceCount=0,a.scriptCount=0,this.activeFullSyncPlaceId===i&&(this.activeFullSyncPlaceId=null),this.clearRuntimePlaceIfMatch(i),g.info("Sync stopped",{placeId:i,reason:n.reason??"requested"}),r.status(200).json({status:"stopped",state:"idle",placeId:i})}catch(n){this.sendError(r,n,"handleSyncStop")}}async handleSyncConfig(e,r){try{if(e.method==="GET"){r.status(200).json(this.config.getConfig());return}let n=cD(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data;i.maxDepth!==void 0&&this.config.updateConfig({maxDepth:i.maxDepth}),i.maxInstances!==void 0&&this.config.updateConfig({maxInstances:i.maxInstances}),r.status(200).json({status:"updated",config:this.config.getConfig()})}catch(n){this.sendError(r,n,"handleSyncConfig")}}async initialize(){try{await this.config.loadFromMeta(),await this.initHandler.cleanupStaleTempDirs(),g.info("SyncController initialized")}catch(e){g.error("SyncController initialization failed",e instanceof Error?e:new Error(String(e)))}}async shutdown(){try{this.places.clear(),g.info("SyncController shut down")}catch(e){g.error("SyncController shutdown error",e instanceof Error?e:new Error(String(e)))}}async atomicWriteFile(e,r){let n=e+".tmp."+nQ().slice(0,8);try{await To.writeFile(n,r,"utf-8"),await To.rename(n,e)}catch(i){throw await To.unlink(n).catch(()=>{}),i}}async handlePreCheck(e,r){try{await this.apiHandler.handlePreCheck(e,r)}catch(n){this.sendError(r,n,"handlePreCheck")}}async handleSyncDirections(e,r){try{await this.apiHandler.handleSyncDirections(e,r)}catch(n){this.sendError(r,n,"handleSyncDirections")}}async handleForwardRestoreList(e,r){try{await this.apiHandler.handleForwardRestoreList(e,r)}catch(n){this.sendError(r,n,"handleForwardRestoreList")}}async handleReversePending(e,r){try{await this.reverseHandler.handleReversePending(e,r)}catch(n){this.sendError(r,n,"handleReversePending")}}async handleReverseSyncChanges(e,r){try{await this.reverseHandler.handleReverseSyncChanges(e,r)}catch(n){this.sendError(r,n,"handleReverseSyncChanges")}}async handleReverseSyncResult(e,r){try{await this.reverseHandler.handleReverseSyncResult(e,r)}catch(n){this.sendError(r,n,"handleReverseSyncResult")}}async handleResolveConflict(e,r){try{await this.reverseHandler.handleResolveConflict(e,r)}catch(n){this.sendError(r,n,"handleResolveConflict")}}async handleReverseRescan(e,r){try{await this.reverseHandler.handleReverseRescan(e,r)}catch(n){this.sendError(r,n,"handleReverseRescan")}}async handleSyncHistory(e,r){try{await this.apiHandler.handleSyncHistory(e,r)}catch(n){this.sendError(r,n,"handleSyncHistory")}}async startFileWatcherForPlace(e){if(e.state!=="syncing"){g.debug("Skipping file watcher start - place not syncing",{placeId:e.placeId,state:e.state});return}e.fileWatcher&&await e.fileWatcher.stop();let r=Lw.join(this.config.getPlaceRoot(e.placeId),"explorer");e.fileWatcher=new df(r,e.index),e.writer.setOnWriteCallback(n=>{e.fileWatcher?.suppressPath(n)}),e.fileWatcher.setDirectionChecker(n=>{let i=IN(n);return e.directions[i]}),e.fileWatcher.setOnForwardViolation(n=>{e.forwardRestoreQueue.includes(n)||(e.forwardRestoreQueue.push(n),g.info("Forward violation queued for restore",{placeId:e.placeId,relativePath:n,queueSize:e.forwardRestoreQueue.length}))}),await e.fileWatcher.start(),g.info("File watcher started for reverse sync",{placeId:e.placeId})}getStatusSummary(){return this.apiHandler.getStatusSummary()}getDirectionForCategory(e){return this.apiHandler.getDirectionForCategory(e)}getStatusDirect(e){return this.apiHandler.getStatusDirect(e)}getConfigDirect(){return this.apiHandler.getConfigDirect()}async getHistoryDirect(e,r){return this.apiHandler.getHistoryDirect(e,r)}getDirectionsDirect(e){return this.apiHandler.getDirectionsDirect(e)}getProgressDirect(e){return this.apiHandler.getProgressDirect(e)}async readSyncedFile(e,r){return this.apiHandler.readSyncedFile(e,r)}async writeSyncedFile(e,r,n){await this.apiHandler.writeSyncedFile(e,r,n)}async executeViaDisk(e,r){return this.apiHandler.executeViaDisk(e,r)}sendError(e,r,n){let i=r instanceof Error?r.message:String(r);if(i.includes("Path traversal detected")){e.status(403).json({error:"Forbidden",message:i});return}let a=r.code;if(a==="ENOSPC"||a==="EPERM"||a==="EACCES"){e.status(500).json({error:"Disk error",message:i});return}g.error(`SyncController.${n} failed`,r instanceof Error?r:new Error(i)),e.status(500).json({error:"Internal error",message:`${n}: ${i}`})}};import{randomUUID as iQ}from"crypto";function dD(t,e){let r=iQ(),n=r.replace(/-/g,"").substring(0,8).toUpperCase(),i=`${n.substring(0,4)}-${n.substring(4,8)}`;return{config:t,app:e,instanceId:r,sessionId:i,startTime:Date.now(),baseUrl:`http://${t.httpHost}:${t.httpPort}`,commandQueue:new Map,pendingCommands:new Map,globalPendingCommands:[],totalCommandsProcessed:0,pluginClients:new Map,mcpInstances:new Map,sseClients:new Set,cachedSelectionMap:new Map,isClientMode:!1,clientModeHealthTimer:null,clientModeConsecutiveHealthFailures:0,clientModeUpstreamReachable:!0,clientModeLastHealthSuccessAt:null,clientModeLastHealthFailureAt:null,clientModeLastHealthError:null,historyManager:null,analyticsManager:null,licenseState:null,syncController:null,internalActionExecutor:null,activeSyncOwnerInstanceId:null,activeProjectRoot:null,playtestControlCommand:null,aiClientName:"",pluginVersion:"",syncedSessionToken:null}}var fD=ri(uo(),1);ue();function mD(t){let e=fD.default.json({limit:"5mb"});t.app.use((r,n,i)=>{if(r.path.startsWith("/sync/")){i();return}e(r,n,i)}),t.app.use((r,n,i)=>{n.setHeader("Access-Control-Allow-Origin","http://localhost:3002"),n.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),n.setHeader("Access-Control-Allow-Headers","Content-Type"),i()}),t.app.use((r,n,i)=>{g.debug(`${r.method} ${r.path}`,{ip:r.ip}),i()})}import{randomUUID as gl}from"crypto";ue();function aQ(){let t=process.env.WEPPY_ROBLOX_MCP_VERSION?.trim();return t||null}var Ve=aQ()??"2.0.7";Sf();function bD(t){let e=new Set;t.aiClientName&&e.add(t.aiClientName);for(let r of t.mcpInstances.values())r.aiClientName&&e.add(r.aiClientName);return Array.from(e)}function sQ(t){let r=Date.now();return Array.from(t.pluginClients.values()).filter(n=>r-n.lastSeen<1e4)}function xD(t){let r=Date.now();for(let[n,i]of t.mcpInstances)i.lastSeen&&r-i.lastSeen>15e3&&(t.mcpInstances.delete(n),g.debug("Removed stale MCP instance",{instanceId:n,lastSeen:i.lastSeen}))}function _D(t,e,r){let n=e.query.clientId;if(n&&t.pluginClients.has(n)){let i=t.pluginClients.get(n);i.lastSeen=Date.now()}try{let i=e.body;if(!i||!Array.isArray(i.selection)||typeof i.count!="number"){g.warn("Invalid selection update request",{body:i}),r.status(400).json({error:"Invalid request body"});return}let a=n||"unknown",o=Date.now();t.cachedSelectionMap.set(a,{selection:i.selection,count:i.count,timestamp:o,clientId:a}),g.debug("Selection cache updated",{count:i.count,clientId:a,timestamp:o}),r.json({status:"ok",timestamp:o})}catch(i){g.error("Error handling selection update",i),r.status(500).json({error:"Internal server error"})}}function wD(t,e,r){let n=parseInt(e.query.maxAge)||3e4,i=hl(t,n);i?r.json({cached:!0,...i}):r.json({cached:!1,message:"No cached selection available"})}function hl(t,e=3e4,r){if(t.cachedSelectionMap.size===0)return null;let n;if(r)n=t.cachedSelectionMap.get(r);else for(let a of t.cachedSelectionMap.values())(!n||a.timestamp>n.timestamp)&&(n=a);if(!n)return null;let i=Date.now()-n.timestamp;return e===0||i<=e?n:null}function SD(t,e,r){try{let n=e.body;if(!n.clientId){r.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.get(n.clientId),a=Date.now(),o={clientId:n.clientId,projectName:n.projectName,placeName:n.placeName,placeId:n.placeId,pluginVersion:n.pluginVersion,connectedAt:i?.connectedAt||a,lastSeen:a,commandsProcessed:i?.commandsProcessed||0,connectionType:"polling"};t.pluginClients.set(n.clientId,o),t.pendingCommands.has(n.clientId)||t.pendingCommands.set(n.clientId,[]),n.pluginVersion&&(t.pluginVersion=n.pluginVersion),kt(t,"connection",{clientId:n.clientId,placeId:o.projectName,placeName:o.placeName,status:"connected"}),al(t,{timestamp:new Date().toISOString(),type:"plugin",status:"connected",clientId:n.clientId,message:`Plugin connected \u2014 ${n.clientId}`,...o.placeId!==void 0?{placeId:o.placeId}:{},...o.placeName?{placeName:o.placeName}:{}}),t.analyticsManager&&(n.pluginVersion&&t.analyticsManager.setPluginVersion(n.pluginVersion),t.analyticsManager.trackPluginConnected()),g.info("Plugin client registered",{clientId:n.clientId,projectName:n.projectName,placeName:n.placeName,isReconnect:!!i}),r.json({status:"ok",clientId:n.clientId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Ve,connectedAt:o.connectedAt,aiClientNames:bD(t),serverStartTime:t.startTime,mcpInstanceCount:t.mcpInstances.size+1})}catch(n){g.error("Error registering plugin client",n),r.status(500).json({error:"Internal server error"})}}function kD(t,e,r){let n=e.body?.clientId;if(!n){r.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.has(n);t.pluginClients.delete(n),t.pendingCommands.delete(n),i&&(kt(t,"connection",{clientId:n,status:"disconnected"}),al(t,{timestamp:new Date().toISOString(),type:"plugin",status:"disconnected",clientId:n,message:`Plugin disconnected \u2014 ${n}`})),g.info("Plugin client unregistered",{clientId:n,existed:i}),r.json({status:"ok",existed:i})}function $D(t,e,r){try{let n=e.body;if(!n.instanceId){r.status(400).json({error:"Missing instanceId"});return}let i=Date.now(),a={instanceId:n.instanceId,pid:n.pid,connectedAt:i,isServer:!1,lastSeen:i};n.aiClientName&&(a.aiClientName=n.aiClientName),n.cwd&&(a.cwd=n.cwd),"projectRoot"in n&&(a.projectRoot=n.projectRoot),t.mcpInstances.set(n.instanceId,a),kt(t,"mcp_status",{aiClientName:a.aiClientName??"Unknown",instanceId:n.instanceId,status:"registered"}),al(t,{timestamp:new Date().toISOString(),type:"mcp",status:"registered",instanceId:n.instanceId,message:`MCP registered \u2014 ${a.aiClientName??n.instanceId}`,...a.aiClientName?{aiClientName:a.aiClientName}:{}}),g.info("MCP instance registered (client mode)",{instanceId:n.instanceId,pid:n.pid,cwd:n.cwd}),r.json({status:"ok",instanceId:n.instanceId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Ve,mcpInstanceCount:t.mcpInstances.size+1})}catch(n){g.error("Error registering MCP instance",n),r.status(500).json({error:"Internal server error"})}}function PD(t,e,r){let n=e.body?.instanceId;if(!n){r.status(400).json({error:"Missing instanceId"});return}let i=t.mcpInstances.get(n),a=!!i;t.mcpInstances.delete(n),a&&(kt(t,"mcp_status",{aiClientName:i?.aiClientName??"Unknown",instanceId:n,status:"unregistered"}),al(t,{timestamp:new Date().toISOString(),type:"mcp",status:"unregistered",instanceId:n,message:`MCP unregistered \u2014 ${i?.aiClientName??n}`,...i?.aiClientName?{aiClientName:i.aiClientName}:{}})),g.info("MCP instance unregistered",{instanceId:n,existed:a}),r.json({status:"ok",existed:a})}function ID(t,e,r){let{instanceId:n,aiClientName:i}=e.body;if(n&&i){let a=t.mcpInstances.get(n);a&&(a.aiClientName=i)}r.json({status:"ok"})}function ED(t){let r=Date.now();for(let[n,i]of t.pluginClients)r-i.lastSeen>3e4&&(t.pluginClients.delete(n),t.pendingCommands.delete(n));return xD(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Ve,uptime:r-t.startTime,serverStartTime:t.startTime,serverExecutable:process.execPath,serverHost:t.config.httpHost,serverPort:t.config.httpPort,serverPid:process.pid,mcpInstances:[{instanceId:t.instanceId,pid:process.pid,connectedAt:t.startTime,isServer:!0,cwd:process.cwd(),projectRoot:mn(),...t.aiClientName?{aiClientName:t.aiClientName}:{}},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function TD(t,e){e.json(ED(t))}function CD(t,e,r){let n=e.query.instanceId;n&&t.mcpInstances.has(n)&&(t.mcpInstances.get(n).lastSeen=Date.now()),xD(t);let i=sQ(t),a=t.sseClients.size,c={...{status:"online",connectedClients:a+i.length,queuedCommands:t.commandQueue.size,uptime:Date.now()-t.startTime,version:Ve,isClientMode:t.isClientMode,pid:process.pid,sessionId:t.sessionId},instanceId:t.instanceId,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:bD(t),pluginVersion:t.pluginVersion||void 0,sseClients:a,dashboardSseClients:t.dashboardSseClients?.size??0,pollingClients:i.length,pluginClients:i.map(l=>({clientId:l.clientId,projectName:l.projectName,placeName:l.placeName,pluginVersion:l.pluginVersion,lastSeen:Date.now()-l.lastSeen})),...t.isClientMode?{upstream:{reachable:t.clientModeUpstreamReachable,consecutiveFailures:t.clientModeConsecutiveHealthFailures,lastSuccessAt:t.clientModeLastHealthSuccessAt,lastFailureAt:t.clientModeLastHealthFailureAt,lastError:t.clientModeLastHealthError,baseUrl:t.baseUrl}}:{}};r.json(c)}function RD(t,e,r,n){let i=e.ip||e.socket.remoteAddress||"";if(!(i==="127.0.0.1"||i==="::1"||i==="::ffff:127.0.0.1"||i==="localhost")){g.warn("Shutdown request rejected from non-localhost",{ip:i}),r.status(403).json({error:"Forbidden: localhost only"});return}g.info("Shutdown request received, initiating graceful shutdown",{requestedBy:i,uptime:Date.now()-t.startTime}),r.json({status:"shutting_down",message:"Server will shutdown gracefully",pid:process.pid}),setTimeout(async()=>{try{await n(),g.info("Graceful shutdown completed"),process.exit(0)}catch(o){g.error("Error during graceful shutdown",o),process.exit(1)}},100)}async function kf(t){if(t.isClientMode)try{let e=await fetch(`${t.baseUrl}/connection-info`);if(e.ok)return await e.json()}catch(e){g.warn("Failed to fetch connection info from server",{error:e})}return ED(t)}ue();var $f={query_instances:{discriminator:"action",mapping:{get:"get_instance",children:"get_instance_children",find_child:"find_first_child",find_descendant:"find_first_descendant",wait_for_child:"wait_for_child",class_info:"get_class_info",search_name:"search_by_name",search_class:"search_by_class",search_property:"search_by_property",search_tag:"search_by_tag",file_tree:"get_file_tree",project_structure:"get_project_structure",descendants:"get_descendants",ancestors:"get_ancestors"},paramAliases:{search_by_name:{query:"pattern"},search_by_property:{root:"rootPath"},search_by_tag:{root:"rootPath"},get_project_structure:{root:"rootPath"}}},mutate_instances:{discriminator:"action",mapping:{create:"create_instance",create_with_props:"create_instance_with_properties",delete:"delete_instance",clone:"clone_instance",move:"move_instance",rename:"rename_instance",pivot:"pivot_to",create_tree:"create_instance_tree",mass_create:"mass_create_instances",mass_delete:"mass_delete_instances",mass_duplicate:"mass_duplicate",smart_duplicate:"smart_duplicate"},paramAliases:{clone_instance:{path:"sourcePath"}}},manage_properties:{discriminator:"action",mapping:{get:"get_property",set:"set_property",get_all:"get_all_properties",set_multiple:"set_multiple_properties",get_attr:"get_attribute",set_attr:"set_attribute",get_all_attrs:"get_all_attributes",delete_attr:"delete_attribute",add_tag:"add_tag",remove_tag:"remove_tag",check_tag:"has_tag",get_tags:"get_tags",get_tagged:"get_tagged",set_calculated:"set_calculated_property",set_relative:"set_relative_property",mass_set:"mass_set_property",mass_get:"mass_get_property",modify_children:"modify_children"},paramAliases:{get_tagged:{tagName:"tag",root:"rootPath"},set_relative_property:{amount:"value"}}},manage_scripts:{discriminator:"action",mapping:{get_source:"get_script_source",set_source:"set_script_source",create:"create_script",delete:"delete_script",edit_replace:"edit_script_lines",edit_insert:"insert_script_lines",edit_delete:"delete_script_lines",search:"search_in_scripts",replace:"replace_in_scripts",get_dependencies:"get_script_dependencies"},paramAliases:{edit_script_lines:{newLines:"newContent"},insert_script_lines:{lines:"content"},replace_in_scripts:{pattern:"searchPattern"}}},manage_lighting:{discriminator:"action",mapping:{lighting:"set_lighting",atmosphere:"set_atmosphere",sky:"set_sky",terrain_props:"set_terrain",time:"set_time_of_day"}},manage_selection:{discriminator:"action",mapping:{get:"get_selection",set:"set_selection",clear:"clear_selection",cached:"get_cached_selection",context:"get_selection_context",details:"get_selection_details",add:"add_to_selection",remove:"remove_from_selection",watch:"watch_selection"}},manage_camera:{discriminator:"action",mapping:{info:"get_camera_info",focus_path:"focus_camera_path",focus_position:"focus_camera_position",suggest:"get_suggested_camera_view"},paramAliases:{get_suggested_camera_view:{path:"targetPath"}}},manage_tween:{discriminator:"action",mapping:{create:"create_tween",play:"play_tween",pause:"pause_tween",cancel:"cancel_tween"}},manage_audio:{discriminator:"action",mapping:{play:"play_sound",stop:"stop_sound",pause:"pause_sound",resume:"resume_sound",set_listener:"set_listener"}},manage_animation:{discriminator:"action",mapping:{load:"load_animation",play:"play_animation",stop:"stop_animation",get_tracks:"get_animation_tracks"}},manage_physics:{discriminator:"action",mapping:{register_group:"register_collision_group",set_collidable:"set_collidable",get_groups:"get_collision_groups"}},manage_effects:{discriminator:"action",mapping:{emit:"emit_particles",clear:"clear_particles",toggle:"toggle_effect"}},manage_terrain:{discriminator:"action",mapping:{fill_block:"terrain_fill_block",fill_ball:"terrain_fill_ball",fill_cylinder:"terrain_fill_cylinder",fill_wedge:"terrain_fill_wedge",clear_region:"terrain_clear",clear_bounds:"terrain_clear_region",replace_material:"terrain_replace_material",colors_get:"terrain_get_material_color",colors_set:"terrain_set_material_color",read_voxel:"terrain_read_voxel",read_voxels:"terrain_read_voxels",write_voxels:"terrain_write_voxels",generate:"terrain_generate",smooth:"terrain_smooth"}},spatial_query:{discriminator:"action",mapping:{raycast:"raycast",find_ground:"find_ground",check_placement:"check_placement",multi_raycast:"multi_raycast",scan_area:"scan_area",find_flat:"find_flat_areas",find_spawn:"find_spawn_positions",analyze_walkable:"analyze_walkable_area",spatial_map:"get_spatial_map",find_space:"find_empty_space",bounds:"get_bounds",snap_grid:"snap_to_grid",collision:"check_collision"},paramAliases:{get_spatial_map:{path:"rootPath"}}},manage_assets:{discriminator:"action",mapping:{insert:"insert_model",info:"get_asset_info",search:"search_creator_store",search_insert:"search_and_insert_model",insert_free:"insert_free_model",insert_package:"insert_package",export:"export_selection"},paramAliases:{search_creator_store:{maxResults:"limit"}}},manage_sync:{discriminator:"action",mapping:{status:"sync_status",config:"sync_config",history:"sync_history",directions:"sync_directions",read_file:"sync_read_file",write_file:"sync_write_file",progress:"sync_progress"}},workspace_state:{discriminator:"action",mapping:{sync:"sync_workspace_state",snapshot:"get_workspace_snapshot",changes:"get_recent_changes",viewport:"get_viewport_info",clear_history:"clear_change_history",metadata:"get_workspace_metadata",scripts:"get_script_list",selection_info:"get_selection_info",clear_cache:"clear_state_cache"}},manage_logs:{discriminator:"action",mapping:{get:"get_output_logs",clear:"clear_output_logs",errors:"get_recent_errors"},paramAliases:{get_output_logs:{level:"type"}}},system_info:{discriminator:"action",mapping:{ping:"ping",connection:"get_connection_info",usage:"get_usage_status",place_info:"get_place_info",services:"get_services",studio_settings:"get_studio_settings",play:"start_playtest",stop:"stop_playtest",pause:"pause_playtest",resume:"resume_playtest",play_status:"get_play_status",run_test:"run_test"}}};var cQ={get_cached_selection:"internal",sync_status:"internal",sync_config:"internal",sync_history:"internal",sync_directions:"internal",sync_read_file:"internal",sync_write_file:"internal",sync_progress:"internal",get_connection_info:"internal",run_test:"internal"};function Pf(t){return cQ[t]||"plugin"}var If=100,lQ=Object.entries($f).reduce((t,[e,r])=>{for(let n of Object.values(r.mapping))t[n]=e;return t},{});function zD(t){return{toolName:lQ[t]||t,actionName:t}}function jD(t,e,r){g.info("Plugin connected via SSE"),r.setHeader("Content-Type","text/event-stream"),r.setHeader("Cache-Control","no-cache"),r.setHeader("Connection","keep-alive"),t.sseClients.add(r),Ww(r,{event:"command",id:gl(),data:{action:"connected",requestId:gl(),params:{serverVersion:Ve,timestamp:Date.now()}}});let n=setInterval(()=>{Ww(r,{event:"command",id:gl(),data:{action:"keepalive",requestId:gl(),params:{timestamp:Date.now()}}})},3e4);r.on("close",()=>{g.info("Plugin disconnected from SSE"),clearInterval(n),t.sseClients.delete(r)})}function Ww(t,e){let r=JSON.stringify(e.data);t.write(`event: ${e.event}
127
127
  `),t.write(`id: ${e.id}
128
128
  `),t.write(`data: ${r}
129
129