@weppy/roblox-mcp 2.0.8 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +9 -1
  3. package/COMMERCIAL-LICENSE.md +1 -1
  4. package/README.md +10 -10
  5. package/TRADEMARKS.md +1 -1
  6. package/docs/assets/screenshots/plugin_main.png +0 -0
  7. package/docs/assets/screenshots/plugins_menu.png +0 -0
  8. package/docs/assets/screenshots/weppy_plugin_toolbar.png +0 -0
  9. package/docs/en/dashboard/changelog.md +1 -1
  10. package/docs/en/dashboard/connection.md +1 -1
  11. package/docs/en/dashboard/overview.md +1 -1
  12. package/docs/en/dashboard/playtest.md +1 -1
  13. package/docs/en/dashboard/settings.md +1 -1
  14. package/docs/en/dashboard/sync.md +1 -1
  15. package/docs/en/dashboard/tools.md +1 -1
  16. package/docs/en/installation/README.md +5 -3
  17. package/docs/en/installation/ai-apps/antigravity.md +1 -1
  18. package/docs/en/installation/ai-apps/claude-app.md +1 -1
  19. package/docs/en/installation/ai-apps/claude-code.md +1 -1
  20. package/docs/en/installation/ai-apps/codex-app.md +1 -1
  21. package/docs/en/installation/ai-apps/codex-cli.md +1 -1
  22. package/docs/en/installation/ai-apps/cursor.md +1 -1
  23. package/docs/en/installation/ai-apps/gemini-cli.md +1 -1
  24. package/docs/en/installation/roblox-explorer.md +9 -9
  25. package/docs/en/installation/roblox-plugin.md +3 -3
  26. package/docs/en/pro-upgrade.md +3 -3
  27. package/docs/en/sync/overview.md +2 -2
  28. package/docs/es/README.md +7 -7
  29. package/docs/es/dashboard/changelog.md +1 -1
  30. package/docs/es/dashboard/connection.md +1 -1
  31. package/docs/es/dashboard/overview.md +1 -1
  32. package/docs/es/dashboard/playtest.md +1 -1
  33. package/docs/es/dashboard/settings.md +1 -1
  34. package/docs/es/dashboard/sync.md +1 -1
  35. package/docs/es/dashboard/tools.md +1 -1
  36. package/docs/es/installation/README.md +2 -2
  37. package/docs/es/installation/ai-apps/antigravity.md +1 -1
  38. package/docs/es/installation/ai-apps/claude-app.md +1 -1
  39. package/docs/es/installation/ai-apps/claude-code.md +1 -1
  40. package/docs/es/installation/ai-apps/codex-app.md +1 -1
  41. package/docs/es/installation/ai-apps/codex-cli.md +1 -1
  42. package/docs/es/installation/ai-apps/cursor.md +1 -1
  43. package/docs/es/installation/ai-apps/gemini-cli.md +1 -1
  44. package/docs/es/installation/roblox-explorer.md +9 -9
  45. package/docs/es/installation/roblox-plugin.md +3 -3
  46. package/docs/es/pro-upgrade.md +1 -1
  47. package/docs/es/sync/overview.md +2 -2
  48. package/docs/id/README.md +7 -7
  49. package/docs/id/dashboard/changelog.md +1 -1
  50. package/docs/id/dashboard/connection.md +1 -1
  51. package/docs/id/dashboard/overview.md +1 -1
  52. package/docs/id/dashboard/playtest.md +1 -1
  53. package/docs/id/dashboard/settings.md +1 -1
  54. package/docs/id/dashboard/sync.md +1 -1
  55. package/docs/id/dashboard/tools.md +1 -1
  56. package/docs/id/installation/README.md +3 -3
  57. package/docs/id/installation/ai-apps/antigravity.md +1 -1
  58. package/docs/id/installation/ai-apps/claude-app.md +1 -1
  59. package/docs/id/installation/ai-apps/claude-code.md +1 -1
  60. package/docs/id/installation/ai-apps/codex-app.md +1 -1
  61. package/docs/id/installation/ai-apps/codex-cli.md +1 -1
  62. package/docs/id/installation/ai-apps/cursor.md +1 -1
  63. package/docs/id/installation/ai-apps/gemini-cli.md +1 -1
  64. package/docs/id/installation/roblox-explorer.md +9 -9
  65. package/docs/id/installation/roblox-plugin.md +3 -3
  66. package/docs/id/pro-upgrade.md +1 -1
  67. package/docs/id/sync/overview.md +2 -2
  68. package/docs/ja/README.md +7 -7
  69. package/docs/ja/installation/README.md +3 -3
  70. package/docs/ja/installation/ai-apps/antigravity.md +1 -1
  71. package/docs/ja/installation/ai-apps/claude-app.md +1 -1
  72. package/docs/ja/installation/ai-apps/claude-code.md +1 -1
  73. package/docs/ja/installation/ai-apps/codex-app.md +1 -1
  74. package/docs/ja/installation/ai-apps/codex-cli.md +1 -1
  75. package/docs/ja/installation/ai-apps/cursor.md +1 -1
  76. package/docs/ja/installation/ai-apps/gemini-cli.md +1 -1
  77. package/docs/ja/installation/roblox-explorer.md +9 -9
  78. package/docs/ja/installation/roblox-plugin.md +3 -3
  79. package/docs/ja/pro-upgrade.md +1 -1
  80. package/docs/ja/sync/overview.md +2 -2
  81. package/docs/ko/README.md +7 -7
  82. package/docs/ko/installation/README.md +5 -3
  83. package/docs/ko/installation/ai-apps/antigravity.md +1 -1
  84. package/docs/ko/installation/ai-apps/claude-app.md +1 -1
  85. package/docs/ko/installation/ai-apps/claude-code.md +1 -1
  86. package/docs/ko/installation/ai-apps/codex-app.md +1 -1
  87. package/docs/ko/installation/ai-apps/codex-cli.md +1 -1
  88. package/docs/ko/installation/ai-apps/cursor.md +1 -1
  89. package/docs/ko/installation/ai-apps/gemini-cli.md +1 -1
  90. package/docs/ko/installation/roblox-explorer.md +9 -9
  91. package/docs/ko/installation/roblox-plugin.md +3 -3
  92. package/docs/ko/pro-upgrade.md +1 -1
  93. package/docs/ko/sync/overview.md +2 -2
  94. package/docs/pt-br/README.md +8 -8
  95. package/docs/pt-br/dashboard/changelog.md +1 -1
  96. package/docs/pt-br/dashboard/connection.md +1 -1
  97. package/docs/pt-br/dashboard/overview.md +1 -1
  98. package/docs/pt-br/dashboard/playtest.md +1 -1
  99. package/docs/pt-br/dashboard/settings.md +1 -1
  100. package/docs/pt-br/dashboard/sync.md +1 -1
  101. package/docs/pt-br/dashboard/tools.md +1 -1
  102. package/docs/pt-br/installation/README.md +3 -3
  103. package/docs/pt-br/installation/ai-apps/antigravity.md +1 -1
  104. package/docs/pt-br/installation/ai-apps/claude-app.md +1 -1
  105. package/docs/pt-br/installation/ai-apps/claude-code.md +1 -1
  106. package/docs/pt-br/installation/ai-apps/codex-app.md +1 -1
  107. package/docs/pt-br/installation/ai-apps/codex-cli.md +1 -1
  108. package/docs/pt-br/installation/ai-apps/cursor.md +1 -1
  109. package/docs/pt-br/installation/ai-apps/gemini-cli.md +1 -1
  110. package/docs/pt-br/installation/roblox-explorer.md +9 -9
  111. package/docs/pt-br/installation/roblox-plugin.md +3 -3
  112. package/docs/pt-br/pro-upgrade.md +1 -1
  113. package/docs/pt-br/sync/overview.md +2 -2
  114. package/docs/troubleshooting.md +1 -1
  115. package/install.ps1 +37 -14
  116. package/install.sh +38 -3
  117. package/llms-full.txt +7 -7
  118. package/llms.txt +4 -4
  119. package/package.json +1 -1
  120. package/plugins/weppy-roblox-mcp/.claude-plugin/plugin.json +1 -1
  121. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ChangelogDetailPage-D1JOF_uS.js → ChangelogDetailPage-DRPIGDB0.js} +1 -1
  122. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ChangelogPage-C4ftyN_p.js → ChangelogPage-DVPYTw2L.js} +1 -1
  123. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ConfirmModal-DPwJWgnH.js → ConfirmModal-Db4rfrTo.js} +1 -1
  124. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ConnectionPage-CBB0jpB4.js → ConnectionPage-Cv2i3LSr.js} +1 -1
  125. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{InfoLabel-CYxzWZMs.js → InfoLabel-DzXzzs6Q.js} +1 -1
  126. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{OverviewPage-DkTqzsFx.js → OverviewPage-FoC28UL8.js} +1 -1
  127. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{PlaytestPage-qErtShLw.js → PlaytestPage-BjBAsHLz.js} +2 -2
  128. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{PropertyDiff-C4VtcZO2.js → PropertyDiff-BZMdMzMr.js} +1 -1
  129. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/SettingsPage-BKp2VKMW.js +1 -0
  130. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{StatusBadge-Ch9hAD89.js → StatusBadge-DtcYlxe-.js} +1 -1
  131. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/SyncPage-DcELF5_j.js +4 -0
  132. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{TierPromoProgress-DsH0LxDt.js → TierPromoProgress-r5fgmYI5.js} +1 -1
  133. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ToolsPage-D6x4enqI.js → ToolsPage-Cfau3bX3.js} +1 -1
  134. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-DWRH2iF4.js +129 -0
  135. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{tier-promo-config-DU-2XXTa.js → tier-promo-config-CQFDWwo4.js} +1 -1
  136. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{useLiveUptime-C7E1-LNV.js → useLiveUptime-5lD1XKnw.js} +1 -1
  137. package/plugins/weppy-roblox-mcp/dashboard/dist/index.html +3 -3
  138. package/plugins/weppy-roblox-mcp/dashboard/dist/wrox-icon.png +0 -0
  139. package/plugins/weppy-roblox-mcp/dist/index.js +2 -2
  140. package/plugins/weppy-roblox-mcp/roblox-plugin/WeppyRobloxMCP.rbxm +0 -0
  141. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/SettingsPage-DJLwbREH.js +0 -1
  142. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/SyncPage-CbegjDPX.js +0 -4
  143. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-CQdnwaWW.js +0 -129
@@ -1 +1 @@
1
- import{r as o,D as C,a as S,f as N,u as k,j as t}from"./index-CQdnwaWW.js";function te(){const[m,e]=o.useState("basic"),[n,c]=o.useState(0),[p,_]=o.useState(0),[a,i]=o.useState(0),[x,j]=o.useState(!0);o.useEffect(()=>{let d=!1;async function y(){try{return await N("gumroad")}catch{return null}}async function P(){var r,h;try{const[u,l]=await Promise.all([y(),S.get("/api/dashboard/tool-stats").catch(()=>null)]);if(d)return;if(u!=null&&u.tier&&e(u.tier),l!=null&&l.tierSummary){const f=((r=l.tierSummary.basic)==null?void 0:r.totalCalls)??0,b=((h=l.tierSummary.pro)==null?void 0:h.totalCalls)??0;i(f),_(b),c(f+b)}}catch{}finally{d||j(!1)}}P();const g=new C;g.connect();const w=g.on("license",()=>{y().then(r=>{!d&&(r!=null&&r.tier)&&e(r.tier)})});return()=>{d=!0,w(),g.disconnect()}},[]);const v=n>0?p/n*100:0;return{tier:m,proUsagePercent:v,totalCalls:n,proCalls:p,basicCalls:a,loading:x}}const R=[{name:"query_instances",description:"Query Roblox instances: get, children, find child/descendant, wait for child, class info, search by name/class. [PRO] file_tree, project_structure, descendants, ancestors, search by property/tag.",basic:["get","children","find_child","find_descendant","wait_for_child","class_info","search_name","search_class"],pro:["search_property","search_tag","file_tree","project_structure","descendants","ancestors"]},{name:"mutate_instances",description:"Create, delete, clone, move, rename, or pivot instances. [PRO] create_tree, mass_create, mass_delete, mass_duplicate, smart_duplicate.",basic:["create","create_with_props","delete","clone","move","rename","pivot"],pro:["create_tree","mass_create","mass_delete","mass_duplicate","smart_duplicate"]},{name:"manage_properties",description:"Get/set properties, attributes, and tags on instances. [PRO] set_calculated, set_relative, mass_set, mass_get, modify_children.",basic:["get","set","get_all","set_multiple","get_attr","set_attr","get_all_attrs","delete_attr","add_tag","remove_tag","check_tag","get_tags","get_tagged"],pro:["set_calculated","set_relative","mass_set","mass_get","modify_children"]},{name:"manage_scripts",description:"Manage script source code: read, write, create, delete, edit lines, search. [PRO] replace across scripts.",basic:["get_source","set_source","create","delete","edit_replace","edit_insert","edit_delete","search","get_dependencies"],pro:["replace"]},{name:"manage_lighting",description:"[PRO] Configure environment: lighting, atmosphere, sky, terrain properties, time of day.",basic:[],pro:["lighting","atmosphere","sky","terrain_props","time"]},{name:"manage_selection",description:"Get, set, or clear selection. [PRO] context, details, add/remove items, watch changes.",basic:["get","set","clear","cached"],pro:["context","details","add","remove","watch"]},{name:"manage_camera",description:"Camera operations: get info, focus on instance by path or position, get suggested view.",basic:["info","focus_path","focus_position","suggest"],pro:[]},{name:"manage_tween",description:"[PRO] Tween service: create, play, pause, cancel tweens for smooth animations.",basic:[],pro:["create","play","pause","cancel"]},{name:"manage_audio",description:"[PRO] Audio management: play, stop, pause, resume sounds. Set audio listener.",basic:[],pro:["play","stop","pause","resume","set_listener"]},{name:"manage_animation",description:"[PRO] Animation: load, play, stop animations. Get animation tracks from humanoid/controller.",basic:[],pro:["load","play","stop","get_tracks"]},{name:"manage_physics",description:"[PRO] Physics collision groups: register, set collidable between groups, list groups.",basic:[],pro:["register_group","set_collidable","get_groups"]},{name:"manage_effects",description:"[PRO] Particle effects: emit particles, clear all particles, toggle effect enabled state.",basic:[],pro:["emit","clear","toggle"]},{name:"manage_terrain",description:"[PRO] Terrain operations: fill shapes, clear regions, replace materials, manage colors, read/write voxels, generate procedural terrain, smooth terrain.",basic:[],pro:["fill_block","fill_ball","fill_cylinder","fill_wedge","clear_region","clear_bounds","replace_material","colors_get","colors_set","read_voxel","read_voxels","write_voxels","generate","smooth"]},{name:"spatial_query",description:"[PRO] Spatial queries: raycast, find ground, check placement, multi-raycast, scan area, find flat areas, find spawn positions, analyze walkable, spatial map, find empty space, get bounds, snap to grid, check collision.",basic:[],pro:["raycast","find_ground","check_placement","multi_raycast","scan_area","find_flat","find_spawn","analyze_walkable","spatial_map","find_space","bounds","snap_grid","collision"]},{name:"manage_assets",description:"[PRO] Asset management: insert models by ID, get asset info, search creator store, insert free models/packages, export selection.",basic:[],pro:["insert","info","search","search_insert","insert_free","insert_package","export"]},{name:"manage_sync",description:"[PRO] Project sync management: status, config, history, direction settings, read/write synced files.",basic:[],pro:["status","config","history","directions","read_file","write_file","progress"]},{name:"workspace_state",description:"[PRO] Workspace state: full sync, snapshot, recent changes, viewport info, clear history, metadata, scripts, selection info, clear cache.",basic:[],pro:["sync","snapshot","changes","viewport","clear_history","metadata","scripts","selection_info","clear_cache"]},{name:"manage_logs",description:"Output logs: get filtered logs, poll incrementally with sinceSeq cursor, clear buffer, get recent errors.",basic:["get","clear","errors"],pro:[]},{name:"system_info",description:"System info: ping, connection status, usage tier. [PRO] place info, services list, studio settings, playtest control, automated test runner.",basic:["ping","connection","usage"],pro:["place_info","services","studio_settings","play","stop","pause","resume","play_status","run_test"]},{name:"batch_execute",description:'[PRO] Execute multiple commands in a single batch. Each command is an object with "tool" name and "args". Commands execute sequentially; optionally continue on error.',basic:[],pro:["batch_execute"]},{name:"execute_luau",description:"[PRO] Execute arbitrary Luau code in Roblox Studio sandbox. Blocked services: HttpService, DataStoreService, MessagingService. Cannot access CoreGui/CorePackages.",basic:[],pro:["execute_luau"]}],T={proOnlyTools:13,mixedTools:6,totalBasicActions:51,totalProActions:107},O={tools:R,summary:T},L="_overlay_4gu92_2",B="_modal_4gu92_13",A="_summaryGrid_4gu92_32",E="_summaryCard_4gu92_39",G="_basicCard_4gu92_45",M="_proCard_4gu92_49",D="_cardTitle_4gu92_54",$="_summarySection_4gu92_70",q="_sectionTitle_4gu92_78",H="_featureList_4gu92_90",W="_summary_4gu92_32",z="_statBasic_4gu92_116",I="_statPro_4gu92_120",U="_statMixed_4gu92_124",F="_detailHeader_4gu92_128",Q="_tableWrap_4gu92_137",J="_table_4gu92_137",K="_toolName_4gu92_165",V="_description_4gu92_171",X="_actionList_4gu92_176",Y="_none_4gu92_184",Z="_closeBtn_4gu92_189",s={overlay:L,modal:B,summaryGrid:A,summaryCard:E,basicCard:G,proCard:M,cardTitle:D,summarySection:$,sectionTitle:q,featureList:H,summary:W,statBasic:z,statPro:I,statMixed:U,detailHeader:F,tableWrap:Q,table:J,toolName:K,description:V,actionList:X,none:Y,closeBtn:Z};function se({onClose:m}){const{t:e}=k(),{tools:n,summary:c}=O,p=[{title:e("tier.comparison.basic.core","Core MCP workflow"),items:[e("tier.comparison.basic.core.item1","Script create/edit"),e("tier.comparison.basic.core.item2","Instance management"),e("tier.comparison.basic.core.item3","Property control"),e("tier.comparison.basic.core.item4","Selection and search"),e("tier.comparison.basic.core.item5","Tag management"),e("tier.comparison.basic.core.item6","Camera control"),e("tier.comparison.basic.core.item7","Log monitoring")]},{title:e("tier.comparison.basic.sync","Sync (Basic)"),items:[e("tier.comparison.basic.sync.item1","Studio to Local one-way sync"),e("tier.comparison.basic.sync.item2","Manual apply as default"),e("tier.comparison.basic.sync.item3","Pro sync settings stay read-only")]}],_=[{title:e("tier.comparison.pro.all","Everything in Basic, plus"),items:[]},{title:e("tier.comparison.pro.sync","Advanced Sync workflow"),items:[e("tier.comparison.pro.sync.item1","Per-type sync direction"),e("tier.comparison.pro.sync.item2","Per-type apply mode"),e("tier.comparison.pro.sync.item3","Bidirectional and reverse sync"),e("tier.comparison.pro.sync.item4","Full sync, resync, and push to Studio"),e("tier.comparison.pro.sync.item5","Multi-place sync")]},{title:e("tier.comparison.pro.playtest","Playtest control"),items:[e("tier.comparison.pro.playtest.item1","Play, stop, pause, and resume"),e("tier.comparison.pro.playtest.item2","Playtest state inspection"),e("tier.comparison.pro.playtest.item3","Automated test execution")]},{title:e("tier.comparison.pro.creation","Advanced creation workflow"),items:[e("tier.comparison.pro.creation.item1","Bulk operations"),e("tier.comparison.pro.creation.item2","Terrain generation"),e("tier.comparison.pro.creation.item3","Asset search and insert"),e("tier.comparison.pro.creation.item4","Raycast and spatial analysis"),e("tier.comparison.pro.creation.item5","Environment control")]}];return t.jsx("div",{className:s.overlay,onClick:m,children:t.jsxs("div",{className:s.modal,onClick:a=>a.stopPropagation(),children:[t.jsx("h2",{children:e("tier.compare")}),t.jsxs("div",{className:s.summaryGrid,children:[t.jsxs("section",{className:`${s.summaryCard} ${s.basicCard}`,children:[t.jsx("h3",{className:s.cardTitle,children:e("tier.basic")}),p.map(a=>t.jsxs("div",{className:s.summarySection,children:[t.jsx("div",{className:s.sectionTitle,children:a.title}),t.jsx("ul",{className:s.featureList,children:a.items.map(i=>t.jsx("li",{children:i},i))})]},a.title))]}),t.jsxs("section",{className:`${s.summaryCard} ${s.proCard}`,children:[t.jsx("h3",{className:s.cardTitle,children:e("tier.pro")}),_.map(a=>t.jsxs("div",{className:s.summarySection,children:[t.jsx("div",{className:s.sectionTitle,children:a.title}),a.items.length>0&&t.jsx("ul",{className:s.featureList,children:a.items.map(i=>t.jsx("li",{children:i},i))})]},a.title))]})]}),t.jsxs("div",{className:s.summary,children:[t.jsxs("span",{className:s.statBasic,children:["Basic: ",c.totalBasicActions," actions"]}),t.jsxs("span",{className:s.statPro,children:["Pro: ",c.totalProActions," actions"]}),t.jsxs("span",{className:s.statMixed,children:[c.mixedTools," mixed / ",c.proOnlyTools," pro-only"]})]}),t.jsx("div",{className:s.detailHeader,children:e("tier.comparison.detailTitle","Detailed Tool Catalog")}),t.jsx("div",{className:s.tableWrap,children:t.jsxs("table",{className:s.table,children:[t.jsx("thead",{children:t.jsxs("tr",{children:[t.jsx("th",{children:e("tools.col.tool")}),t.jsx("th",{children:e("tier.comparison.description","Description")}),t.jsx("th",{children:e("tier.basic")}),t.jsx("th",{children:e("tier.pro")})]})}),t.jsx("tbody",{children:n.map(a=>t.jsxs("tr",{children:[t.jsx("td",{className:s.toolName,children:a.name}),t.jsx("td",{className:s.description,children:e(`tier.tool.${a.name}.desc`,a.description)}),t.jsx("td",{className:s.actionList,children:a.basic.length>0?a.basic.join(", "):t.jsx("span",{className:s.none,children:"—"})}),t.jsx("td",{className:s.actionList,children:a.pro.length>0?a.pro.join(", "):t.jsx("span",{className:s.none,children:"—"})})]},a.name))})]})}),t.jsx("button",{className:s.closeBtn,onClick:m,children:e("tier.comparison.close","Close")})]})})}const ae={overview:"https://gum.co/u/2nc0fhky",changelog:"https://gum.co/u/wjcnucpy",sync:"https://gum.co/u/p0barnlb",playtest:"https://gum.co/u/kchczjry",tools:"https://gum.co/u/ljmteaed"};export{ae as T,se as a,te as u};
1
+ import{r as o,D as C,a as S,f as N,u as k,j as t}from"./index-DWRH2iF4.js";function te(){const[m,e]=o.useState("basic"),[n,c]=o.useState(0),[p,_]=o.useState(0),[a,i]=o.useState(0),[x,j]=o.useState(!0);o.useEffect(()=>{let d=!1;async function y(){try{return await N("gumroad")}catch{return null}}async function P(){var r,h;try{const[u,l]=await Promise.all([y(),S.get("/api/dashboard/tool-stats").catch(()=>null)]);if(d)return;if(u!=null&&u.tier&&e(u.tier),l!=null&&l.tierSummary){const f=((r=l.tierSummary.basic)==null?void 0:r.totalCalls)??0,b=((h=l.tierSummary.pro)==null?void 0:h.totalCalls)??0;i(f),_(b),c(f+b)}}catch{}finally{d||j(!1)}}P();const g=new C;g.connect();const w=g.on("license",()=>{y().then(r=>{!d&&(r!=null&&r.tier)&&e(r.tier)})});return()=>{d=!0,w(),g.disconnect()}},[]);const v=n>0?p/n*100:0;return{tier:m,proUsagePercent:v,totalCalls:n,proCalls:p,basicCalls:a,loading:x}}const R=[{name:"query_instances",description:"Query Roblox instances: get, children, find child/descendant, wait for child, class info, search by name/class. [PRO] file_tree, project_structure, descendants, ancestors, search by property/tag.",basic:["get","children","find_child","find_descendant","wait_for_child","class_info","search_name","search_class"],pro:["search_property","search_tag","file_tree","project_structure","descendants","ancestors"]},{name:"mutate_instances",description:"Create, delete, clone, move, rename, or pivot instances. [PRO] create_tree, mass_create, mass_delete, mass_duplicate, smart_duplicate.",basic:["create","create_with_props","delete","clone","move","rename","pivot"],pro:["create_tree","mass_create","mass_delete","mass_duplicate","smart_duplicate"]},{name:"manage_properties",description:"Get/set properties, attributes, and tags on instances. [PRO] set_calculated, set_relative, mass_set, mass_get, modify_children.",basic:["get","set","get_all","set_multiple","get_attr","set_attr","get_all_attrs","delete_attr","add_tag","remove_tag","check_tag","get_tags","get_tagged"],pro:["set_calculated","set_relative","mass_set","mass_get","modify_children"]},{name:"manage_scripts",description:"Manage script source code: read, write, create, delete, edit lines, search. [PRO] replace across scripts.",basic:["get_source","set_source","create","delete","edit_replace","edit_insert","edit_delete","search","get_dependencies"],pro:["replace"]},{name:"manage_lighting",description:"[PRO] Configure environment: lighting, atmosphere, sky, terrain properties, time of day.",basic:[],pro:["lighting","atmosphere","sky","terrain_props","time"]},{name:"manage_selection",description:"Get, set, or clear selection. [PRO] context, details, add/remove items, watch changes.",basic:["get","set","clear","cached"],pro:["context","details","add","remove","watch"]},{name:"manage_camera",description:"Camera operations: get info, focus on instance by path or position, get suggested view.",basic:["info","focus_path","focus_position","suggest"],pro:[]},{name:"manage_tween",description:"[PRO] Tween service: create, play, pause, cancel tweens for smooth animations.",basic:[],pro:["create","play","pause","cancel"]},{name:"manage_audio",description:"[PRO] Audio management: play, stop, pause, resume sounds. Set audio listener.",basic:[],pro:["play","stop","pause","resume","set_listener"]},{name:"manage_animation",description:"[PRO] Animation: load, play, stop animations. Get animation tracks from humanoid/controller.",basic:[],pro:["load","play","stop","get_tracks"]},{name:"manage_physics",description:"[PRO] Physics collision groups: register, set collidable between groups, list groups.",basic:[],pro:["register_group","set_collidable","get_groups"]},{name:"manage_effects",description:"[PRO] Particle effects: emit particles, clear all particles, toggle effect enabled state.",basic:[],pro:["emit","clear","toggle"]},{name:"manage_terrain",description:"[PRO] Terrain operations: fill shapes, clear regions, replace materials, manage colors, read/write voxels, generate procedural terrain, smooth terrain.",basic:[],pro:["fill_block","fill_ball","fill_cylinder","fill_wedge","clear_region","clear_bounds","replace_material","colors_get","colors_set","read_voxel","read_voxels","write_voxels","generate","smooth"]},{name:"spatial_query",description:"[PRO] Spatial queries: raycast, find ground, check placement, multi-raycast, scan area, find flat areas, find spawn positions, analyze walkable, spatial map, find empty space, get bounds, snap to grid, check collision.",basic:[],pro:["raycast","find_ground","check_placement","multi_raycast","scan_area","find_flat","find_spawn","analyze_walkable","spatial_map","find_space","bounds","snap_grid","collision"]},{name:"manage_assets",description:"[PRO] Asset management: insert models by ID, get asset info, search creator store, insert free models/packages, export selection.",basic:[],pro:["insert","info","search","search_insert","insert_free","insert_package","export"]},{name:"manage_sync",description:"[PRO] Project sync management: status, config, history, direction settings, read/write synced files.",basic:[],pro:["status","config","history","directions","read_file","write_file","progress"]},{name:"workspace_state",description:"[PRO] Workspace state: full sync, snapshot, recent changes, viewport info, clear history, metadata, scripts, selection info, clear cache.",basic:[],pro:["sync","snapshot","changes","viewport","clear_history","metadata","scripts","selection_info","clear_cache"]},{name:"manage_logs",description:"Output logs: get filtered logs, poll incrementally with sinceSeq cursor, clear buffer, get recent errors.",basic:["get","clear","errors"],pro:[]},{name:"system_info",description:"System info: ping, connection status, usage tier. [PRO] place info, services list, studio settings, playtest control, automated test runner.",basic:["ping","connection","usage"],pro:["place_info","services","studio_settings","play","stop","pause","resume","play_status","run_test"]},{name:"batch_execute",description:'[PRO] Execute multiple commands in a single batch. Each command is an object with "tool" name and "args". Commands execute sequentially; optionally continue on error.',basic:[],pro:["batch_execute"]},{name:"execute_luau",description:"[PRO] Execute arbitrary Luau code in Roblox Studio sandbox. Blocked services: HttpService, DataStoreService, MessagingService. Cannot access CoreGui/CorePackages.",basic:[],pro:["execute_luau"]}],T={proOnlyTools:13,mixedTools:6,totalBasicActions:51,totalProActions:107},O={tools:R,summary:T},L="_overlay_4gu92_2",B="_modal_4gu92_13",A="_summaryGrid_4gu92_32",E="_summaryCard_4gu92_39",G="_basicCard_4gu92_45",M="_proCard_4gu92_49",D="_cardTitle_4gu92_54",$="_summarySection_4gu92_70",q="_sectionTitle_4gu92_78",H="_featureList_4gu92_90",W="_summary_4gu92_32",z="_statBasic_4gu92_116",I="_statPro_4gu92_120",U="_statMixed_4gu92_124",F="_detailHeader_4gu92_128",Q="_tableWrap_4gu92_137",J="_table_4gu92_137",K="_toolName_4gu92_165",V="_description_4gu92_171",X="_actionList_4gu92_176",Y="_none_4gu92_184",Z="_closeBtn_4gu92_189",s={overlay:L,modal:B,summaryGrid:A,summaryCard:E,basicCard:G,proCard:M,cardTitle:D,summarySection:$,sectionTitle:q,featureList:H,summary:W,statBasic:z,statPro:I,statMixed:U,detailHeader:F,tableWrap:Q,table:J,toolName:K,description:V,actionList:X,none:Y,closeBtn:Z};function se({onClose:m}){const{t:e}=k(),{tools:n,summary:c}=O,p=[{title:e("tier.comparison.basic.core","Core MCP workflow"),items:[e("tier.comparison.basic.core.item1","Script create/edit"),e("tier.comparison.basic.core.item2","Instance management"),e("tier.comparison.basic.core.item3","Property control"),e("tier.comparison.basic.core.item4","Selection and search"),e("tier.comparison.basic.core.item5","Tag management"),e("tier.comparison.basic.core.item6","Camera control"),e("tier.comparison.basic.core.item7","Log monitoring")]},{title:e("tier.comparison.basic.sync","Sync (Basic)"),items:[e("tier.comparison.basic.sync.item1","Studio to Local one-way sync"),e("tier.comparison.basic.sync.item2","Manual apply as default"),e("tier.comparison.basic.sync.item3","Pro sync settings stay read-only")]}],_=[{title:e("tier.comparison.pro.all","Everything in Basic, plus"),items:[]},{title:e("tier.comparison.pro.sync","Advanced Sync workflow"),items:[e("tier.comparison.pro.sync.item1","Per-type sync direction"),e("tier.comparison.pro.sync.item2","Per-type apply mode"),e("tier.comparison.pro.sync.item3","Bidirectional and reverse sync"),e("tier.comparison.pro.sync.item4","Full sync, resync, and push to Studio"),e("tier.comparison.pro.sync.item5","Multi-place sync")]},{title:e("tier.comparison.pro.playtest","Playtest control"),items:[e("tier.comparison.pro.playtest.item1","Play, stop, pause, and resume"),e("tier.comparison.pro.playtest.item2","Playtest state inspection"),e("tier.comparison.pro.playtest.item3","Automated test execution")]},{title:e("tier.comparison.pro.creation","Advanced creation workflow"),items:[e("tier.comparison.pro.creation.item1","Bulk operations"),e("tier.comparison.pro.creation.item2","Terrain generation"),e("tier.comparison.pro.creation.item3","Asset search and insert"),e("tier.comparison.pro.creation.item4","Raycast and spatial analysis"),e("tier.comparison.pro.creation.item5","Environment control")]}];return t.jsx("div",{className:s.overlay,onClick:m,children:t.jsxs("div",{className:s.modal,onClick:a=>a.stopPropagation(),children:[t.jsx("h2",{children:e("tier.compare")}),t.jsxs("div",{className:s.summaryGrid,children:[t.jsxs("section",{className:`${s.summaryCard} ${s.basicCard}`,children:[t.jsx("h3",{className:s.cardTitle,children:e("tier.basic")}),p.map(a=>t.jsxs("div",{className:s.summarySection,children:[t.jsx("div",{className:s.sectionTitle,children:a.title}),t.jsx("ul",{className:s.featureList,children:a.items.map(i=>t.jsx("li",{children:i},i))})]},a.title))]}),t.jsxs("section",{className:`${s.summaryCard} ${s.proCard}`,children:[t.jsx("h3",{className:s.cardTitle,children:e("tier.pro")}),_.map(a=>t.jsxs("div",{className:s.summarySection,children:[t.jsx("div",{className:s.sectionTitle,children:a.title}),a.items.length>0&&t.jsx("ul",{className:s.featureList,children:a.items.map(i=>t.jsx("li",{children:i},i))})]},a.title))]})]}),t.jsxs("div",{className:s.summary,children:[t.jsxs("span",{className:s.statBasic,children:["Basic: ",c.totalBasicActions," actions"]}),t.jsxs("span",{className:s.statPro,children:["Pro: ",c.totalProActions," actions"]}),t.jsxs("span",{className:s.statMixed,children:[c.mixedTools," mixed / ",c.proOnlyTools," pro-only"]})]}),t.jsx("div",{className:s.detailHeader,children:e("tier.comparison.detailTitle","Detailed Tool Catalog")}),t.jsx("div",{className:s.tableWrap,children:t.jsxs("table",{className:s.table,children:[t.jsx("thead",{children:t.jsxs("tr",{children:[t.jsx("th",{children:e("tools.col.tool")}),t.jsx("th",{children:e("tier.comparison.description","Description")}),t.jsx("th",{children:e("tier.basic")}),t.jsx("th",{children:e("tier.pro")})]})}),t.jsx("tbody",{children:n.map(a=>t.jsxs("tr",{children:[t.jsx("td",{className:s.toolName,children:a.name}),t.jsx("td",{className:s.description,children:e(`tier.tool.${a.name}.desc`,a.description)}),t.jsx("td",{className:s.actionList,children:a.basic.length>0?a.basic.join(", "):t.jsx("span",{className:s.none,children:"—"})}),t.jsx("td",{className:s.actionList,children:a.pro.length>0?a.pro.join(", "):t.jsx("span",{className:s.none,children:"—"})})]},a.name))})]})}),t.jsx("button",{className:s.closeBtn,onClick:m,children:e("tier.comparison.close","Close")})]})})}const ae={overview:"https://gum.co/u/2nc0fhky",changelog:"https://gum.co/u/wjcnucpy",sync:"https://gum.co/u/p0barnlb",playtest:"https://gum.co/u/kchczjry",tools:"https://gum.co/u/ljmteaed"};export{ae as T,se as a,te as u};
@@ -1 +1 @@
1
- import{r as a}from"./index-CQdnwaWW.js";function c(t){const r=Math.max(0,Math.floor(t/1e3)),n=Math.floor(r/3600),o=Math.floor(r%3600/60),e=Math.floor(r%60);return`${String(n).padStart(2,"0")}:${String(o).padStart(2,"0")}:${String(e).padStart(2,"0")}`}const s=1e3;function i(t){const[r,n]=a.useState(t??null);return a.useEffect(()=>{if(t==null){n(null);return}const o=Date.now();n(t);const e=window.setInterval(()=>{n(t+(Date.now()-o))},s);return()=>{window.clearInterval(e)}},[t]),r}export{c as f,i as u};
1
+ import{r as a}from"./index-DWRH2iF4.js";function c(t){const r=Math.max(0,Math.floor(t/1e3)),n=Math.floor(r/3600),o=Math.floor(r%3600/60),e=Math.floor(r%60);return`${String(n).padStart(2,"0")}:${String(o).padStart(2,"0")}:${String(e).padStart(2,"0")}`}const s=1e3;function i(t){const[r,n]=a.useState(t??null);return a.useEffect(()=>{if(t==null){n(null);return}const o=Date.now();n(t);const e=window.setInterval(()=>{n(t+(Date.now()-o))},s);return()=>{window.clearInterval(e)}},[t]),r}export{c as f,i as u};
@@ -3,9 +3,9 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <link rel="icon" href="data:," />
7
- <title>WeppyRobloxMCP</title>
8
- <script type="module" crossorigin src="/dashboard/assets/index-CQdnwaWW.js"></script>
6
+ <link rel="icon" href="/dashboard/wrox-icon.png" />
7
+ <title>WROX Dashboard</title>
8
+ <script type="module" crossorigin src="/dashboard/assets/index-DWRH2iF4.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/dashboard/assets/index-BOVxaPTw.css">
10
10
  </head>
11
11
  <body>
@@ -123,7 +123,7 @@ data: ${JSON.stringify(n)}
123
123
  `).filter(g=>g.length>0)}catch(h){if(h.code==="ENOENT"){n.status(200).json({entries:[],total:0,hasMore:!1});return}throw h}let p=[];for(let h=u.length-1;h>=0;h--)try{let g=JSON.parse(u[h]);if(o&&g.direction!==o||s&&g.type!==s)continue;p.push(g)}catch{continue}let d=p.length,m={entries:p.slice(a,a+i),total:d,hasMore:a+i<d};n.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,n]of this.ctx.places.entries())if(n.state==="syncing")return{active:!0,placeId:e};return{active:!1}}getDirectionForCategory(e){let n;if(this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0&&(n=this.ctx.places.get(this.ctx.activeFullSyncPlaceId)),!n){let i=this.ctx.getDefaultRuntimePlaceId();i!=null&&(n=this.ctx.places.get(i))}if(!n)return"forward";let r=e;return n.directions[r]??"forward"}getStatusDirect(e){let n=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(n==null)return{state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.ctx.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0,applyModes:{...Bi}};let r=this.ctx.places.get(n);if(!r)return{state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.ctx.config.getPlaceRoot(n),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0,applyModes:{...Bi}};let i=r.fileWatcher?.getPendingCount()??0;return{state:r.state,instanceCount:r.instanceCount,scriptCount:r.scriptCount,lastFullSync:r.lastFullSync,lastIncrementalSync:r.lastIncrementalSync,syncRoot:this.ctx.config.getPlaceRoot(n),activeClientId:r.activeClientId,reverseSyncAvailable:i>0,modifiedFileCount:i,applyModes:{...r.applyModes}}}getConfigDirect(){return this.ctx.config.getConfig()}async getHistoryDirect(e,n){let r=parseInt(e,10),i=this.ctx.places.get(r);i&&await i.writer.flushHistory();let a=Math.min(Math.max(n?.limit??50,1),200),o=Math.max(n?.offset??0,0),s=this.ctx.config.getHistoryPath(r),c=[];try{c=(await Mo.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 n=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(n==null)return{...qi};let r=this.ctx.places.get(n);return r?{...r.directions}:{...qi}}getProgressDirect(e){let n=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(n==null)return{state:"idle",isSyncing:!1};let r=this.ctx.places.get(n);if(!r)return{state:"idle",isSyncing:!1};if(!r.syncProgress)return{state:r.state,isSyncing:!1,lastSync:{instanceCount:r.instanceCount,scriptCount:r.scriptCount,completedAt:r.lastFullSync}};let i=r.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:r.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,n){let r=parseInt(e,10),i=this.ctx.places.get(r);if(!i)throw new Error(`Place ${e} not found in sync cache`);let a=i.index.resolvePropsPath(n);try{return{content:await Mo.readFile(a,"utf-8"),path:a}}catch(s){if(s.code!=="ENOENT")throw s}for(let s of Eo){let c=i.index.resolveScriptPath(n,s,!1);try{return{content:await Mo.readFile(c,"utf-8"),path:c}}catch{continue}}let o=i.index.resolveValuePath(n);try{return{content:await Mo.readFile(o,"utf-8"),path:o}}catch(s){if(s.code!=="ENOENT")throw s}throw new Error(`No synced file found for instance: ${n}`)}async writeSyncedFile(e,n,r){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(n,"Script",r,!1)}async executeViaDisk(e,n){let r=this.ctx.getDefaultRuntimePlaceId();if(r==null)throw new Error("No active sync place for disk execution");let i=this.ctx.places.get(r);if(!i)throw new Error(`Place ${r} not found in sync cache`);switch(e){case"set_script_source":{let a=n.scriptType||n.className||"Script";return await i.writer.writeScript(n.path,a,n.source,!1),{success:!0,path:n.path}}case"set_property":return await i.writer.writeProps(n.path,{className:n.className||"Instance",name:Ct(n.path),properties:{[n.property]:n.value}}),{success:!0,path:n.path};default:throw new Error(`Disk execution not supported for action: ${e}`)}}};import $e from"path";import{randomUUID as AQ}from"crypto";import{promises as Xt}from"fs";pe();var $f=class{constructor(e){this.ctx=e}preserveLocalFilesMap=new Map;pendingServiceTrees=new Map;async handleInitStart(e,n,r){if(n.previousPlaceId!==void 0&&n.previousPlaceId!==e&&(y.info("Place promotion detected",{from:n.previousPlaceId,to:e}),await this.ctx.config.promotePlaceRoot(n.previousPlaceId,e,n.placeName),this.ctx.places.get(n.previousPlaceId)&&this.ctx.places.delete(n.previousPlaceId),this.ctx.activeFullSyncPlaceId===n.previousPlaceId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(n.previousPlaceId)),this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0&&this.ctx.activeFullSyncPlaceId!==e){r.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,n.placeName);if(i.activeClientId&&i.activeClientId!==n.clientId&&i.activeFullSyncSessionId!==null){r.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=n.clientId,i.placeName=n.placeName,n.directions&&typeof n.directions=="object"){let p=n.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),y.info("Sync directions received",{placeId:e,directions:i.directions})}else i.directions={...qi};if(n.applyModes&&typeof n.applyModes=="object"){let p=n.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={...Bi};i.forwardRestoreQueue=[];let a=AQ();i.activeFullSyncSessionId=a,this.pendingServiceTrees.set(a,new Map),i.instanceCount=0,i.scriptCount=0;let o=this.ctx.config.getPlaceRoot(e),s=$e.join(o,`explorer_tmp_${a}`);await Xt.mkdir(s,{recursive:!0}),i.tmpIndex=new Yr(o,s),i.tmpWriter=new jo(this.ctx.config,i.tmpIndex,e),i.collisionDirMap=new Map;let c=n.preserveLocalFiles;Array.isArray(c)&&c.length>0&&(this.setPreserveLocalFiles(a,c),y.info("PreserveLocalFiles set for sync",{syncId:a,fileCount:c.length}));let l={version:1,placeId:n.placeId,placeName:n.placeName,lastFullSync:null,lastIncrementalSync:null,instanceCount:0,scriptCount:0,syncMode:"mirror"},u=$e.join(o,".sync-meta.json");await Xt.mkdir($e.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:n.totalInstances,totalServices:n.totalServices,processedInstances:0,processedServices:0,currentService:null,currentChunkIndex:0,currentTotalChunks:0,bytesReceived:0,processedChunks:0},i.writer.appendChangeLog(`FULL_SYNC_START clientId=${n.clientId} placeId=${n.placeId}`),i.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncStart",direction:"forward",path:`place_${n.placeId}`,details:`services:${n.totalServices} instances:${n.totalInstances}`}),y.info("Full sync started",{syncId:a,clientId:n.clientId,placeId:n.placeId,placeName:n.placeName,totalServices:n.totalServices,totalInstances:n.totalInstances}),r.status(200).json({status:"started",syncId:i.activeFullSyncSessionId})}async handleInitChunk(e,n,r){let i=this.ctx.places.get(e);if(!i||!i.activeFullSyncSessionId||!i.tmpIndex||!i.tmpWriter){r.status(400).json({error:"No active sync session",message:"Call sync/init with phase=start first"});return}if(i.activeClientId&&n.clientId&&i.activeClientId!==n.clientId){r.status(409).json({error:"Conflict",message:`This sync session belongs to client ${i.activeClientId}`});return}let a=i.activeFullSyncSessionId,o=this.getOrCreatePendingServiceTree(a,n),s=0,c=i.collisionDirMap;for(let u of n.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 Qr(i.tmpIndex,d,m.from,m.to),this.rewritePendingEffectivePaths(o,i.tmpIndex.getExplorerRoot(),$e.join(d,m.from),$e.join(d,m.to)));let h=i.tmpIndex.resolveChildrenDir(u.path),g=$e.join(d,f);g!==h&&c.set(h,g);let x=i.tmpIndex.sanitizeName(u.name),w=u;if(d!==p||f!==x){let k=i.tmpIndex.getExplorerRoot(),I=$e.relative(k,d).split($e.sep).filter(H=>H.length>0),O=Xe(["game",...I,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,n.chunkIndex,n.totalChunks);if(l){let u=this.buildServiceTree(i,o);await i.tmpWriter.writeTree(n.serviceName,u);let p=this.pendingServiceTrees.get(a);p?.delete(n.serviceName),p&&p.size===0&&this.pendingServiceTrees.delete(a)}i.syncProgress&&(i.syncProgress.processedInstances+=s,i.syncProgress.currentService=n.serviceName,i.syncProgress.currentChunkIndex=n.chunkIndex,i.syncProgress.currentTotalChunks=n.totalChunks,i.syncProgress.processedChunks++,i.syncProgress.bytesReceived+=JSON.stringify(n).length,l&&i.syncProgress.processedServices++),y.debug("Sync chunk processed",{placeId:e,serviceName:n.serviceName,chunkIndex:n.chunkIndex,totalChunks:n.totalChunks,processed:s}),r.status(200).json({processed:s,service:n.serviceName})}async handleInitComplete(e,n,r){let i=this.ctx.places.get(e);if(!i||!i.activeFullSyncSessionId){r.status(400).json({error:"No active sync session",message:"Call sync/init with phase=start first"});return}if(i.activeClientId&&n.clientId&&i.activeClientId!==n.clientId){r.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=$e.join(o,"explorer"),c=$e.join(o,`explorer_tmp_${a}`),l=this.getAndClearPreserveLocalFiles(a),u=new Map,p=[];if(l.length>0){for(let m of l){let h=$e.resolve(o,m);try{let g=await Xt.readFile(h,"utf-8");u.set(m,g)}catch{p.push(m)}}y.info("Backed up local files for preservation",{placeId:e,requested:l.length,backed:u.size,deleted:p.length})}try{await Xt.rm(s,{recursive:!0,force:!0})}catch{}await Xt.rename(c,s),i.instanceCount=n.instanceCount,i.scriptCount=n.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=$e.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[g,x]of i.tmpIndex.getAllHashes()){let w=$e.relative(m,g),_=$e.resolve(h,w);i.index.updateHashByValue(_,x)}for(let[g,x]of i.tmpIndex.getAllFileHashes()){let w=$e.relative(m,g),_=$e.resolve(h,w);i.index.updateFileHashByValue(_,x)}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 g=$e.resolve(o,m);try{await Xt.mkdir($e.dirname(g),{recursive:!0}),await Xt.writeFile(g,h,"utf-8")}catch(x){y.warn("Failed to restore preserved file",{path:m,error:x instanceof Error?x.message:String(x)})}}y.info("Restored preserved local files",{placeId:e,count:u.size})}if(p.length>0){let m=0;for(let h of p){let g=$e.resolve(o,h);try{await Xt.unlink(g),i.index.removeHash(g),m++}catch(x){x.code!=="ENOENT"&&y.warn("Failed to delete preserved-as-deleted file",{path:h,error:x instanceof Error?x.message:String(x)})}}m>0&&(await i.index.saveToDisk(),y.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}`}),y.info("Full sync completed",{placeId:e,instanceCount:i.instanceCount,scriptCount:i.scriptCount}),r.status(200).json({status:"completed",instanceCount:i.instanceCount,scriptCount:i.scriptCount,syncRoot:this.ctx.config.getPlaceRoot(e)})}setPreserveLocalFiles(e,n){this.preserveLocalFilesMap.set(e,n)}getAndClearPreserveLocalFiles(e){let n=this.preserveLocalFilesMap.get(e)||[];return this.preserveLocalFilesMap.delete(e),n}clearPreserveLocalFiles(e){this.preserveLocalFilesMap.delete(e)}clearPendingServiceTrees(e){this.pendingServiceTrees.delete(e)}startTTLTimerForPlace(e,n){let r=setTimeout(async()=>{y.warn("Incomplete sync TTL expired, cleaning up",{placeId:e.placeId,syncId:n});let i=this.ctx.config.getPlaceRoot(e.placeId),a=$e.join(i,`explorer_tmp_${n}`);try{await Xt.rm(a,{recursive:!0,force:!0})}catch(o){y.error("Failed to clean up expired temp dir",o instanceof Error?o:new Error(String(o)))}e.incompleteSyncTimer=null,e.activeFullSyncSessionId===n&&(e.activeFullSyncSessionId=null,e.activeClientId=null,e.state="idle",e.tmpIndex=null,this.pendingServiceTrees.delete(n),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))},vN);r&&typeof r=="object"&&"unref"in r&&r.unref(),e.incompleteSyncTimer=r}getOrCreatePendingServiceTree(e,n){let r=this.pendingServiceTrees.get(e);r||(r=new Map,this.pendingServiceTrees.set(e,r));let i=r.get(n.serviceName);if(i)return i;let a={serviceName:n.tree?.name??n.serviceName,serviceClassName:n.tree?.className??n.serviceClassName,zeroBasedChunkIndex:n.chunkIndex===0,instances:[]};return r.set(n.serviceName,a),a}isLastChunk(e,n,r){return r<=1?!0:e.zeroBasedChunkIndex?n>=r-1:n>=r}buildServiceTree(e,n){let r={name:n.serviceName,className:n.serviceClassName,childCount:0,children:[],syncedAt:new Date().toISOString()};for(let i of n.instances){let a=this.resolveEffectiveSegments(e,i.effectivePath),o=gt(i.originalPath);this.upsertTreeNode(r,a,o,i.className)}return this.recomputeTreeChildCounts(r),r.syncedAt=new Date().toISOString(),r}resolveEffectiveSegments(e,n){return e.tmpIndex?gt(n).map(r=>e.tmpIndex.sanitizeName(r)):gt(n)}rewritePendingEffectivePaths(e,n,r,i){let a=$e.relative(n,r).split($e.sep).filter(l=>l.length>0),o=$e.relative(n,i).split($e.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=Xe(["game",...a]),c=Xe(["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,n,r,i){if(n.length<=1)return;let a=e.children;for(let o=1;o<n.length;o++){let s=n[o],c=r[o],l=o===n.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 n=r=>{let i=r.children??[];r.children=i;for(let a of i)n(a);r.childCount=i.length};for(let r of e.children)n(r);e.childCount=e.children.length}clearTTLTimerForPlace(e,n){e.incompleteSyncTimer&&e.activeFullSyncSessionId===n&&(clearTimeout(e.incompleteSyncTimer),e.incompleteSyncTimer=null)}async cleanupStaleTempDirs(){let e=this.ctx.config.getSyncRoot();try{let n=await Xt.readdir(e,{withFileTypes:!0});for(let r of n)if(r.isDirectory()){if(r.name.startsWith("explorer_tmp_")){let i=$e.join(e,r.name);y.warn("Removing stale temp directory from crashed sync",{dir:r.name});try{await Xt.rm(i,{recursive:!0,force:!0})}catch(a){y.error(`Failed to remove stale temp dir: ${r.name}`,a instanceof Error?a:new Error(String(a)))}}if(r.name.startsWith("place_")){let i=$e.join(e,r.name);try{let a=await Xt.readdir(i,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=$e.join(i,o.name);y.warn("Removing stale temp directory from crashed sync",{dir:`${r.name}/${o.name}`});try{await Xt.rm(s,{recursive:!0,force:!0})}catch(c){y.error(`Failed to remove stale temp dir: ${r.name}/${o.name}`,c instanceof Error?c:new Error(String(c)))}}}catch{continue}}}}catch(n){if(n.code==="ENOENT")return;y.warn("Failed to scan for stale temp dirs",{error:n instanceof Error?n.message:String(n)})}}};import Gn from"path";import{promises as kN}from"fs";pe();var Cf=class{constructor(e){this.ctx=e}async handleReversePending(e,n){let r=this.ctx.resolveQueryPlaceId(e);if(r==null){n.status(200).json({pending:0,hasConflicts:!1,lastDetected:null});return}let i=this.ctx.places.get(r);if(!i){n.status(404).json({error:"Place not found",message:`No sync context for place ${r}`});return}let a={pending:i.fileWatcher?.getPendingCount()??0,hasConflicts:!1,lastDetected:i.fileWatcher?.getLastDetected()??null,forwardRestoreNeeded:i.forwardRestoreQueue.length};this.ctx.touchRuntimePlace(r),n.status(200).json(a)}async handleReverseSyncChanges(e,n){let r=this.ctx.resolveQueryPlaceId(e);if(r==null){n.status(200).json({changes:[],count:0});return}let i=this.ctx.places.get(r);if(!i){n.status(404).json({error:"Place not found",message:`No sync context for place ${r}`});return}if(i.state!=="syncing"){n.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(r),n.status(200).json({changes:o,count:o.length})}async handleReverseSyncResult(e,n){let r=e.body,i=r.placeId??this.ctx.getDefaultRuntimePlaceId();if(i==null){n.status(400).json({error:"Validation error",message:"placeId is required (in body or via active sync session)"});return}let a=r.appliedFiles??r.appliedPaths;if(!a||!Array.isArray(a)){n.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){n.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=Gn.resolve(s,u);if(!kf(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await kN.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=Ct(m),g=Et(m);g&&h&&await o.writer.removeFromTree(g,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}`}),n.status(200).json({updated:c,failed:l.length,errors:l})}async handleResolveConflict(e,n){let r=e.body;if(!r.fsPath||!r.resolution){n.status(400).json({error:"Validation error",message:"fsPath and resolution are required"});return}let{fsPath:i,resolution:a}=r,o=this.ctx.config.getSyncRoot();if(!kf(o,Gn.resolve(o,i))){n.status(403).json({error:"Forbidden",message:"Path is outside the sync root"});return}if(a==="skip"){n.status(200).json({status:"skipped",fsPath:i});return}let s;if(r.placeId&&(s=this.ctx.places.get(r.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){n.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=Gn.resolve(c,i);if(!kf(c,l)){n.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(),n.status(200).json({status:"resolved",resolution:"apply-studio",fsPath:i});return}if(a==="apply-file"){let d=await kN.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);n.status(200).json({status:"resolved",resolution:"apply-file",fsPath:i,instancePath:h,fileType:m,content:d});return}n.status(400).json({error:"Invalid resolution",message:`Unknown resolution: ${a}`})}async handleReverseRescan(e,n){let r=this.ctx.resolveQueryPlaceId(e);if(r==null){n.status(200).json({added:0});return}let i=this.ctx.places.get(r);if(!i){n.status(404).json({error:"Place not found",message:`No sync context for place ${r}`});return}if(!i.fileWatcher){n.status(200).json({added:0});return}let a=await i.fileWatcher.rescan();this.ctx.touchRuntimePlace(r),y.info("Reverse rescan completed",{placeId:r,added:a}),n.status(200).json({added:a})}resolveInstancePathForAppliedPath(e,n){let r=e.resolveInstancePathFromFsPath(n);if(r)return r;let i=e.getExplorerRoot(),a=Gn.relative(i,n);if(a.startsWith("..")||a===""||Gn.isAbsolute(a))return null;let o=a.split(Gn.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=Gn.basename(n),c=Gn.dirname(n),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(Co.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=i;for(let f of o){d=Gn.join(d,f);let m=Gn.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return Xe(p)}};pe();function IN(t){if(!t||typeof t!="object")return;let e=t;if(Array.isArray(e.instances))for(let n of e.instances)Array.isArray(n.properties)&&n.properties.length===0&&(n.properties={}),Array.isArray(n.attributes)&&n.attributes.length===0&&(n.attributes={});if(Array.isArray(e.changes))for(let n of e.changes)Array.isArray(n.properties)&&n.properties.length===0&&(n.properties={}),Array.isArray(n.attributes)&&n.attributes.length===0&&(n.attributes={})}var Uo=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;constructor(e){this.config=new cf(e),this.apiHandler=new Pf(this),this.changeProcessor=new If(this),this.initHandler=new $f(this),this.reverseHandler=new Cf(this),this.places=new sf({max:3,dispose:(n,r)=>{y.info("Disposing place context (LRU eviction)",{placeId:r}),this.activeFullSyncPlaceId===r&&(this.activeFullSyncPlaceId=null),this.activeRuntimeSyncPlaceId===r&&(this.activeRuntimeSyncPlaceId=null),n.fileWatcher&&(n.fileWatcher.stop().catch(i=>{y.error("Error stopping file watcher during dispose",i)}),n.fileWatcher=null),n.writer.stopChangeLogFlusher(),n.incompleteSyncTimer&&(clearTimeout(n.incompleteSyncTimer),n.incompleteSyncTimer=null),n.index.saveToDisk().catch(i=>{y.error("Error saving index during dispose",i)}),n.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(n.activeFullSyncSessionId),n.tmpWriter&&(n.tmpWriter.stopChangeLogFlusher(),n.tmpWriter=null),n.tmpIndex=null,n.collisionDirMap=null}})}getSyncRoot(){return this.config.getSyncRoot()}async getOrCreatePlaceContext(e,n){let r=this.places.get(e);if(r&&n){let i=this.config.getPlaceRoot(e),a=await this.config.resolvePlaceRoot(e,n);a!==i&&(y.info("Place root migrated, recreating context",{placeId:e,from:i,to:a}),this.places.delete(e),r=void 0)}if(!r){let i=await this.config.resolvePlaceRoot(e,n),a=Yw.join(i,"explorer");await Lo.mkdir(i,{recursive:!0}),await Lo.mkdir(a,{recursive:!0});let o=new Yr(i,a);await o.loadFromDisk();let s=new jo(this.config,o,e),c=new pf(this.config,o,i);s.startChangeLogFlusher(),r={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:{...qi},applyModes:{...Bi},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null},this.places.set(e,r),y.info("Created new place context",{placeId:e,placeRoot:i})}return r}async handleSyncInit(e,n){try{IN(e.body);let r=bN(e.body);if(!r.success){n.status(400).json({error:"Validation error",message:r.error});return}let i=r.data,a=i.phase==="start"?i.placeId??null:i.phase==="chunk"||i.phase==="complete"?this.activeFullSyncPlaceId:null;if(a==null){n.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,n);break;case"chunk":await this.initHandler.handleInitChunk(a,i,n);break;case"complete":await this.initHandler.handleInitComplete(a,i,n);break}}catch(r){this.sendError(n,r,"handleSyncInit")}}async handleSyncUpdate(e,n){try{IN(e.body);let r=_N(e.body);if(!r.success){n.status(400).json({error:"Validation error",message:r.error});return}let i=r.data,a=i.placeId??this.getDefaultRuntimePlaceId();if(a==null){n.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"){n.status(409).json({error:"Conflict",message:`Full sync in progress for place ${a}`});return}o.activeClientId=i.clientId,this.touchRuntimePlace(a);let s=yN(o,i.changes);y.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 _f(o,f)}catch(m){c.push({path:f.instancePath,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=xN&&(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),n.status(200).json(d)}catch(r){this.sendError(n,r,"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,n]of this.places.entries())if(n.state==="syncing"||n.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,n="runtime"){let r=e.query.placeId;if(r){let i=parseInt(r,10);if(!isNaN(i))return i}return n==="full"?this.activeFullSyncPlaceId:this.getDefaultRuntimePlaceId()}async handleSyncStatus(e,n){try{let r=this.resolveQueryPlaceId(e);if(r==null){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};n.status(200).json(s);return}let i=this.places.get(r);if(!i){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getPlaceRoot(r),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};n.status(200).json(s);return}let a=i.fileWatcher?.getPendingCount()??0;this.touchRuntimePlace(r);let o={state:i.state,instanceCount:i.instanceCount,scriptCount:i.scriptCount,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,syncRoot:this.config.getPlaceRoot(r),activeClientId:i.activeClientId,reverseSyncAvailable:a>0,modifiedFileCount:a,applyModes:i.applyModes,directions:i.directions,fileWatcherActive:i.fileWatcher!==null,forwardOnlyClasses:[...To]};n.status(200).json(o)}catch(r){this.sendError(n,r,"handleSyncStatus")}}async handleSyncStop(e,n){try{let r=e.body,i=r.placeId??this.getDefaultRuntimePlaceId();if(i==null){n.status(200).json({status:"idle",state:"idle",placeId:null,message:"No active sync place"});return}let a=this.places.get(i);if(!a){n.status(200).json({status:"idle",state:"idle",placeId:i,message:`No sync context for place ${i}`});return}if(r.clientId&&a.activeClientId&&r.clientId!==a.activeClientId&&(a.activeFullSyncSessionId!==null||a.state==="syncing"||a.state==="initializing")){n.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=Yw.join(s,`explorer_tmp_${o}`);await Lo.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),y.info("Sync stopped",{placeId:i,reason:r.reason??"requested"}),n.status(200).json({status:"stopped",state:"idle",placeId:i})}catch(r){this.sendError(n,r,"handleSyncStop")}}async handleSyncConfig(e,n){try{if(e.method==="GET"){n.status(200).json(this.config.getConfig());return}let r=wN(e.body);if(!r.success){n.status(400).json({error:"Validation error",message:r.error});return}let i=r.data;i.maxDepth!==void 0&&this.config.updateConfig({maxDepth:i.maxDepth}),i.maxInstances!==void 0&&this.config.updateConfig({maxInstances:i.maxInstances}),n.status(200).json({status:"updated",config:this.config.getConfig()})}catch(r){this.sendError(n,r,"handleSyncConfig")}}async initialize(){try{await this.config.loadFromMeta(),await this.initHandler.cleanupStaleTempDirs(),y.info("SyncController initialized")}catch(e){y.error("SyncController initialization failed",e instanceof Error?e:new Error(String(e)))}}async shutdown(){try{this.places.clear(),y.info("SyncController shut down")}catch(e){y.error("SyncController shutdown error",e instanceof Error?e:new Error(String(e)))}}async atomicWriteFile(e,n){let r=e+".tmp."+NQ().slice(0,8);try{await Lo.writeFile(r,n,"utf-8"),await Lo.rename(r,e)}catch(i){throw await Lo.unlink(r).catch(()=>{}),i}}async handlePreCheck(e,n){try{await this.apiHandler.handlePreCheck(e,n)}catch(r){this.sendError(n,r,"handlePreCheck")}}async handleSyncDirections(e,n){try{await this.apiHandler.handleSyncDirections(e,n)}catch(r){this.sendError(n,r,"handleSyncDirections")}}async handleForwardRestoreList(e,n){try{await this.apiHandler.handleForwardRestoreList(e,n)}catch(r){this.sendError(n,r,"handleForwardRestoreList")}}async handleReversePending(e,n){try{await this.reverseHandler.handleReversePending(e,n)}catch(r){this.sendError(n,r,"handleReversePending")}}async handleReverseSyncChanges(e,n){try{await this.reverseHandler.handleReverseSyncChanges(e,n)}catch(r){this.sendError(n,r,"handleReverseSyncChanges")}}async handleReverseSyncResult(e,n){try{await this.reverseHandler.handleReverseSyncResult(e,n)}catch(r){this.sendError(n,r,"handleReverseSyncResult")}}async handleResolveConflict(e,n){try{await this.reverseHandler.handleResolveConflict(e,n)}catch(r){this.sendError(n,r,"handleResolveConflict")}}async handleReverseRescan(e,n){try{await this.reverseHandler.handleReverseRescan(e,n)}catch(r){this.sendError(n,r,"handleReverseRescan")}}async handleSyncHistory(e,n){try{await this.apiHandler.handleSyncHistory(e,n)}catch(r){this.sendError(n,r,"handleSyncHistory")}}async startFileWatcherForPlace(e){if(e.state!=="syncing"){y.debug("Skipping file watcher start - place not syncing",{placeId:e.placeId,state:e.state});return}e.fileWatcher&&await e.fileWatcher.stop();let n=Yw.join(this.config.getPlaceRoot(e.placeId),"explorer");e.fileWatcher=new bf(n,e.index),e.writer.setOnWriteCallback(r=>{e.fileWatcher?.suppressPath(r)}),e.fileWatcher.setDirectionChecker(r=>{let i=FA(r);return e.directions[i]}),e.fileWatcher.setOnForwardViolation(r=>{e.forwardRestoreQueue.includes(r)||(e.forwardRestoreQueue.push(r),y.info("Forward violation queued for restore",{placeId:e.placeId,relativePath:r,queueSize:e.forwardRestoreQueue.length}))}),await e.fileWatcher.start(),y.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,n){return this.apiHandler.getHistoryDirect(e,n)}getDirectionsDirect(e){return this.apiHandler.getDirectionsDirect(e)}getProgressDirect(e){return this.apiHandler.getProgressDirect(e)}async readSyncedFile(e,n){return this.apiHandler.readSyncedFile(e,n)}async writeSyncedFile(e,n,r){await this.apiHandler.writeSyncedFile(e,n,r)}async executeViaDisk(e,n){return this.apiHandler.executeViaDisk(e,n)}sendError(e,n,r){let i=n instanceof Error?n.message:String(n);if(i.includes("Path traversal detected")){e.status(403).json({error:"Forbidden",message:i});return}let a=n.code;if(a==="ENOSPC"||a==="EPERM"||a==="EACCES"){e.status(500).json({error:"Disk error",message:i});return}y.error(`SyncController.${r} failed`,n instanceof Error?n:new Error(i)),e.status(500).json({error:"Internal error",message:`${r}: ${i}`})}};import{randomUUID as OQ}from"crypto";function PN(t,e){let n=OQ(),r=n.replace(/-/g,"").substring(0,8).toUpperCase(),i=`${r.substring(0,4)}-${r.substring(4,8)}`;return{config:t,app:e,instanceId:n,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,clientModeUpstreamContextCaptureEnabled:!0,clientModeLastHealthSuccessAt:null,clientModeLastHealthFailureAt:null,clientModeLastHealthError:null,historyManager:null,analyticsManager:null,executionContextManager:null,licenseState:null,syncController:null,internalActionExecutor:null,activeSyncOwnerInstanceId:null,activeProjectRoot:null,playtestControlCommand:null,aiClientName:"",pluginVersion:"",syncedSessionToken:null,serverLastCommandAt:null}}var $N=ri(xo(),1);pe();function CN(t){let e=$N.default.json({limit:"5mb"});t.app.use((n,r,i)=>{if(n.path.startsWith("/sync/")){i();return}e(n,r,i)}),t.app.use((n,r,i)=>{r.setHeader("Access-Control-Allow-Origin","http://localhost:3002"),r.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),r.setHeader("Access-Control-Allow-Headers","Content-Type"),i()}),t.app.use((n,r,i)=>{y.debug(`${n.method} ${n.path}`,{ip:n.ip}),i()})}import{randomUUID as kl}from"crypto";pe();function DQ(){let t=process.env.WEPPY_ROBLOX_MCP_VERSION?.trim();return t||null}var We=DQ()??"2.0.8";Rf();function zN(t){let e=new Set;t.aiClientName&&e.add(t.aiClientName);for(let n of t.mcpInstances.values())n.aiClientName&&e.add(n.aiClientName);return Array.from(e)}function LQ(t){let n=Date.now();return Array.from(t.pluginClients.values()).filter(r=>n-r.lastSeen<1e4)}function AN(t){let n=Date.now();for(let[r,i]of t.mcpInstances)i.lastSeen&&n-i.lastSeen>15e3&&(t.mcpInstances.delete(r),i.sessionId&&t.executionContextManager?.endSession(i.sessionId),y.debug("Removed stale MCP instance",{instanceId:r,lastSeen:i.lastSeen}))}function NN(t,e,n){let r=e.query.clientId;if(r&&t.pluginClients.has(r)){let i=t.pluginClients.get(r);i.lastSeen=Date.now()}try{let i=e.body;if(!i||!Array.isArray(i.selection)||typeof i.count!="number"){y.warn("Invalid selection update request",{body:i}),n.status(400).json({error:"Invalid request body"});return}let a=r||"unknown",o=Date.now();t.cachedSelectionMap.set(a,{selection:i.selection,count:i.count,timestamp:o,clientId:a}),y.debug("Selection cache updated",{count:i.count,clientId:a,timestamp:o}),n.json({status:"ok",timestamp:o})}catch(i){y.error("Error handling selection update",i),n.status(500).json({error:"Internal server error"})}}function ON(t,e,n){let r=parseInt(e.query.maxAge)||3e4,i=Sl(t,r);i?n.json({cached:!0,...i}):n.json({cached:!1,message:"No cached selection available"})}function Sl(t,e=3e4,n){if(t.cachedSelectionMap.size===0)return null;let r;if(n)r=t.cachedSelectionMap.get(n);else for(let a of t.cachedSelectionMap.values())(!r||a.timestamp>r.timestamp)&&(r=a);if(!r)return null;let i=Date.now()-r.timestamp;return e===0||i<=e?r:null}function DN(t,e,n){try{let r=e.body;if(!r.clientId){n.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.get(r.clientId),a=Date.now(),o={clientId:r.clientId,projectName:r.projectName,placeName:r.placeName,placeId:r.placeId,pluginVersion:r.pluginVersion,connectedAt:i?.connectedAt||a,lastSeen:a,commandsProcessed:i?.commandsProcessed||0,connectionType:"polling"};t.pluginClients.set(r.clientId,o),t.pendingCommands.has(r.clientId)||t.pendingCommands.set(r.clientId,[]),r.pluginVersion&&(t.pluginVersion=r.pluginVersion),$t(t,"connection",{clientId:r.clientId,placeId:o.projectName,placeName:o.placeName,status:"connected"}),dl(t,{timestamp:new Date().toISOString(),type:"plugin",status:"connected",clientId:r.clientId,message:`Plugin connected \u2014 ${r.clientId}`,...o.placeId!==void 0?{placeId:o.placeId}:{},...o.placeName?{placeName:o.placeName}:{}}),t.analyticsManager&&(r.pluginVersion&&t.analyticsManager.setPluginVersion(r.pluginVersion),t.analyticsManager.trackPluginConnected()),y.info("Plugin client registered",{clientId:r.clientId,projectName:r.projectName,placeName:r.placeName,isReconnect:!!i}),n.json({status:"ok",clientId:r.clientId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:We,connectedAt:o.connectedAt,aiClientNames:zN(t),serverStartTime:t.startTime,mcpInstanceCount:t.mcpInstances.size+1})}catch(r){y.error("Error registering plugin client",r),n.status(500).json({error:"Internal server error"})}}function MN(t,e,n){let r=e.body?.clientId;if(!r){n.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.has(r);t.pluginClients.delete(r),t.pendingCommands.delete(r),i&&($t(t,"connection",{clientId:r,status:"disconnected"}),dl(t,{timestamp:new Date().toISOString(),type:"plugin",status:"disconnected",clientId:r,message:`Plugin disconnected \u2014 ${r}`})),y.info("Plugin client unregistered",{clientId:r,existed:i}),n.json({status:"ok",existed:i})}function LN(t,e,n){try{let r=e.body;if(!r.instanceId){n.status(400).json({error:"Missing instanceId"});return}let i=Date.now(),a={instanceId:r.instanceId,...typeof r.sessionId=="string"?{sessionId:r.sessionId}:{},pid:r.pid,connectedAt:i,isServer:!1,lastSeen:i};r.aiClientName&&(a.aiClientName=r.aiClientName),r.cwd&&(a.cwd=r.cwd),"projectRoot"in r&&(a.projectRoot=r.projectRoot),t.mcpInstances.set(r.instanceId,a),$t(t,"mcp_status",{aiClientName:a.aiClientName??"Unknown",instanceId:r.instanceId,status:"registered"}),dl(t,{timestamp:new Date().toISOString(),type:"mcp",status:"registered",instanceId:r.instanceId,message:`MCP registered \u2014 ${a.aiClientName??r.instanceId}`,...a.aiClientName?{aiClientName:a.aiClientName}:{}}),y.info("MCP instance registered (client mode)",{instanceId:r.instanceId,pid:r.pid,cwd:r.cwd}),n.json({status:"ok",instanceId:r.instanceId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:We,mcpInstanceCount:t.mcpInstances.size+1})}catch(r){y.error("Error registering MCP instance",r),n.status(500).json({error:"Internal server error"})}}function UN(t,e,n){let r=e.body?.instanceId;if(!r){n.status(400).json({error:"Missing instanceId"});return}let i=t.mcpInstances.get(r),a=!!i;t.mcpInstances.delete(r),i?.sessionId&&t.executionContextManager?.endSession(i.sessionId),a&&($t(t,"mcp_status",{aiClientName:i?.aiClientName??"Unknown",instanceId:r,status:"unregistered"}),dl(t,{timestamp:new Date().toISOString(),type:"mcp",status:"unregistered",instanceId:r,message:`MCP unregistered \u2014 ${i?.aiClientName??r}`,...i?.aiClientName?{aiClientName:i.aiClientName}:{}})),y.info("MCP instance unregistered",{instanceId:r,existed:a}),n.json({status:"ok",existed:a})}function FN(t,e,n){let{instanceId:r,aiClientName:i}=e.body;if(r&&i){let a=t.mcpInstances.get(r);a&&(a.aiClientName=i)}n.json({status:"ok"})}function qN(t){let n=Date.now();for(let[r,i]of t.pluginClients)n-i.lastSeen>3e4&&(t.pluginClients.delete(r),t.pendingCommands.delete(r));return AN(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:We,uptime:n-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:ml(),...t.aiClientName?{aiClientName:t.aiClientName}:{}},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function BN(t,e){e.json(qN(t))}function ZN(t,e,n){let r=e.query.instanceId;r&&t.mcpInstances.has(r)&&(t.mcpInstances.get(r).lastSeen=Date.now()),AN(t);let i=LQ(t),a=t.sseClients.size,c={...{status:"online",connectedClients:a+i.length,queuedCommands:t.commandQueue.size,uptime:Date.now()-t.startTime,version:We,enableContextCapture:t.executionContextManager?.isEnabled()??t.config.enableContextCapture??!0,isClientMode:t.isClientMode,pid:process.pid,sessionId:t.sessionId},instanceId:t.instanceId,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:zN(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}}:{}};n.json(c)}function HN(t,e,n,r){let i=e.ip||e.socket.remoteAddress||"";if(!(i==="127.0.0.1"||i==="::1"||i==="::ffff:127.0.0.1"||i==="localhost")){y.warn("Shutdown request rejected from non-localhost",{ip:i}),n.status(403).json({error:"Forbidden: localhost only"});return}y.info("Shutdown request received, initiating graceful shutdown",{requestedBy:i,uptime:Date.now()-t.startTime}),n.json({status:"shutting_down",message:"Server will shutdown gracefully",pid:process.pid}),setTimeout(async()=>{try{await r(),y.info("Graceful shutdown completed"),process.exit(0)}catch(o){y.error("Error during graceful shutdown",o),process.exit(1)}},100)}async function jf(t){if(t.isClientMode)try{let e=await fetch(`${t.baseUrl}/connection-info`);if(e.ok)return await e.json()}catch(e){y.warn("Failed to fetch connection info from server",{error:e})}return qN(t)}pe();var zf={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 UQ={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 Af(t){return UQ[t]||"plugin"}var Nf=100,FQ=Object.entries(zf).reduce((t,[e,n])=>{for(let r of Object.values(n.mapping))t[r]=e;return t},{});function VN(t){return{toolName:FQ[t]||t,actionName:t}}function WN(t,e){if(typeof t.contextId=="string"||!e||typeof e!="object")return t;let n=e.contextId;return typeof n=="string"?{...t,contextId:n}:t}function GN(t,e,n){y.info("Plugin connected via SSE"),n.setHeader("Content-Type","text/event-stream"),n.setHeader("Cache-Control","no-cache"),n.setHeader("Connection","keep-alive"),t.sseClients.add(n),oS(n,{event:"command",id:kl(),data:{action:"connected",requestId:kl(),params:{serverVersion:We,timestamp:Date.now()}}});let r=setInterval(()=>{oS(n,{event:"command",id:kl(),data:{action:"keepalive",requestId:kl(),params:{timestamp:Date.now()}}})},3e4);n.on("close",()=>{y.info("Plugin disconnected from SSE"),clearInterval(r),t.sseClients.delete(n)})}function oS(t,e){let n=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[g,x]of i.tmpIndex.getAllHashes()){let w=$e.relative(m,g),_=$e.resolve(h,w);i.index.updateHashByValue(_,x)}for(let[g,x]of i.tmpIndex.getAllFileHashes()){let w=$e.relative(m,g),_=$e.resolve(h,w);i.index.updateFileHashByValue(_,x)}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 g=$e.resolve(o,m);try{await Xt.mkdir($e.dirname(g),{recursive:!0}),await Xt.writeFile(g,h,"utf-8")}catch(x){y.warn("Failed to restore preserved file",{path:m,error:x instanceof Error?x.message:String(x)})}}y.info("Restored preserved local files",{placeId:e,count:u.size})}if(p.length>0){let m=0;for(let h of p){let g=$e.resolve(o,h);try{await Xt.unlink(g),i.index.removeHash(g),m++}catch(x){x.code!=="ENOENT"&&y.warn("Failed to delete preserved-as-deleted file",{path:h,error:x instanceof Error?x.message:String(x)})}}m>0&&(await i.index.saveToDisk(),y.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}`}),y.info("Full sync completed",{placeId:e,instanceCount:i.instanceCount,scriptCount:i.scriptCount}),r.status(200).json({status:"completed",instanceCount:i.instanceCount,scriptCount:i.scriptCount,syncRoot:this.ctx.config.getPlaceRoot(e)})}setPreserveLocalFiles(e,n){this.preserveLocalFilesMap.set(e,n)}getAndClearPreserveLocalFiles(e){let n=this.preserveLocalFilesMap.get(e)||[];return this.preserveLocalFilesMap.delete(e),n}clearPreserveLocalFiles(e){this.preserveLocalFilesMap.delete(e)}clearPendingServiceTrees(e){this.pendingServiceTrees.delete(e)}startTTLTimerForPlace(e,n){let r=setTimeout(async()=>{y.warn("Incomplete sync TTL expired, cleaning up",{placeId:e.placeId,syncId:n});let i=this.ctx.config.getPlaceRoot(e.placeId),a=$e.join(i,`explorer_tmp_${n}`);try{await Xt.rm(a,{recursive:!0,force:!0})}catch(o){y.error("Failed to clean up expired temp dir",o instanceof Error?o:new Error(String(o)))}e.incompleteSyncTimer=null,e.activeFullSyncSessionId===n&&(e.activeFullSyncSessionId=null,e.activeClientId=null,e.state="idle",e.tmpIndex=null,this.pendingServiceTrees.delete(n),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))},vN);r&&typeof r=="object"&&"unref"in r&&r.unref(),e.incompleteSyncTimer=r}getOrCreatePendingServiceTree(e,n){let r=this.pendingServiceTrees.get(e);r||(r=new Map,this.pendingServiceTrees.set(e,r));let i=r.get(n.serviceName);if(i)return i;let a={serviceName:n.tree?.name??n.serviceName,serviceClassName:n.tree?.className??n.serviceClassName,zeroBasedChunkIndex:n.chunkIndex===0,instances:[]};return r.set(n.serviceName,a),a}isLastChunk(e,n,r){return r<=1?!0:e.zeroBasedChunkIndex?n>=r-1:n>=r}buildServiceTree(e,n){let r={name:n.serviceName,className:n.serviceClassName,childCount:0,children:[],syncedAt:new Date().toISOString()};for(let i of n.instances){let a=this.resolveEffectiveSegments(e,i.effectivePath),o=gt(i.originalPath);this.upsertTreeNode(r,a,o,i.className)}return this.recomputeTreeChildCounts(r),r.syncedAt=new Date().toISOString(),r}resolveEffectiveSegments(e,n){return e.tmpIndex?gt(n).map(r=>e.tmpIndex.sanitizeName(r)):gt(n)}rewritePendingEffectivePaths(e,n,r,i){let a=$e.relative(n,r).split($e.sep).filter(l=>l.length>0),o=$e.relative(n,i).split($e.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=Xe(["game",...a]),c=Xe(["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,n,r,i){if(n.length<=1)return;let a=e.children;for(let o=1;o<n.length;o++){let s=n[o],c=r[o],l=o===n.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 n=r=>{let i=r.children??[];r.children=i;for(let a of i)n(a);r.childCount=i.length};for(let r of e.children)n(r);e.childCount=e.children.length}clearTTLTimerForPlace(e,n){e.incompleteSyncTimer&&e.activeFullSyncSessionId===n&&(clearTimeout(e.incompleteSyncTimer),e.incompleteSyncTimer=null)}async cleanupStaleTempDirs(){let e=this.ctx.config.getSyncRoot();try{let n=await Xt.readdir(e,{withFileTypes:!0});for(let r of n)if(r.isDirectory()){if(r.name.startsWith("explorer_tmp_")){let i=$e.join(e,r.name);y.warn("Removing stale temp directory from crashed sync",{dir:r.name});try{await Xt.rm(i,{recursive:!0,force:!0})}catch(a){y.error(`Failed to remove stale temp dir: ${r.name}`,a instanceof Error?a:new Error(String(a)))}}if(r.name.startsWith("place_")){let i=$e.join(e,r.name);try{let a=await Xt.readdir(i,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=$e.join(i,o.name);y.warn("Removing stale temp directory from crashed sync",{dir:`${r.name}/${o.name}`});try{await Xt.rm(s,{recursive:!0,force:!0})}catch(c){y.error(`Failed to remove stale temp dir: ${r.name}/${o.name}`,c instanceof Error?c:new Error(String(c)))}}}catch{continue}}}}catch(n){if(n.code==="ENOENT")return;y.warn("Failed to scan for stale temp dirs",{error:n instanceof Error?n.message:String(n)})}}};import Gn from"path";import{promises as kN}from"fs";pe();var Cf=class{constructor(e){this.ctx=e}async handleReversePending(e,n){let r=this.ctx.resolveQueryPlaceId(e);if(r==null){n.status(200).json({pending:0,hasConflicts:!1,lastDetected:null});return}let i=this.ctx.places.get(r);if(!i){n.status(404).json({error:"Place not found",message:`No sync context for place ${r}`});return}let a={pending:i.fileWatcher?.getPendingCount()??0,hasConflicts:!1,lastDetected:i.fileWatcher?.getLastDetected()??null,forwardRestoreNeeded:i.forwardRestoreQueue.length};this.ctx.touchRuntimePlace(r),n.status(200).json(a)}async handleReverseSyncChanges(e,n){let r=this.ctx.resolveQueryPlaceId(e);if(r==null){n.status(200).json({changes:[],count:0});return}let i=this.ctx.places.get(r);if(!i){n.status(404).json({error:"Place not found",message:`No sync context for place ${r}`});return}if(i.state!=="syncing"){n.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(r),n.status(200).json({changes:o,count:o.length})}async handleReverseSyncResult(e,n){let r=e.body,i=r.placeId??this.ctx.getDefaultRuntimePlaceId();if(i==null){n.status(400).json({error:"Validation error",message:"placeId is required (in body or via active sync session)"});return}let a=r.appliedFiles??r.appliedPaths;if(!a||!Array.isArray(a)){n.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){n.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=Gn.resolve(s,u);if(!kf(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await kN.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=Ct(m),g=Et(m);g&&h&&await o.writer.removeFromTree(g,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}`}),n.status(200).json({updated:c,failed:l.length,errors:l})}async handleResolveConflict(e,n){let r=e.body;if(!r.fsPath||!r.resolution){n.status(400).json({error:"Validation error",message:"fsPath and resolution are required"});return}let{fsPath:i,resolution:a}=r,o=this.ctx.config.getSyncRoot();if(!kf(o,Gn.resolve(o,i))){n.status(403).json({error:"Forbidden",message:"Path is outside the sync root"});return}if(a==="skip"){n.status(200).json({status:"skipped",fsPath:i});return}let s;if(r.placeId&&(s=this.ctx.places.get(r.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){n.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=Gn.resolve(c,i);if(!kf(c,l)){n.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(),n.status(200).json({status:"resolved",resolution:"apply-studio",fsPath:i});return}if(a==="apply-file"){let d=await kN.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);n.status(200).json({status:"resolved",resolution:"apply-file",fsPath:i,instancePath:h,fileType:m,content:d});return}n.status(400).json({error:"Invalid resolution",message:`Unknown resolution: ${a}`})}async handleReverseRescan(e,n){let r=this.ctx.resolveQueryPlaceId(e);if(r==null){n.status(200).json({added:0});return}let i=this.ctx.places.get(r);if(!i){n.status(404).json({error:"Place not found",message:`No sync context for place ${r}`});return}if(!i.fileWatcher){n.status(200).json({added:0});return}let a=await i.fileWatcher.rescan();this.ctx.touchRuntimePlace(r),y.info("Reverse rescan completed",{placeId:r,added:a}),n.status(200).json({added:a})}resolveInstancePathForAppliedPath(e,n){let r=e.resolveInstancePathFromFsPath(n);if(r)return r;let i=e.getExplorerRoot(),a=Gn.relative(i,n);if(a.startsWith("..")||a===""||Gn.isAbsolute(a))return null;let o=a.split(Gn.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=Gn.basename(n),c=Gn.dirname(n),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(Co.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=i;for(let f of o){d=Gn.join(d,f);let m=Gn.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return Xe(p)}};pe();function IN(t){if(!t||typeof t!="object")return;let e=t;if(Array.isArray(e.instances))for(let n of e.instances)Array.isArray(n.properties)&&n.properties.length===0&&(n.properties={}),Array.isArray(n.attributes)&&n.attributes.length===0&&(n.attributes={});if(Array.isArray(e.changes))for(let n of e.changes)Array.isArray(n.properties)&&n.properties.length===0&&(n.properties={}),Array.isArray(n.attributes)&&n.attributes.length===0&&(n.attributes={})}var Uo=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;constructor(e){this.config=new cf(e),this.apiHandler=new Pf(this),this.changeProcessor=new If(this),this.initHandler=new $f(this),this.reverseHandler=new Cf(this),this.places=new sf({max:3,dispose:(n,r)=>{y.info("Disposing place context (LRU eviction)",{placeId:r}),this.activeFullSyncPlaceId===r&&(this.activeFullSyncPlaceId=null),this.activeRuntimeSyncPlaceId===r&&(this.activeRuntimeSyncPlaceId=null),n.fileWatcher&&(n.fileWatcher.stop().catch(i=>{y.error("Error stopping file watcher during dispose",i)}),n.fileWatcher=null),n.writer.stopChangeLogFlusher(),n.incompleteSyncTimer&&(clearTimeout(n.incompleteSyncTimer),n.incompleteSyncTimer=null),n.index.saveToDisk().catch(i=>{y.error("Error saving index during dispose",i)}),n.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(n.activeFullSyncSessionId),n.tmpWriter&&(n.tmpWriter.stopChangeLogFlusher(),n.tmpWriter=null),n.tmpIndex=null,n.collisionDirMap=null}})}getSyncRoot(){return this.config.getSyncRoot()}async getOrCreatePlaceContext(e,n){let r=this.places.get(e);if(r&&n){let i=this.config.getPlaceRoot(e),a=await this.config.resolvePlaceRoot(e,n);a!==i&&(y.info("Place root migrated, recreating context",{placeId:e,from:i,to:a}),this.places.delete(e),r=void 0)}if(!r){let i=await this.config.resolvePlaceRoot(e,n),a=Yw.join(i,"explorer");await Lo.mkdir(i,{recursive:!0}),await Lo.mkdir(a,{recursive:!0});let o=new Yr(i,a);await o.loadFromDisk();let s=new jo(this.config,o,e),c=new pf(this.config,o,i);s.startChangeLogFlusher(),r={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:{...qi},applyModes:{...Bi},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null},this.places.set(e,r),y.info("Created new place context",{placeId:e,placeRoot:i})}return r}async handleSyncInit(e,n){try{IN(e.body);let r=bN(e.body);if(!r.success){n.status(400).json({error:"Validation error",message:r.error});return}let i=r.data,a=i.phase==="start"?i.placeId??null:i.phase==="chunk"||i.phase==="complete"?this.activeFullSyncPlaceId:null;if(a==null){n.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,n);break;case"chunk":await this.initHandler.handleInitChunk(a,i,n);break;case"complete":await this.initHandler.handleInitComplete(a,i,n);break}}catch(r){this.sendError(n,r,"handleSyncInit")}}async handleSyncUpdate(e,n){try{IN(e.body);let r=_N(e.body);if(!r.success){n.status(400).json({error:"Validation error",message:r.error});return}let i=r.data,a=i.placeId??this.getDefaultRuntimePlaceId();if(a==null){n.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"){n.status(409).json({error:"Conflict",message:`Full sync in progress for place ${a}`});return}o.activeClientId=i.clientId,this.touchRuntimePlace(a);let s=yN(o,i.changes);y.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 _f(o,f)}catch(m){c.push({path:f.instancePath,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=xN&&(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),n.status(200).json(d)}catch(r){this.sendError(n,r,"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,n]of this.places.entries())if(n.state==="syncing"||n.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,n="runtime"){let r=e.query.placeId;if(r){let i=parseInt(r,10);if(!isNaN(i))return i}return n==="full"?this.activeFullSyncPlaceId:this.getDefaultRuntimePlaceId()}async handleSyncStatus(e,n){try{let r=this.resolveQueryPlaceId(e);if(r==null){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};n.status(200).json(s);return}let i=this.places.get(r);if(!i){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getPlaceRoot(r),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};n.status(200).json(s);return}let a=i.fileWatcher?.getPendingCount()??0;this.touchRuntimePlace(r);let o={state:i.state,instanceCount:i.instanceCount,scriptCount:i.scriptCount,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,syncRoot:this.config.getPlaceRoot(r),activeClientId:i.activeClientId,reverseSyncAvailable:a>0,modifiedFileCount:a,applyModes:i.applyModes,directions:i.directions,fileWatcherActive:i.fileWatcher!==null,forwardOnlyClasses:[...To]};n.status(200).json(o)}catch(r){this.sendError(n,r,"handleSyncStatus")}}async handleSyncStop(e,n){try{let r=e.body,i=r.placeId??this.getDefaultRuntimePlaceId();if(i==null){n.status(200).json({status:"idle",state:"idle",placeId:null,message:"No active sync place"});return}let a=this.places.get(i);if(!a){n.status(200).json({status:"idle",state:"idle",placeId:i,message:`No sync context for place ${i}`});return}if(r.clientId&&a.activeClientId&&r.clientId!==a.activeClientId&&(a.activeFullSyncSessionId!==null||a.state==="syncing"||a.state==="initializing")){n.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=Yw.join(s,`explorer_tmp_${o}`);await Lo.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),y.info("Sync stopped",{placeId:i,reason:r.reason??"requested"}),n.status(200).json({status:"stopped",state:"idle",placeId:i})}catch(r){this.sendError(n,r,"handleSyncStop")}}async handleSyncConfig(e,n){try{if(e.method==="GET"){n.status(200).json(this.config.getConfig());return}let r=wN(e.body);if(!r.success){n.status(400).json({error:"Validation error",message:r.error});return}let i=r.data;i.maxDepth!==void 0&&this.config.updateConfig({maxDepth:i.maxDepth}),i.maxInstances!==void 0&&this.config.updateConfig({maxInstances:i.maxInstances}),n.status(200).json({status:"updated",config:this.config.getConfig()})}catch(r){this.sendError(n,r,"handleSyncConfig")}}async initialize(){try{await this.config.loadFromMeta(),await this.initHandler.cleanupStaleTempDirs(),y.info("SyncController initialized")}catch(e){y.error("SyncController initialization failed",e instanceof Error?e:new Error(String(e)))}}async shutdown(){try{this.places.clear(),y.info("SyncController shut down")}catch(e){y.error("SyncController shutdown error",e instanceof Error?e:new Error(String(e)))}}async atomicWriteFile(e,n){let r=e+".tmp."+NQ().slice(0,8);try{await Lo.writeFile(r,n,"utf-8"),await Lo.rename(r,e)}catch(i){throw await Lo.unlink(r).catch(()=>{}),i}}async handlePreCheck(e,n){try{await this.apiHandler.handlePreCheck(e,n)}catch(r){this.sendError(n,r,"handlePreCheck")}}async handleSyncDirections(e,n){try{await this.apiHandler.handleSyncDirections(e,n)}catch(r){this.sendError(n,r,"handleSyncDirections")}}async handleForwardRestoreList(e,n){try{await this.apiHandler.handleForwardRestoreList(e,n)}catch(r){this.sendError(n,r,"handleForwardRestoreList")}}async handleReversePending(e,n){try{await this.reverseHandler.handleReversePending(e,n)}catch(r){this.sendError(n,r,"handleReversePending")}}async handleReverseSyncChanges(e,n){try{await this.reverseHandler.handleReverseSyncChanges(e,n)}catch(r){this.sendError(n,r,"handleReverseSyncChanges")}}async handleReverseSyncResult(e,n){try{await this.reverseHandler.handleReverseSyncResult(e,n)}catch(r){this.sendError(n,r,"handleReverseSyncResult")}}async handleResolveConflict(e,n){try{await this.reverseHandler.handleResolveConflict(e,n)}catch(r){this.sendError(n,r,"handleResolveConflict")}}async handleReverseRescan(e,n){try{await this.reverseHandler.handleReverseRescan(e,n)}catch(r){this.sendError(n,r,"handleReverseRescan")}}async handleSyncHistory(e,n){try{await this.apiHandler.handleSyncHistory(e,n)}catch(r){this.sendError(n,r,"handleSyncHistory")}}async startFileWatcherForPlace(e){if(e.state!=="syncing"){y.debug("Skipping file watcher start - place not syncing",{placeId:e.placeId,state:e.state});return}e.fileWatcher&&await e.fileWatcher.stop();let n=Yw.join(this.config.getPlaceRoot(e.placeId),"explorer");e.fileWatcher=new bf(n,e.index),e.writer.setOnWriteCallback(r=>{e.fileWatcher?.suppressPath(r)}),e.fileWatcher.setDirectionChecker(r=>{let i=FA(r);return e.directions[i]}),e.fileWatcher.setOnForwardViolation(r=>{e.forwardRestoreQueue.includes(r)||(e.forwardRestoreQueue.push(r),y.info("Forward violation queued for restore",{placeId:e.placeId,relativePath:r,queueSize:e.forwardRestoreQueue.length}))}),await e.fileWatcher.start(),y.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,n){return this.apiHandler.getHistoryDirect(e,n)}getDirectionsDirect(e){return this.apiHandler.getDirectionsDirect(e)}getProgressDirect(e){return this.apiHandler.getProgressDirect(e)}async readSyncedFile(e,n){return this.apiHandler.readSyncedFile(e,n)}async writeSyncedFile(e,n,r){await this.apiHandler.writeSyncedFile(e,n,r)}async executeViaDisk(e,n){return this.apiHandler.executeViaDisk(e,n)}sendError(e,n,r){let i=n instanceof Error?n.message:String(n);if(i.includes("Path traversal detected")){e.status(403).json({error:"Forbidden",message:i});return}let a=n.code;if(a==="ENOSPC"||a==="EPERM"||a==="EACCES"){e.status(500).json({error:"Disk error",message:i});return}y.error(`SyncController.${r} failed`,n instanceof Error?n:new Error(i)),e.status(500).json({error:"Internal error",message:`${r}: ${i}`})}};import{randomUUID as OQ}from"crypto";function PN(t,e){let n=OQ(),r=n.replace(/-/g,"").substring(0,8).toUpperCase(),i=`${r.substring(0,4)}-${r.substring(4,8)}`;return{config:t,app:e,instanceId:n,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,clientModeUpstreamContextCaptureEnabled:!0,clientModeLastHealthSuccessAt:null,clientModeLastHealthFailureAt:null,clientModeLastHealthError:null,historyManager:null,analyticsManager:null,executionContextManager:null,licenseState:null,syncController:null,internalActionExecutor:null,activeSyncOwnerInstanceId:null,activeProjectRoot:null,playtestControlCommand:null,aiClientName:"",pluginVersion:"",syncedSessionToken:null,serverLastCommandAt:null}}var $N=ri(xo(),1);pe();function CN(t){let e=$N.default.json({limit:"5mb"});t.app.use((n,r,i)=>{if(n.path.startsWith("/sync/")){i();return}e(n,r,i)}),t.app.use((n,r,i)=>{r.setHeader("Access-Control-Allow-Origin","http://localhost:3002"),r.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),r.setHeader("Access-Control-Allow-Headers","Content-Type"),i()}),t.app.use((n,r,i)=>{y.debug(`${n.method} ${n.path}`,{ip:n.ip}),i()})}import{randomUUID as kl}from"crypto";pe();function DQ(){let t=process.env.WEPPY_ROBLOX_MCP_VERSION?.trim();return t||null}var We=DQ()??"2.0.9";Rf();function zN(t){let e=new Set;t.aiClientName&&e.add(t.aiClientName);for(let n of t.mcpInstances.values())n.aiClientName&&e.add(n.aiClientName);return Array.from(e)}function LQ(t){let n=Date.now();return Array.from(t.pluginClients.values()).filter(r=>n-r.lastSeen<1e4)}function AN(t){let n=Date.now();for(let[r,i]of t.mcpInstances)i.lastSeen&&n-i.lastSeen>15e3&&(t.mcpInstances.delete(r),i.sessionId&&t.executionContextManager?.endSession(i.sessionId),y.debug("Removed stale MCP instance",{instanceId:r,lastSeen:i.lastSeen}))}function NN(t,e,n){let r=e.query.clientId;if(r&&t.pluginClients.has(r)){let i=t.pluginClients.get(r);i.lastSeen=Date.now()}try{let i=e.body;if(!i||!Array.isArray(i.selection)||typeof i.count!="number"){y.warn("Invalid selection update request",{body:i}),n.status(400).json({error:"Invalid request body"});return}let a=r||"unknown",o=Date.now();t.cachedSelectionMap.set(a,{selection:i.selection,count:i.count,timestamp:o,clientId:a}),y.debug("Selection cache updated",{count:i.count,clientId:a,timestamp:o}),n.json({status:"ok",timestamp:o})}catch(i){y.error("Error handling selection update",i),n.status(500).json({error:"Internal server error"})}}function ON(t,e,n){let r=parseInt(e.query.maxAge)||3e4,i=Sl(t,r);i?n.json({cached:!0,...i}):n.json({cached:!1,message:"No cached selection available"})}function Sl(t,e=3e4,n){if(t.cachedSelectionMap.size===0)return null;let r;if(n)r=t.cachedSelectionMap.get(n);else for(let a of t.cachedSelectionMap.values())(!r||a.timestamp>r.timestamp)&&(r=a);if(!r)return null;let i=Date.now()-r.timestamp;return e===0||i<=e?r:null}function DN(t,e,n){try{let r=e.body;if(!r.clientId){n.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.get(r.clientId),a=Date.now(),o={clientId:r.clientId,projectName:r.projectName,placeName:r.placeName,placeId:r.placeId,pluginVersion:r.pluginVersion,connectedAt:i?.connectedAt||a,lastSeen:a,commandsProcessed:i?.commandsProcessed||0,connectionType:"polling"};t.pluginClients.set(r.clientId,o),t.pendingCommands.has(r.clientId)||t.pendingCommands.set(r.clientId,[]),r.pluginVersion&&(t.pluginVersion=r.pluginVersion),$t(t,"connection",{clientId:r.clientId,placeId:o.projectName,placeName:o.placeName,status:"connected"}),dl(t,{timestamp:new Date().toISOString(),type:"plugin",status:"connected",clientId:r.clientId,message:`Plugin connected \u2014 ${r.clientId}`,...o.placeId!==void 0?{placeId:o.placeId}:{},...o.placeName?{placeName:o.placeName}:{}}),t.analyticsManager&&(r.pluginVersion&&t.analyticsManager.setPluginVersion(r.pluginVersion),t.analyticsManager.trackPluginConnected()),y.info("Plugin client registered",{clientId:r.clientId,projectName:r.projectName,placeName:r.placeName,isReconnect:!!i}),n.json({status:"ok",clientId:r.clientId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:We,connectedAt:o.connectedAt,aiClientNames:zN(t),serverStartTime:t.startTime,mcpInstanceCount:t.mcpInstances.size+1})}catch(r){y.error("Error registering plugin client",r),n.status(500).json({error:"Internal server error"})}}function MN(t,e,n){let r=e.body?.clientId;if(!r){n.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.has(r);t.pluginClients.delete(r),t.pendingCommands.delete(r),i&&($t(t,"connection",{clientId:r,status:"disconnected"}),dl(t,{timestamp:new Date().toISOString(),type:"plugin",status:"disconnected",clientId:r,message:`Plugin disconnected \u2014 ${r}`})),y.info("Plugin client unregistered",{clientId:r,existed:i}),n.json({status:"ok",existed:i})}function LN(t,e,n){try{let r=e.body;if(!r.instanceId){n.status(400).json({error:"Missing instanceId"});return}let i=Date.now(),a={instanceId:r.instanceId,...typeof r.sessionId=="string"?{sessionId:r.sessionId}:{},pid:r.pid,connectedAt:i,isServer:!1,lastSeen:i};r.aiClientName&&(a.aiClientName=r.aiClientName),r.cwd&&(a.cwd=r.cwd),"projectRoot"in r&&(a.projectRoot=r.projectRoot),t.mcpInstances.set(r.instanceId,a),$t(t,"mcp_status",{aiClientName:a.aiClientName??"Unknown",instanceId:r.instanceId,status:"registered"}),dl(t,{timestamp:new Date().toISOString(),type:"mcp",status:"registered",instanceId:r.instanceId,message:`MCP registered \u2014 ${a.aiClientName??r.instanceId}`,...a.aiClientName?{aiClientName:a.aiClientName}:{}}),y.info("MCP instance registered (client mode)",{instanceId:r.instanceId,pid:r.pid,cwd:r.cwd}),n.json({status:"ok",instanceId:r.instanceId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:We,mcpInstanceCount:t.mcpInstances.size+1})}catch(r){y.error("Error registering MCP instance",r),n.status(500).json({error:"Internal server error"})}}function UN(t,e,n){let r=e.body?.instanceId;if(!r){n.status(400).json({error:"Missing instanceId"});return}let i=t.mcpInstances.get(r),a=!!i;t.mcpInstances.delete(r),i?.sessionId&&t.executionContextManager?.endSession(i.sessionId),a&&($t(t,"mcp_status",{aiClientName:i?.aiClientName??"Unknown",instanceId:r,status:"unregistered"}),dl(t,{timestamp:new Date().toISOString(),type:"mcp",status:"unregistered",instanceId:r,message:`MCP unregistered \u2014 ${i?.aiClientName??r}`,...i?.aiClientName?{aiClientName:i.aiClientName}:{}})),y.info("MCP instance unregistered",{instanceId:r,existed:a}),n.json({status:"ok",existed:a})}function FN(t,e,n){let{instanceId:r,aiClientName:i}=e.body;if(r&&i){let a=t.mcpInstances.get(r);a&&(a.aiClientName=i)}n.json({status:"ok"})}function qN(t){let n=Date.now();for(let[r,i]of t.pluginClients)n-i.lastSeen>3e4&&(t.pluginClients.delete(r),t.pendingCommands.delete(r));return AN(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:We,uptime:n-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:ml(),...t.aiClientName?{aiClientName:t.aiClientName}:{}},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function BN(t,e){e.json(qN(t))}function ZN(t,e,n){let r=e.query.instanceId;r&&t.mcpInstances.has(r)&&(t.mcpInstances.get(r).lastSeen=Date.now()),AN(t);let i=LQ(t),a=t.sseClients.size,c={...{status:"online",connectedClients:a+i.length,queuedCommands:t.commandQueue.size,uptime:Date.now()-t.startTime,version:We,enableContextCapture:t.executionContextManager?.isEnabled()??t.config.enableContextCapture??!0,isClientMode:t.isClientMode,pid:process.pid,sessionId:t.sessionId},instanceId:t.instanceId,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:zN(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}}:{}};n.json(c)}function HN(t,e,n,r){let i=e.ip||e.socket.remoteAddress||"";if(!(i==="127.0.0.1"||i==="::1"||i==="::ffff:127.0.0.1"||i==="localhost")){y.warn("Shutdown request rejected from non-localhost",{ip:i}),n.status(403).json({error:"Forbidden: localhost only"});return}y.info("Shutdown request received, initiating graceful shutdown",{requestedBy:i,uptime:Date.now()-t.startTime}),n.json({status:"shutting_down",message:"Server will shutdown gracefully",pid:process.pid}),setTimeout(async()=>{try{await r(),y.info("Graceful shutdown completed"),process.exit(0)}catch(o){y.error("Error during graceful shutdown",o),process.exit(1)}},100)}async function jf(t){if(t.isClientMode)try{let e=await fetch(`${t.baseUrl}/connection-info`);if(e.ok)return await e.json()}catch(e){y.warn("Failed to fetch connection info from server",{error:e})}return qN(t)}pe();var zf={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 UQ={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 Af(t){return UQ[t]||"plugin"}var Nf=100,FQ=Object.entries(zf).reduce((t,[e,n])=>{for(let r of Object.values(n.mapping))t[r]=e;return t},{});function VN(t){return{toolName:FQ[t]||t,actionName:t}}function WN(t,e){if(typeof t.contextId=="string"||!e||typeof e!="object")return t;let n=e.contextId;return typeof n=="string"?{...t,contextId:n}:t}function GN(t,e,n){y.info("Plugin connected via SSE"),n.setHeader("Content-Type","text/event-stream"),n.setHeader("Cache-Control","no-cache"),n.setHeader("Connection","keep-alive"),t.sseClients.add(n),oS(n,{event:"command",id:kl(),data:{action:"connected",requestId:kl(),params:{serverVersion:We,timestamp:Date.now()}}});let r=setInterval(()=>{oS(n,{event:"command",id:kl(),data:{action:"keepalive",requestId:kl(),params:{timestamp:Date.now()}}})},3e4);n.on("close",()=>{y.info("Plugin disconnected from SSE"),clearInterval(r),t.sseClients.delete(n)})}function oS(t,e){let n=JSON.stringify(e.data);t.write(`event: ${e.event}
127
127
  `),t.write(`id: ${e.id}
128
128
  `),t.write(`data: ${n}
129
129
 
@@ -149,7 +149,7 @@ data: ${JSON.stringify(n)}
149
149
  `}var em=class{syncRoot;placeRootResolver;constructor(e=process.cwd(),n){this.syncRoot=Yi.join(e,"roblox-project-sync"),this.placeRootResolver=n??null}async write(e){let n=Yee(new Date),r;if(this.placeRootResolver)try{r=await this.placeRootResolver(e.placeId,e.placeName)}catch{r=Yi.join(this.syncRoot,`place_${e.placeId}`)}else r=Yi.join(this.syncRoot,`place_${e.placeId}`);let i=Yi.join(r,"tests",n),a=Yi.join(i,Jee),o=Yi.join(i,Kee),s=Yi.join(i,Xee);await Hee(i,{recursive:!0});let c=[ES(a,tte(e)),ES(o,nte(e.logs))];return Qee(e)&&c.push(ES(s,ete(e))),await Promise.all(c),{timestamp:n,testDir:i,reportPath:a,logPath:o}}};var nD="__MCP_TestRunner",rte=`game.ServerScriptService.${nD}`,ite=60,ate=300,ote=500,ste=6e4,cte=1e3,lte=5,ute=500,jS="[WEPPY_TEST]:";var pte="FINISHED";function RS(t){return new Promise(e=>{setTimeout(e,t)})}function dte(t){let e=typeof t.script=="string"?t.script:"";if(!e.trim())throw new Error("script is required");let n=typeof t.timeout=="number"?t.timeout:typeof t.timeout=="string"?Number(t.timeout):ite;if(!Number.isFinite(n)||n<=0)throw new Error("timeout must be a positive number");let r=t.mode==="run"?"run":"play",a=(typeof t.test_name=="string"?t.test_name:typeof t.testName=="string"?t.testName:"").trim()||`run_test_${new Date().toISOString().replace(/[:.]/g,"-")}`;return{script:e,testName:a,timeoutSeconds:Math.min(ate,Math.max(1,Math.floor(n))),mode:r}}function fte(t){return!t||typeof t!="object"||Array.isArray(t)?{}:t}function mte(t){return t&&typeof t=="object"&&!Array.isArray(t)?t:void 0}function hte(t,e){let r={...mte(t.contextSummary)??{}};return typeof t.testScenario=="string"&&t.testScenario.trim()&&(r.testScenario=t.testScenario.trim()),typeof t.expectedBehavior=="string"&&t.expectedBehavior.trim()&&(r.expectedBehavior=t.expectedBehavior.trim()),typeof t.observedBehavior=="string"&&t.observedBehavior.trim()&&(r.observedBehavior=t.observedBehavior.trim()),(r.testScenario===void 0||r.testScenario===null||r.testScenario==="")&&e.testName.trim()&&(r.testScenario=e.testName.trim()),Object.keys(r).length>0?r:void 0}function gte(t){for(let e of t)if(typeof e.message=="string"&&/\[TEST\]\s*FAIL\b/i.test(e.message))return e.message.trim();return null}function tD(t,e){let n=t&&typeof t=="object"&&!Array.isArray(t)?{...t}:{};return n.mode===void 0&&(n.mode=e.mode),n.timeout===void 0&&(n.timeout=e.timeoutSeconds),n}function yte(t){return t.split(`
150
150
  `).map(e=>` ${e}`).join(`
151
151
  `)}function vte(t){let e=JSON.stringify(t.testName),n=JSON.stringify(t.mode);return['local HttpService = game:GetService("HttpService")',`local TEST_NAME = ${e}`,`local TEST_MODE = ${n}`,`local SIGNAL_PREFIX = ${JSON.stringify(jS)}`,"","local function emit(kind, payload)"," payload = payload or {}"," payload.testName = payload.testName or TEST_NAME"," payload.mode = payload.mode or TEST_MODE",' print(string.format("%s%s:%s", SIGNAL_PREFIX, kind, HttpService:JSONEncode(payload)))',"end","","local function __mcp_test_body()",yte(t.script),"end","","local startedAt = DateTime.now().UnixTimestampMillis",'emit("START", { startedAt = startedAt })',"local clockStartedAt = os.clock()","local ok, err = xpcall(__mcp_test_body, function(message)"," return debug.traceback(tostring(message), 2)","end)","local durationMs = math.floor((os.clock() - clockStartedAt) * 1000)",'emit("FINISHED", {'," startedAt = startedAt,"," finishedAt = DateTime.now().UnixTimestampMillis,"," passed = ok,"," durationMs = durationMs,",' errorMessage = if ok then "" else tostring(err),',"})",""].join(`
152
- `)}function xte(t){if(typeof t.message!="string"||!t.message.startsWith(jS))return null;let e=t.message.slice(jS.length),n=e.indexOf(":");if(n===-1)return null;let r=e.slice(0,n),i=e.slice(n+1),a={...typeof t.seq=="number"?{seq:t.seq}:{},...typeof t.timestampMs=="number"?{timestampMs:t.timestampMs}:{}},o;try{o=JSON.parse(i)}catch{o={raw:i}}return{kind:r,payload:o,message:t.message,...a}}function bte(t){return!!t&&typeof t=="object"}var tm=class{httpBridge;reportWriter;constructor(e,n){if(this.httpBridge=e,n)this.reportWriter=n;else{let r=async(i,a)=>{let o=e.getSyncController();if(!o)throw new Error("SyncController not available");return o.config.resolvePlaceRoot(i,a)};this.reportWriter=new em(void 0,r)}}async runTest(e){let n=dte(e),r=Date.now(),i={placeId:typeof e.placeId=="number"?e.placeId:0,name:"Unknown"},a=!1,o=0,s=!1,c=!1,l=!1,u="",p=null,d=[],f=[];try{i=await this.getPlaceInfo(e),a=!0,await this.ensurePlaytestIdle(i.placeId);let k=await this.executePlugin("clear_output_logs",this.withPlace({},i.placeId));o=typeof k.lastSeq=="number"?k.lastSeq:0,await this.executePlugin("create_script",this.withPlace({scriptType:"Script",parent:"game.ServerScriptService",name:nD,source:vte(n)},i.placeId)),s=!0,await this.executePlugin("start_playtest",this.withPlace({mode:n.mode},i.placeId)),c=!0;let I=Date.now()+n.timeoutSeconds*1e3;for(;Date.now()<I;){let O=await this.pollLogs(i.placeId,o);if(d.push(...O.logs),f.push(...O.signals),o=O.nextSeq,O.finishedPayload){p=O.finishedPayload;break}await RS(ote)}p||(l=!0,u=`Timed out after ${n.timeoutSeconds} seconds`)}catch(k){u=k instanceof Error?k.message:String(k),y.warn("run_test orchestration failed",{testName:n.testName,error:u})}finally{if(c&&a&&await this.stopIfNeeded(i.placeId),s&&a&&await this.deleteRunnerScript(i.placeId),a){let k=await this.pollLogs(i.placeId,o);d.push(...k.logs),f.push(...k.signals),o=k.nextSeq,!p&&k.finishedPayload&&(p=k.finishedPayload)}}let m=typeof p?.errorMessage=="string"?p.errorMessage:"";!u&&m&&(u=m);let h=gte(d);!u&&h&&(u=h);let g=typeof p?.durationMs=="number"?p.durationMs:Math.max(0,Date.now()-r),x=!!p?.passed&&!l&&!u,w=hte(e,n);return{...await this.reportWriter.write({placeId:i.placeId,placeName:i.name,testName:n.testName,mode:n.mode,passed:x,timedOut:l,durationMs:g,errorMessage:u,logs:d,signals:f,...typeof e.contextId=="string"?{contextId:e.contextId}:{},...w?{contextSummary:w}:{},replayMetadata:tD(e.replayMetadata,n)}),passed:x,timedOut:l,testName:n.testName,mode:n.mode,placeId:i.placeId,placeName:i.name,durationMs:g,signalCount:f.length,...typeof e.contextId=="string"?{contextId:e.contextId}:{},...w?{contextSummary:w}:{},replayMetadata:tD(e.replayMetadata,n)}}async ensurePlaytestIdle(e){let n=await this.executePlugin("get_play_status",this.withPlace({},e)),r=typeof n.state=="string"?n.state:"unknown";if(r!=="edit")throw new Error(`Playtest already running (current state: ${r})`)}withPlace(e,n){return{...e,placeId:n}}async getPlaceInfo(e){let n=await this.executePlugin("get_place_info",e.placeId!==void 0?{placeId:e.placeId}:{}),r=typeof n.placeId=="number"?n.placeId:0,i=typeof n.name=="string"&&n.name.trim()?n.name:"Unknown";return{placeId:r,name:i}}async stopIfNeeded(e){try{if((await this.executePlugin("get_play_status",this.withPlace({},e))).state==="edit")return;await this.executePlugin("stop_playtest",this.withPlace({},e));for(let r=0;r<lte;r+=1)if(await RS(cte),(await this.executePlugin("get_play_status",this.withPlace({},e))).state==="edit")return}catch(n){y.warn("run_test cleanup could not stop playtest",{placeId:e,error:n instanceof Error?n.message:String(n)})}}async deleteRunnerScript(e){try{await this.executePlugin("delete_script",this.withPlace({path:rte},e))}catch(n){y.warn("run_test cleanup could not delete runner script",{placeId:e,error:n instanceof Error?n.message:String(n)})}}async pollLogs(e,n){let r=[],i=[],a=n,o=null;for(;;){let s=await this.executeLogPoll(e,a),c=Array.isArray(s.logs)?s.logs:[];for(let l of c){r.push(l),typeof l.seq=="number"&&(a=l.seq);let u=xte(l);u&&(i.push(u),u.kind===pte&&bte(u.payload)&&(o=u.payload))}if(c.length===0&&(s.cursorStatus==="reset"||s.cursorStatus==="gap")&&typeof s.lastSeq=="number"&&(a=s.lastSeq),!s.hasMore)break}return{logs:r,signals:i,nextSeq:a,finishedPayload:o}}async executeLogPoll(e,n){let r=this.withPlace({sinceSeq:n,limit:500},e);try{return await this.executePlugin("get_output_logs",r)}catch(i){y.debug("run_test pollLogs retrying after failure",{placeId:e,sinceSeq:n,error:i instanceof Error?i.message:String(i)}),await RS(ute);try{return await this.executePlugin("get_output_logs",r)}catch(a){return y.debug("run_test pollLogs retry failed, returning empty batch",{placeId:e,sinceSeq:n,error:a instanceof Error?a.message:String(a)}),{logs:[],hasMore:!1}}}}async executePlugin(e,n,r={timeout:ste}){let i=await this.httpBridge.executeCommand(e,n,r);if(!i.success)throw new Error(i.error||`${e} failed`);return fte(i.data)}};function ane(t){let e=t.toLowerCase();return/upstream_server_down/.test(e)?{errorType:"bridge_error",errorDetail:"upstream_down"}:/^http [45]\d\d/.test(e)?{errorType:"bridge_error",errorDetail:"http_error"}:/timeout/.test(e)?{errorType:"command_timeout",errorDetail:"timeout"}:/econnrefused|socket hang up|econnreset|epipe|network/.test(e)?{errorType:"bridge_error",errorDetail:"connection"}:/abort/.test(e)?{errorType:"command_timeout",errorDetail:"aborted"}:/runtime error|traceback|attempt to/.test(e)?{errorType:"plugin_runtime",errorDetail:"runtime_error"}:/instance not found|not found in/.test(e)?{errorType:"execution_failed",errorDetail:"not_found"}:/parent not found/.test(e)?{errorType:"execution_failed",errorDetail:"parent_not_found"}:/is required|missing required/.test(e)?{errorType:"execution_failed",errorDetail:"missing_param"}:/invalid classname|invalid class/.test(e)?{errorType:"execution_failed",errorDetail:"invalid_class"}:/unknown action/.test(e)?{errorType:"execution_failed",errorDetail:"unknown_action"}:/failed to insert asset/.test(e)?{errorType:"execution_failed",errorDetail:"asset_error"}:/failed to read property|property access denied/.test(e)?{errorType:"execution_failed",errorDetail:"property_error"}:/must be|invalid|cannot/.test(e)?{errorType:"execution_failed",errorDetail:"validation"}:{errorType:"execution_failed",errorDetail:"other"}}function one(t){let n=(t instanceof Error?t.message:String(t)).toLowerCase();return/timeout/.test(n)?{errorType:"command_timeout",errorDetail:"timeout"}:/econnrefused|socket hang up|network|econnreset|epipe/.test(n)?{errorType:"bridge_error",errorDetail:"connection"}:/eaddrinuse/.test(n)?{errorType:"bridge_error",errorDetail:"port_in_use"}:{errorType:"exception",errorDetail:"unexpected"}}function na(t){let e=t.split(".");return e[e.length-1]||t}function yt(t){let e=t.trim();return e.startsWith("game.")?e:e==="Workspace"||e.startsWith("Workspace.")?`game.${e}`:e}function jD(t,e,n,r){let i=r&&typeof r=="object"&&!Array.isArray(r)?r:null,a=i&&typeof i.path=="string"?i.path:null,o=typeof n.path=="string"?n.path:null,s=typeof n.parent=="string"?n.parent:null,c=typeof n.name=="string"?n.name:null,l=typeof n.property=="string"?n.property:null,u=typeof n.test_name=="string"?n.test_name.trim():typeof n.testName=="string"?n.testName.trim():"";if(t==="manage_properties"&&o&&l)return[{kind:"property",target:yt(`${o}.${l}`),label:`${na(o)} ${l}`}];if(t==="manage_scripts"&&a)return[{kind:"script",target:yt(a),label:na(a)}];if(t==="manage_scripts"&&s&&c){let p=`${s}.${c}`;return[{kind:"script",target:yt(p),label:c}]}if(t==="manage_scripts"&&o)return[{kind:"script",target:yt(o),label:na(o)}];if(t==="mutate_instances"){if(a)return[{kind:"instance",target:yt(a),label:na(a)}];if(s&&c){let d=`${s}.${c}`;return[{kind:"instance",target:yt(d),label:c}]}let p=typeof n.sourcePath=="string"?n.sourcePath:s||o;if(p)return[{kind:"instance",target:yt(p),label:na(p)}]}if(t==="manage_terrain")return[{kind:"terrain",target:yt("game.Workspace.Terrain"),label:"Terrain"}];if(t==="manage_lighting")return[{kind:"lighting",target:yt("game.Lighting"),label:"Lighting"}];if(t==="manage_assets"){let p=a||(s&&c?`${s}.${c}`:s||"game.Workspace");return[{kind:"asset",target:yt(p),label:na(p)}]}return e==="run_test"&&u?[{kind:"gameplay",target:yt(u),label:u}]:o?[{kind:"other",target:yt(o),label:na(o)}]:[]}function sne(t,e){let n=new Map;for(let r of t)n.set(yt(r.target),{...r,target:yt(r.target)});for(let r of e)n.set(yt(r.target),{...r,target:yt(r.target)});return[...n.values()]}var cne={manage_scripts:new Set(["set_script_source","create_script","delete_script","edit_script_lines","replace_in_scripts","insert_script_lines","delete_script_lines"]),mutate_instances:new Set(["create_instance","create_instance_with_properties","clone_instance","delete_instance","move_instance","rename_instance","pivot_to","create_instance_tree","mass_create_instances","mass_delete_instances","mass_duplicate","smart_duplicate"]),manage_properties:new Set(["set_property","set_multiple_properties","set_attribute","delete_attribute","add_tag","remove_tag","set_calculated_property","set_relative_property","mass_set_property","modify_children"]),manage_assets:new Set(["insert_model","insert_free_model","insert_package","search_and_insert_model"]),manage_lighting:new Set(["set_lighting","set_atmosphere","set_sky","set_terrain","set_time_of_day"]),manage_audio:new Set(["play_sound","stop_sound","pause_sound","resume_sound","set_listener"]),manage_animation:new Set(["load_animation","play_animation","stop_animation"]),manage_physics:new Set(["register_collision_group","set_collidable"]),manage_effects:new Set(["emit_particles","clear_particles","toggle_effect"]),manage_tween:new Set(["create_tween","play_tween","pause_tween","cancel_tween"]),manage_terrain:new Set(["terrain_fill_block","terrain_fill_ball","terrain_fill_cylinder","terrain_fill_wedge","terrain_clear","terrain_clear_region","terrain_replace_material","terrain_set_material_color","terrain_write_voxels","terrain_generate","terrain_smooth"]),manage_sync:new Set(["sync_write_file"])},YS=class{server;httpBridge;config;historyManager;executionContextManager;clientModeContextIdsByScope=new Map;analyticsManager;licenseStateManager;testRunner;stopPromise=null;constructor(e){this.config=e,this.server=new Lp({name:"weppy-roblox-mcp",version:We},{capabilities:{tools:{}}}),this.httpBridge=new MO(e),this.testRunner=new tm(this.httpBridge),this.historyManager=new Zf({enableLocalHistory:e.enableLocalHistory??!1,enableLocalStatistics:e.enableLocalStatistics??!1,dataDir:e.dataDir??gr()}),this.historyManager.setPlaceResolver(i=>{let a=i.placeId,o=typeof a=="number"?a:typeof a=="string"&&a.trim().length>0?Number.parseInt(a,10):null;if(o!==null&&Number.isFinite(o))return{placeId:o,placeName:typeof i.placeName=="string"?i.placeName:null};let s=this.httpBridge.getSyncController()?.getDefaultRuntimePlaceId();return typeof s=="number"&&Number.isFinite(s)?{placeId:s}:{}}),this.executionContextManager=JO({enabled:e.enableContextCapture??!0,dataDir:e.dataDir??gr(),runtimePlaceIdResolver:()=>this.httpBridge.getSyncController()?.getDefaultRuntimePlaceId()??null,runtimePlaceNameResolver:i=>Ut(this.httpBridge.getBridgeContext(),i)}),this.analyticsManager=new Gf({enabled:e.enableTelemetry});let n=new Kf({baseUrl:e.licenseApiBaseUrl||"https://roblox-license-api.hope841026.workers.dev",projectId:e.licenseProjectId||"roblox-mcp",provider:e.licenseProvider||"gumroad",timeoutMs:e.licenseRequestTimeoutMs??8e3,retryCount:e.licenseRetryCount??1,retryDelayMs:e.licenseRetryDelayMs??300}),r=new Yf;this.licenseStateManager=new Qf(n,r,this.httpBridge.getInstanceId(),{fallbackMaxHours:e.licenseFallbackMaxHours??48}),this.httpBridge.setHistoryManager(this.historyManager),this.httpBridge.setExecutionContextManager(this.executionContextManager),this.httpBridge.setAnalyticsManager(this.analyticsManager),this.httpBridge.setLicenseStateManager(this.licenseStateManager),this.httpBridge.setInternalActionExecutor(async(i,a)=>{let o=await this.executeInternalAction(i,a,this.httpBridge.getSyncController()),s={success:o.success};return o.data!==void 0&&(s.data=o.data),o.error&&(s.error=o.error),s}),this.setupHandlers(),y.info("MCP Server initialized",{name:"weppy-roblox-mcp",version:We,tools:qp.length,localHistoryEnabled:e.enableLocalHistory??!1,localStatisticsEnabled:e.enableLocalStatistics??!1,telemetryEnabled:e.enableTelemetry??!1,licenseApiConfigured:this.licenseStateManager.isConfigured()})}setupHandlers(){this.server.setRequestHandler(Jy,async()=>(y.debug("Received tools/list request"),{tools:qp})),this.server.setRequestHandler(Ys,async e=>{let{name:n,arguments:r}=e.params;y.info("Received tool call",{tool:n,args:r});try{let i=await this.executeTool(n,r||{});return i.success?{content:[{type:"text",text:i.data!==void 0?JSON.stringify(i.data,null,2):JSON.stringify({success:!0,message:"Command executed"})}]}:{content:[{type:"text",text:JSON.stringify({success:!1,error:i.error},null,2)}],isError:!0}}catch(i){return y.error("Tool execution failed",i),{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}})}async executeInternalAction(e,n,r){let i=this.resolveExecutionContextSessionId(n);if(e==="get_cached_selection"){let s=n.maxAge,c=this.httpBridge.getCachedSelection(s!==void 0?s:3e4);if(!c)return{success:!0,data:{cached:!1,message:"No cached selection data available. The plugin may not be connected or no selection changes have occurred yet."}};let l=Date.now()-c.timestamp;return{success:!0,data:{cached:!0,selection:c.selection,count:c.count,timestamp:c.timestamp,age:l}}}if(e==="get_connection_info")return{success:!0,data:await this.httpBridge.getConnectionInfo()};if(e==="run_test"){if(this.httpBridge.getIsClientMode()){let s=Object.prototype.hasOwnProperty.call(n,"timeout")?typeof n.timeout=="number"?n.timeout:typeof n.timeout=="string"?Number(n.timeout):Number.NaN:null,c=Number.isFinite(s)&&s!==null&&s>0?await this.httpBridge.executeCommand(e,n,{timeout:s*1e3+1e4}):await this.httpBridge.executeCommand(e,n),l={success:c.success};return c.data!==void 0&&(l.data=c.data),c.error&&(l.error=c.error),c.success||(l.failureCode="PROXY_FAILED"),l}try{let s=await this.enrichRunTestParams(n,i),c=await this.testRunner.runTest(s);if(typeof s.contextId=="string"&&typeof c.timestamp=="string")try{await this.executionContextManager.appendLinks({sessionId:i,placeId:typeof s.placeId=="number"?s.placeId:null,contextId:s.contextId,links:{playtestTimestamps:[c.timestamp]}})}catch(l){y.warn("run_test context link bookkeeping failed",{contextId:s.contextId,placeId:typeof s.placeId=="number"?s.placeId:null,error:l instanceof Error?l.message:String(l)})}return{success:!0,data:c}}catch(s){return{success:!1,error:s instanceof Error?s.message:String(s),failureCode:"INTERNAL_FAILED"}}}if(!e.startsWith("sync_"))return{success:!1,error:`Unknown internal action: ${e}`,failureCode:"INTERNAL_FAILED"};if(!r&&this.httpBridge.getIsClientMode()){let s=await this.httpBridge.executeCommand(e,n),c={success:s.success};return s.data!==void 0&&(c.data=s.data),s.error&&(c.error=s.error),s.success||(c.failureCode="PROXY_FAILED"),c}let a=await FO(e,n,r),o={success:a.success};return a.data!==void 0&&(o.data=a.data),a.error&&(o.error=a.error),a.success||(o.failureCode="INTERNAL_FAILED"),o}async executePluginBatch(e,n){let r={commands:e.map(({action:a,params:o})=>({action:a,params:o})),stopOnError:n},i=await this.httpBridge.executeCommand("batch_execute",r);if(i.success&&Array.isArray(i.data?.results)){let a=i.data.results;return e.map((o,s)=>{let c=a[s];if(!c)return{index:o.index,action:o.action,success:!1,error:"Batch result missing"};let l={index:o.index,action:o.action,success:c.success};return c.data!==void 0&&(l.data=c.data),c.error&&(l.error=c.error),l})}return e.map(a=>({index:a.index,action:a.action,success:!1,error:i.error||"Batch execution failed"}))}async executeTool(e,n){let r=Date.now();if(this.httpBridge.noteLocalCommandActivity(r),!qp.find(p=>p.name===e)){let p=`Unknown tool: ${e}`,d=Date.now()-r;return await this.historyManager.recordFailure(e,n,p,d,"UNKNOWN_TOOL"),this.analyticsManager.trackToolCall(e,"basic",!1,void 0,"execution_failed","unknown_tool"),this.analyticsManager.trackToolError(e,"basic",void 0,"execution_failed","unknown_tool"),{success:!1,error:p}}let{action:a,params:o}=vS(e,n),s=await this.attachContextIfNeeded(e,a,n,o),c=s.args,l=s.params,u=Bo(a);if(u==="pro"){let p=await this.licenseStateManager.evaluateProAccess();if(!p.allowed){if(p.state.reason==="device_temporarily_blocked"){let d=Date.now()-r,f=p.reason||"This device is temporarily blocked";return await this.historyManager.recordFailure(e,c,f,d,"DEVICE_BLOCKED",a),this.analyticsManager.trackToolCall(e,u,!1,a,"device_blocked"),this.analyticsManager.trackToolError(e,u,a,"device_blocked"),{success:!1,error:`${f} (status=${p.state.status})`}}this.analyticsManager.trackToolCall(e,u,!1,a,"delegated_to_plugin"),y.debug("Pro action delegated to plugin for fallback",{toolName:e,action:a})}}y.debug("Executing tool",{toolName:e,action:a,args:l,tier:u});try{let p=this.httpBridge.getSyncController(),d=xS(e,a,p);if(d.target==="internal"){let g=await this.executeInternalAction(a,l,p),x=Date.now()-r,w=this.withResultContextId(c,g.data);return g.success?(await this.historyManager.recordSuccess(e,w,g.data,x,a),await this.refreshImplicitContextIfNeeded(e,a,w,g.data),this.analyticsManager.trackToolCall(e,u,!0,a),{success:!0,data:g.data}):(await this.historyManager.recordFailure(e,w,g.error||"Internal operation failed",x,g.failureCode||"INTERNAL_FAILED",a),this.analyticsManager.trackToolCall(e,u,!1,a,"execution_failed","other"),this.analyticsManager.trackToolError(e,u,a,"execution_failed","other"),{success:!1,error:g.error||"Internal operation failed"})}if(d.target==="disk"){let g=await bS(a,l,p),x=Date.now()-r;if(!g.success)y.warn("Disk execution failed, falling back to plugin",{action:a,error:g.error});else return await this.historyManager.recordSuccess(e,c,g.data,x,a),await this.refreshImplicitContextIfNeeded(e,a,c,g.data),this.analyticsManager.trackToolCall(e,u,!0,a),{success:!0,data:g.data}}if(a==="batch_execute"&&Array.isArray(l.commands)){let g=l.commands,x=l.stopOnError!==!1,w=[],_=[],k=async()=>{if(_.length===0)return!1;let ye=await this.executePluginBatch(_,x);_=[];let ie=!1;for(let De of ye)w[De.index]={action:De.action,success:De.success,data:De.data,error:De.error},!De.success&&x&&(ie=!0);return ie},I=!1;for(let ye=0;ye<g.length;ye++){let ie=g[ye],De=ie.params||{},Ge=(ie.toolName?await this.attachContextIfNeeded(ie.toolName,ie.action,De,De):{args:De,params:De}).params,Ft=xS(ie.toolName||"",ie.action,p).target;if(Ft==="plugin"){_.push({index:ye,action:ie.action,params:Ge,toolName:ie.toolName});continue}if(await k()){I=!0;break}let nt;if(Ft==="internal"){let qt=await this.executeInternalAction(ie.action,Ge,p);nt={success:qt.success,data:qt.data,error:qt.error}}else{let qt=await bS(ie.action,Ge,p);if(qt.success)nt={success:!0,data:qt.data};else{y.warn("Disk execution failed inside batch, falling back to plugin",{action:ie.action,error:qt.error});let ti=await this.httpBridge.executeCommand(ie.action,Ge);nt={success:ti.success,data:ti.data,error:ti.error}}}if(w[ye]={action:ie.action,success:nt.success,data:nt.data,error:nt.error},!nt.success&&x){I=!0;break}}I||(I=await k());let O=w.filter(Boolean),H=Date.now()-r,Be=O.every(ye=>ye.success);return await this.historyManager.recordSuccess(e,c,{results:O},H,a),await this.refreshImplicitContextIfNeeded(e,a,c,{results:O}),this.analyticsManager.trackToolCall(e,u,Be,a),Be||this.analyticsManager.trackToolError(e,u,a,"execution_failed","other"),{success:!0,data:{results:O,totalCommands:O.length}}}let f=await this.httpBridge.executeCommand(a,l),m=Date.now()-r,h=this.withResultContextId(c,f.data);if(!f.success){let g=f.error||"Tool execution failed",{errorType:x,errorDetail:w}=ane(g);return await this.historyManager.recordFailure(e,h,g,m,x.toUpperCase(),a),this.analyticsManager.trackToolCall(e,u,!1,a,x,w),this.analyticsManager.trackToolError(e,u,a,x,w),x==="bridge_error"||x==="command_timeout"?y.warn(`Tool ${x}`,{toolName:e,action:a,errorDetail:w,error:g}):x==="plugin_runtime"&&y.warn("Plugin runtime error",{toolName:e,action:a,error:g}),{success:!1,error:g}}return await this.historyManager.recordSuccess(e,h,f.data,m,a),await this.refreshImplicitContextIfNeeded(e,a,h,f.data),this.analyticsManager.trackToolCall(e,u,!0,a),{success:!0,data:f.data}}catch(p){let{errorType:d,errorDetail:f}=one(p),m=Date.now()-r,h=p instanceof Error?p.message:"Unknown error";return y.error(`Tool ${d}`,{toolName:e,action:a,error:h}),await this.historyManager.recordFailure(e,c,h,m,d.toUpperCase(),a),this.analyticsManager.trackToolCall(e,u,!1,a,d,f),this.analyticsManager.trackToolError(e,u,a,d,f),{success:!1,error:h}}}async start(){y.info("Starting Weppy Roblox MCP Server"),await this.licenseStateManager.initialize(),await this.httpBridge.start();let e=this.httpBridge.getSessionId();try{await this.historyManager.initialize(e)}catch(a){y.warn("Tool history manager initialization failed, continuing without history/statistics",a)}this.analyticsManager.initialize(e);let n=this.licenseStateManager.getStatus();this.analyticsManager.setTier(n.canUsePro?"pro":"basic"),this.analyticsManager.trackSessionStart();let r=Wf();r&&(this.httpBridge.setAiClientInfo(r.name,r.version),y.info("AI client detected via environment",{name:r.name})),this.server.oninitialized=()=>{try{let a=this.server.getClientVersion();if(a?.name){let o=KO(a.name),s=a.version||"";this.httpBridge.setAiClientInfo(o,s),y.info("AI client detected via MCP protocol",{raw:a.name,display:o,version:s})}}catch(a){y.debug("Could not detect AI client info from protocol",{error:a})}},this.tryAutoOpenDashboard();let i=new Fp;await this.server.connect(i),y.info("MCP Server started successfully",{transport:"stdio",httpBridge:`${this.config.httpHost}:${this.config.httpPort}`,sessionId:e})}tryAutoOpenDashboard(){let e=process.env.DASHBOARD_AUTO_OPEN;if(e==="false"||e==="0"){y.debug("Dashboard auto-open disabled via DASHBOARD_AUTO_OPEN");return}let n=this.httpBridge.getIsClientMode();if(!n){let i=ine(import.meta.url),a=zD.dirname(i);if(!Ff(a)){y.debug("Dashboard auto-open skipped: dist not found");return}}setTimeout(async()=>{let i=!1;if(n)try{let{fetchStatus:o}=await Promise.resolve().then(()=>(Rf(),jN));i=((await o(`http://127.0.0.1:${this.config.httpPort}`,2e3))?.dashboardSseClients??0)>0}catch{y.debug("Dashboard auto-open skipped: upstream status check failed");return}else i=this.httpBridge.getDashboardSseClientCount()>0;if(i){y.debug("Dashboard auto-open skipped: browser tab already connected");return}let a=`http://127.0.0.1:${this.config.httpPort}/dashboard`;Promise.resolve().then(()=>(RD(),TD)).then(o=>o.default(a)).then(()=>y.info("Dashboard auto-opened",{url:a,isClientMode:n})).catch(o=>y.debug("Dashboard auto-open failed (non-critical)",{error:String(o)}))},4e3)}async stop(){return this.stopPromise?this.stopPromise:(this.stopPromise=this.stopInternal(),this.stopPromise)}async stopInternal(){y.info("Stopping MCP Server"),this.analyticsManager.trackSessionEnd(),await this.analyticsManager.shutdown(),await this.executionContextManager.shutdown(),await this.historyManager.shutdown(),await this.httpBridge.stop(),await this.licenseStateManager.shutdown(),await this.server.close(),y.info("MCP Server stopped")}readContextSummary(e){return e&&typeof e=="object"?e:null}readReplayMetadata(e){return e&&typeof e=="object"?e:null}readInlineContextSummary(e){let r={...this.readContextSummary(e.contextSummary)??{}};for(let i of["intent","affectedAreas","testScenario","expectedBehavior","observedBehavior"])e[i]!==void 0&&(r[i]=e[i]);return Object.keys(r).length>0?r:null}readInlineReplayMetadata(e){return this.readReplayMetadata(e.replayMetadata)}isContextAwareTool(e,n){return n==="run_test"||e==="execute_luau"?!0:cne[e]?.has(n)??!1}isContextCaptureEnabledForCurrentProcess(){let e=this.executionContextManager.isEnabled();return e?this.httpBridge.getIsClientMode()?this.httpBridge.getContext().clientModeUpstreamContextCaptureEnabled?e:(this.clientModeContextIdsByScope.clear(),!1):e:(this.clientModeContextIdsByScope.clear(),!1)}resolveExecutionContextSessionId(e){return typeof e.__sessionId=="string"?e.__sessionId:this.httpBridge.getSessionId()}buildContextScopeKey(e,n){let r=typeof n=="number"&&Number.isFinite(n)?n:this.httpBridge.getSyncController()?.getDefaultRuntimePlaceId()??null;return typeof r!="number"||!Number.isFinite(r)?null:`${e}:${r}`}findClientModeContextId(e,n){let r=this.buildContextScopeKey(e,n);if(r&&this.clientModeContextIdsByScope.has(r))return this.clientModeContextIdsByScope.get(r)??null;let i=[...this.clientModeContextIdsByScope.entries()].filter(([a])=>a.startsWith(`${e}:`)).map(([,a])=>a);return i.length===1?i[0]:null}async attachContextIfNeeded(e,n,r,i){if(!this.isContextAwareTool(e,n)||!this.isContextCaptureEnabledForCurrentProcess())return{args:r,params:i};let a=this.resolveExecutionContextSessionId(i),o=typeof i.placeId=="number"?i.placeId:null,s=this.readInlineContextSummary(i),c=this.readInlineReplayMetadata(i),l=typeof i.contextId=="string"?i.contextId:null,u=this.httpBridge.getIsClientMode()?this.findClientModeContextId(a,o):null,p=u?{contextId:u}:await this.executionContextManager.findActiveContext(a,o);try{if(s||c){let m=typeof p=="object"&&p&&"contextId"in p&&typeof p.contextId=="string"?p.contextId:null,h=typeof p=="object"&&p&&"source"in p&&p.source==="implicit"?"implicit":null,g=l||(h==="implicit"?m:null);if(g)try{await this.executionContextManager.update({sessionId:a,placeId:o,placeName:typeof i.placeName=="string"?i.placeName:null,contextId:g,patch:{...h==="implicit"?{source:"explicit"}:{},...s?{contextSummary:s}:{},...c?{replayMetadata:c}:{}}}),p=await this.executionContextManager.getLatestSnapshot(g)}catch{p=null}if(!p){let x=await this.executionContextManager.begin({sessionId:a,placeId:o,placeName:typeof i.placeName=="string"?i.placeName:null,source:"explicit",...s?{contextSummary:s}:{},...c?{replayMetadata:c}:{}});typeof x.contextId=="string"&&(p=await this.executionContextManager.getLatestSnapshot(x.contextId))}}else l&&(p=await this.executionContextManager.getLatestSnapshot(l)??{contextId:l});if(!p){let m=jD(e,n,i);if(m.length>0){let h=await this.executionContextManager.begin({sessionId:a,placeId:o,placeName:typeof i.placeName=="string"?i.placeName:null,source:"implicit",contextSummary:{affectedAreas:m,...n==="run_test"&&typeof r.test_name=="string"&&r.test_name.trim()?{testScenario:r.test_name.trim()}:{}}});typeof h.contextId=="string"&&(p={contextId:h.contextId})}}}catch(m){y.debug("context capture skipped for tool execution",{toolName:e,action:n,error:m instanceof Error?m.message:String(m)}),p=null}if(!p)return{args:r,params:i};let d=n==="run_test"?{...r.contextSummary===void 0&&typeof p=="object"&&"contextSummary"in p?{contextSummary:p.contextSummary}:{},...r.replayMetadata===void 0&&typeof p=="object"&&"replayMetadata"in p?{replayMetadata:p.replayMetadata}:{}}:{},f={...r.contextSummary===void 0&&s?{contextSummary:s}:{},...r.replayMetadata===void 0&&c?{replayMetadata:c}:{}};return{args:{...r,contextId:p.contextId,...f,...d},params:{...i,contextId:p.contextId,...i.contextSummary===void 0&&s?{contextSummary:s}:{},...i.replayMetadata===void 0&&c?{replayMetadata:c}:{},...n==="run_test"&&i.contextSummary===void 0&&"contextSummary"in d?{contextSummary:d.contextSummary}:{},...n==="run_test"&&i.replayMetadata===void 0&&"replayMetadata"in d?{replayMetadata:d.replayMetadata}:{}}}}async refreshImplicitContextIfNeeded(e,n,r,i){if(!this.isContextCaptureEnabledForCurrentProcess())return;let a=typeof r.contextId=="string"?r.contextId:null;if(!a)return;let o=await this.executionContextManager.getLatestSnapshot(a);if(!o||o.source!=="implicit")return;let s=jD(e,n,r,i);if(s.length===0)return;let c=sne(o.contextSummary.affectedAreas,s);c.length===o.contextSummary.affectedAreas.length&&c.every((u,p)=>{let d=o.contextSummary.affectedAreas[p];return d?.target===u.target&&d?.label===u.label&&d?.kind===u.kind})||await this.executionContextManager.update({sessionId:this.resolveExecutionContextSessionId(r),placeId:typeof r.placeId=="number"?r.placeId:null,placeName:typeof r.placeName=="string"?r.placeName:null,contextId:a,patch:{contextSummary:{affectedAreas:c}}})}async enrichRunTestParams(e,n){if(!this.isContextCaptureEnabledForCurrentProcess())return e;let r=typeof e.contextId=="string"?e.contextId:null,i=typeof e.placeId=="number"?e.placeId:null,a=r?await this.executionContextManager.getLatestSnapshot(r):await this.executionContextManager.findActiveContext(n,i);return a?{...e,...r?{}:{contextId:a.contextId},...e.contextSummary===void 0?{contextSummary:a.contextSummary}:{},...e.replayMetadata===void 0?{replayMetadata:a.replayMetadata}:{}}:e}withResultContextId(e,n){if(typeof e.contextId=="string"||!n||typeof n!="object")return e;let r=n.contextId;return typeof r=="string"?{...e,contextId:r}:e}},AD=YS;pe();pe();import{createHash as lne}from"node:crypto";import{copyFileSync as ND,existsSync as am,mkdirSync as une,readFileSync as pne}from"node:fs";import{homedir as dne,platform as fne}from"node:os";import{dirname as mne,join as Rl,resolve as OD}from"node:path";import{fileURLToPath as hne}from"node:url";var gne=hne(import.meta.url),DD=mne(gne),QS="WeppyRobloxMCP.rbxm";function yne(){let t=fne(),e=dne();switch(t){case"win32":return Rl(process.env.LOCALAPPDATA||Rl(e,"AppData","Local"),"Roblox","Plugins");case"darwin":return Rl(e,"Documents","Roblox","Plugins");default:return Rl(e,"Documents","Roblox","Plugins")}}function vne(){let t=OD(DD,"..","roblox-plugin",QS);return am(t)?t:OD(DD,"..","..","roblox-plugin",QS)}function MD(t){let e=pne(t);return lne("md5").update(e).digest("hex")}async function LD(){if(process.env.SKIP_PLUGIN_INSTALL==="true"||process.env.SKIP_PLUGIN_INSTALL==="1")return y.debug("Plugin \uC790\uB3D9\uC124\uCE58 \uC2A4\uD0B5 (SKIP_PLUGIN_INSTALL=true)"),null;let t=vne();if(!am(t))return y.debug("\uBC88\uB4E4\uB41C Plugin \uD30C\uC77C \uC5C6\uC74C, \uC790\uB3D9\uC124\uCE58 \uC2A4\uD0B5",{path:t}),null;let e=yne(),n=Rl(e,QS);if(am(n)){let r=MD(t),i=MD(n);return r===i?(y.debug("Roblox Plugin \uC774\uBBF8 \uCD5C\uC2E0 \uC0C1\uD0DC"),null):(ND(t,n),y.info(`Roblox Plugin \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC \u2192 ${n}`),"updated")}return am(e)||une(e,{recursive:!0}),ND(t,n),y.info(`Roblox Plugin \uC124\uCE58 \uC644\uB8CC \u2192 ${n}`),"installed"}var xne=new Set(["--version","-v"]);function UD(t,e,n=console.log){return t.slice(2).some(i=>xne.has(i))?(n(e),!0):!1}yr();gw();function e0(t,e){return t===void 0||t===""?e:t.toLowerCase()==="true"||t==="1"}function FD(t={}){let e=t.env??process.env,n=t.dataDir??gr(),r=t.appDataDir??hl(),i=Uf(r),a=o=>e[o]!==void 0&&e[o]!=="";return{httpPort:parseInt(e.HTTP_PORT||"3002",10),httpHost:e.HTTP_HOST||"127.0.0.1",logLevel:a("LOG_LEVEL")?e.LOG_LEVEL:i.LOG_LEVEL||"info",requestTimeout:a("REQUEST_TIMEOUT")?parseInt(e.REQUEST_TIMEOUT,10):i.REQUEST_TIMEOUT,enableLocalHistory:a("ENABLE_LOCAL_HISTORY")?e0(e.ENABLE_LOCAL_HISTORY,!0):i.ENABLE_LOCAL_HISTORY,enableLocalStatistics:a("ENABLE_LOCAL_STATISTICS")?e0(e.ENABLE_LOCAL_STATISTICS,!0):i.ENABLE_LOCAL_STATISTICS,enableContextCapture:i.ENABLE_CONTEXT_CAPTURE,dataDir:n,appDataDir:r,enableTelemetry:a("ENABLE_TELEMETRY")?e0(e.ENABLE_TELEMETRY,!1):void 0,licenseApiBaseUrl:e.LICENSE_API_BASE_URL||"https://roblox-license-api.hope841026.workers.dev",licenseProjectId:e.LICENSE_PROJECT_ID||"roblox-mcp",licenseProvider:a("LICENSE_PROVIDER")?e.LICENSE_PROVIDER:i.LICENSE_PROVIDER,licenseRequestTimeoutMs:parseInt(e.LICENSE_REQUEST_TIMEOUT_MS||"8000",10),licenseRetryCount:parseInt(e.LICENSE_RETRY_COUNT||"1",10),licenseRetryDelayMs:parseInt(e.LICENSE_RETRY_DELAY_MS||"300",10),licenseFallbackMaxHours:parseInt(e.LICENSE_FALLBACK_MAX_HOURS||"48",10)}}function qD(t){return t instanceof Error?t:new Error(String(t))}function BD(t){let e=t.timeoutMs??5e3,n=null,r=!1,i=a=>{r||(r=!0,t.exit(a))};return{shutdown(a){return n?(t.logger.warn("Shutdown already in progress, ignoring duplicate request",{reason:a.reason,exitCode:a.exitCode}),n):(n=(async()=>{let o=setTimeout(()=>{t.logger.error("Shutdown timed out, forcing process exit",{reason:a.reason,timeoutMs:e}),i(a.exitCode===0?1:a.exitCode)},e);o.unref?.(),a.error?t.logger.error(`Shutdown requested by ${a.reason}`,a.error):t.logger.info("Shutdown requested",{reason:a.reason,exitCode:a.exitCode});try{await t.stop(),clearTimeout(o),i(a.exitCode)}catch(s){clearTimeout(o),t.logger.error("Error during shutdown cleanup",s),i(a.exitCode===0?1:a.exitCode)}})(),n)},isShuttingDown(){return n!==null}}}function ZD(t){let e=[],n=(o,s,c)=>{o.on(s,c),e.push({target:o,event:s,handler:c})};n(t.stdin,"end",()=>{t.shutdown({reason:"stdin-end",exitCode:0})}),n(t.stdin,"close",()=>{t.shutdown({reason:"stdin-close",exitCode:0})}),n(t.process,"disconnect",()=>{t.shutdown({reason:"disconnect",exitCode:0})});for(let o of["SIGINT","SIGTERM","SIGHUP"])n(t.process,o,()=>{t.shutdown({reason:o,exitCode:0})});n(t.process,"uncaughtException",o=>{t.shutdown({reason:"uncaughtException",exitCode:1,error:qD(o)})}),n(t.process,"unhandledRejection",o=>{t.shutdown({reason:"unhandledRejection",exitCode:1,error:qD(o)})});let r=typeof t.process.ppid=="number"?t.process.ppid:null,i=t.ownerWatchdogIntervalMs??3e3,a=r&&r>1?setInterval(()=>{let o=typeof t.process.ppid=="number"?t.process.ppid:null;o!==null&&(o<=1||o!==r)&&(t.logger.warn("Parent process disappeared, starting graceful shutdown",{initialParentPid:r,currentParentPid:o}),t.shutdown({reason:"parent-exit",exitCode:0}))},i):null;return a?.unref?.(),()=>{for(let{target:o,event:s,handler:c}of e)o.off(s,c);a&&clearInterval(a)}}async function bne(){let t=null,e=null;try{let n=FD();y.info("Weppy Roblox MCP Server",{version:We,config:{httpHost:n.httpHost,httpPort:n.httpPort,logLevel:n.logLevel,requestTimeout:n.requestTimeout,enableLocalHistory:n.enableLocalHistory,enableLocalStatistics:n.enableLocalStatistics,enableContextCapture:n.enableContextCapture,enableTelemetry:n.enableTelemetry,dataDir:n.dataDir,appDataDir:n.appDataDir,licenseApiBaseUrl:n.licenseApiBaseUrl,licenseProjectId:n.licenseProjectId,licenseProvider:n.licenseProvider,licenseRequestTimeoutMs:n.licenseRequestTimeoutMs,licenseRetryCount:n.licenseRetryCount,licenseRetryDelayMs:n.licenseRetryDelayMs,licenseFallbackMaxHours:n.licenseFallbackMaxHours}});try{await LD()}catch(a){y.warn("Plugin \uC790\uB3D9\uC124\uCE58 \uC2E4\uD328 (\uC11C\uBC84 \uC2DC\uC791\uC5D0 \uC601\uD5A5 \uC5C6\uC74C)",a)}let r=new AD(n);e=BD({stop:async()=>{t?.(),await r.stop()},exit:a=>{process.exit(a)},logger:y}).shutdown,t=ZD({process,stdin:process.stdin,shutdown:e,logger:y}),await r.start()}catch(n){if(e){await e({reason:"startup-failure",exitCode:1,error:n});return}y.error("Failed to start server",n),process.exit(1)}}UD(process.argv,We)||bne();
152
+ `)}function xte(t){if(typeof t.message!="string"||!t.message.startsWith(jS))return null;let e=t.message.slice(jS.length),n=e.indexOf(":");if(n===-1)return null;let r=e.slice(0,n),i=e.slice(n+1),a={...typeof t.seq=="number"?{seq:t.seq}:{},...typeof t.timestampMs=="number"?{timestampMs:t.timestampMs}:{}},o;try{o=JSON.parse(i)}catch{o={raw:i}}return{kind:r,payload:o,message:t.message,...a}}function bte(t){return!!t&&typeof t=="object"}var tm=class{httpBridge;reportWriter;constructor(e,n){if(this.httpBridge=e,n)this.reportWriter=n;else{let r=async(i,a)=>{let o=e.getSyncController();if(!o)throw new Error("SyncController not available");return o.config.resolvePlaceRoot(i,a)};this.reportWriter=new em(void 0,r)}}async runTest(e){let n=dte(e),r=Date.now(),i={placeId:typeof e.placeId=="number"?e.placeId:0,name:"Unknown"},a=!1,o=0,s=!1,c=!1,l=!1,u="",p=null,d=[],f=[];try{i=await this.getPlaceInfo(e),a=!0,await this.ensurePlaytestIdle(i.placeId);let k=await this.executePlugin("clear_output_logs",this.withPlace({},i.placeId));o=typeof k.lastSeq=="number"?k.lastSeq:0,await this.executePlugin("create_script",this.withPlace({scriptType:"Script",parent:"game.ServerScriptService",name:nD,source:vte(n)},i.placeId)),s=!0,await this.executePlugin("start_playtest",this.withPlace({mode:n.mode},i.placeId)),c=!0;let I=Date.now()+n.timeoutSeconds*1e3;for(;Date.now()<I;){let O=await this.pollLogs(i.placeId,o);if(d.push(...O.logs),f.push(...O.signals),o=O.nextSeq,O.finishedPayload){p=O.finishedPayload;break}await RS(ote)}p||(l=!0,u=`Timed out after ${n.timeoutSeconds} seconds`)}catch(k){u=k instanceof Error?k.message:String(k),y.warn("run_test orchestration failed",{testName:n.testName,error:u})}finally{if(c&&a&&await this.stopIfNeeded(i.placeId),s&&a&&await this.deleteRunnerScript(i.placeId),a){let k=await this.pollLogs(i.placeId,o);d.push(...k.logs),f.push(...k.signals),o=k.nextSeq,!p&&k.finishedPayload&&(p=k.finishedPayload)}}let m=typeof p?.errorMessage=="string"?p.errorMessage:"";!u&&m&&(u=m);let h=gte(d);!u&&h&&(u=h);let g=typeof p?.durationMs=="number"?p.durationMs:Math.max(0,Date.now()-r),x=!!p?.passed&&!l&&!u,w=hte(e,n);return{...await this.reportWriter.write({placeId:i.placeId,placeName:i.name,testName:n.testName,mode:n.mode,passed:x,timedOut:l,durationMs:g,errorMessage:u,logs:d,signals:f,...typeof e.contextId=="string"?{contextId:e.contextId}:{},...w?{contextSummary:w}:{},replayMetadata:tD(e.replayMetadata,n)}),passed:x,timedOut:l,testName:n.testName,mode:n.mode,placeId:i.placeId,placeName:i.name,durationMs:g,signalCount:f.length,...typeof e.contextId=="string"?{contextId:e.contextId}:{},...w?{contextSummary:w}:{},replayMetadata:tD(e.replayMetadata,n)}}async ensurePlaytestIdle(e){let n=await this.executePlugin("get_play_status",this.withPlace({},e)),r=typeof n.state=="string"?n.state:"unknown";if(r!=="edit")throw new Error(`Playtest already running (current state: ${r})`)}withPlace(e,n){return{...e,placeId:n}}async getPlaceInfo(e){let n=await this.executePlugin("get_place_info",e.placeId!==void 0?{placeId:e.placeId}:{}),r=typeof n.placeId=="number"?n.placeId:0,i=typeof n.name=="string"&&n.name.trim()?n.name:"Unknown";return{placeId:r,name:i}}async stopIfNeeded(e){try{if((await this.executePlugin("get_play_status",this.withPlace({},e))).state==="edit")return;await this.executePlugin("stop_playtest",this.withPlace({},e));for(let r=0;r<lte;r+=1)if(await RS(cte),(await this.executePlugin("get_play_status",this.withPlace({},e))).state==="edit")return}catch(n){y.warn("run_test cleanup could not stop playtest",{placeId:e,error:n instanceof Error?n.message:String(n)})}}async deleteRunnerScript(e){try{await this.executePlugin("delete_script",this.withPlace({path:rte},e))}catch(n){y.warn("run_test cleanup could not delete runner script",{placeId:e,error:n instanceof Error?n.message:String(n)})}}async pollLogs(e,n){let r=[],i=[],a=n,o=null;for(;;){let s=await this.executeLogPoll(e,a),c=Array.isArray(s.logs)?s.logs:[];for(let l of c){r.push(l),typeof l.seq=="number"&&(a=l.seq);let u=xte(l);u&&(i.push(u),u.kind===pte&&bte(u.payload)&&(o=u.payload))}if(c.length===0&&(s.cursorStatus==="reset"||s.cursorStatus==="gap")&&typeof s.lastSeq=="number"&&(a=s.lastSeq),!s.hasMore)break}return{logs:r,signals:i,nextSeq:a,finishedPayload:o}}async executeLogPoll(e,n){let r=this.withPlace({sinceSeq:n,limit:500},e);try{return await this.executePlugin("get_output_logs",r)}catch(i){y.debug("run_test pollLogs retrying after failure",{placeId:e,sinceSeq:n,error:i instanceof Error?i.message:String(i)}),await RS(ute);try{return await this.executePlugin("get_output_logs",r)}catch(a){return y.debug("run_test pollLogs retry failed, returning empty batch",{placeId:e,sinceSeq:n,error:a instanceof Error?a.message:String(a)}),{logs:[],hasMore:!1}}}}async executePlugin(e,n,r={timeout:ste}){let i=await this.httpBridge.executeCommand(e,n,r);if(!i.success)throw new Error(i.error||`${e} failed`);return fte(i.data)}};function ane(t){let e=t.toLowerCase();return/upstream_server_down/.test(e)?{errorType:"bridge_error",errorDetail:"upstream_down"}:/^http [45]\d\d/.test(e)?{errorType:"bridge_error",errorDetail:"http_error"}:/timeout/.test(e)?{errorType:"command_timeout",errorDetail:"timeout"}:/econnrefused|socket hang up|econnreset|epipe|network/.test(e)?{errorType:"bridge_error",errorDetail:"connection"}:/abort/.test(e)?{errorType:"command_timeout",errorDetail:"aborted"}:/runtime error|traceback|attempt to/.test(e)?{errorType:"plugin_runtime",errorDetail:"runtime_error"}:/instance not found|not found in/.test(e)?{errorType:"execution_failed",errorDetail:"not_found"}:/parent not found/.test(e)?{errorType:"execution_failed",errorDetail:"parent_not_found"}:/is required|missing required/.test(e)?{errorType:"execution_failed",errorDetail:"missing_param"}:/invalid classname|invalid class/.test(e)?{errorType:"execution_failed",errorDetail:"invalid_class"}:/unknown action/.test(e)?{errorType:"execution_failed",errorDetail:"unknown_action"}:/failed to insert asset/.test(e)?{errorType:"execution_failed",errorDetail:"asset_error"}:/failed to read property|property access denied/.test(e)?{errorType:"execution_failed",errorDetail:"property_error"}:/must be|invalid|cannot/.test(e)?{errorType:"execution_failed",errorDetail:"validation"}:{errorType:"execution_failed",errorDetail:"other"}}function one(t){let n=(t instanceof Error?t.message:String(t)).toLowerCase();return/timeout/.test(n)?{errorType:"command_timeout",errorDetail:"timeout"}:/econnrefused|socket hang up|network|econnreset|epipe/.test(n)?{errorType:"bridge_error",errorDetail:"connection"}:/eaddrinuse/.test(n)?{errorType:"bridge_error",errorDetail:"port_in_use"}:{errorType:"exception",errorDetail:"unexpected"}}function na(t){let e=t.split(".");return e[e.length-1]||t}function yt(t){let e=t.trim();return e.startsWith("game.")?e:e==="Workspace"||e.startsWith("Workspace.")?`game.${e}`:e}function jD(t,e,n,r){let i=r&&typeof r=="object"&&!Array.isArray(r)?r:null,a=i&&typeof i.path=="string"?i.path:null,o=typeof n.path=="string"?n.path:null,s=typeof n.parent=="string"?n.parent:null,c=typeof n.name=="string"?n.name:null,l=typeof n.property=="string"?n.property:null,u=typeof n.test_name=="string"?n.test_name.trim():typeof n.testName=="string"?n.testName.trim():"";if(t==="manage_properties"&&o&&l)return[{kind:"property",target:yt(`${o}.${l}`),label:`${na(o)} ${l}`}];if(t==="manage_scripts"&&a)return[{kind:"script",target:yt(a),label:na(a)}];if(t==="manage_scripts"&&s&&c){let p=`${s}.${c}`;return[{kind:"script",target:yt(p),label:c}]}if(t==="manage_scripts"&&o)return[{kind:"script",target:yt(o),label:na(o)}];if(t==="mutate_instances"){if(a)return[{kind:"instance",target:yt(a),label:na(a)}];if(s&&c){let d=`${s}.${c}`;return[{kind:"instance",target:yt(d),label:c}]}let p=typeof n.sourcePath=="string"?n.sourcePath:s||o;if(p)return[{kind:"instance",target:yt(p),label:na(p)}]}if(t==="manage_terrain")return[{kind:"terrain",target:yt("game.Workspace.Terrain"),label:"Terrain"}];if(t==="manage_lighting")return[{kind:"lighting",target:yt("game.Lighting"),label:"Lighting"}];if(t==="manage_assets"){let p=a||(s&&c?`${s}.${c}`:s||"game.Workspace");return[{kind:"asset",target:yt(p),label:na(p)}]}return e==="run_test"&&u?[{kind:"gameplay",target:yt(u),label:u}]:o?[{kind:"other",target:yt(o),label:na(o)}]:[]}function sne(t,e){let n=new Map;for(let r of t)n.set(yt(r.target),{...r,target:yt(r.target)});for(let r of e)n.set(yt(r.target),{...r,target:yt(r.target)});return[...n.values()]}var cne={manage_scripts:new Set(["set_script_source","create_script","delete_script","edit_script_lines","replace_in_scripts","insert_script_lines","delete_script_lines"]),mutate_instances:new Set(["create_instance","create_instance_with_properties","clone_instance","delete_instance","move_instance","rename_instance","pivot_to","create_instance_tree","mass_create_instances","mass_delete_instances","mass_duplicate","smart_duplicate"]),manage_properties:new Set(["set_property","set_multiple_properties","set_attribute","delete_attribute","add_tag","remove_tag","set_calculated_property","set_relative_property","mass_set_property","modify_children"]),manage_assets:new Set(["insert_model","insert_free_model","insert_package","search_and_insert_model"]),manage_lighting:new Set(["set_lighting","set_atmosphere","set_sky","set_terrain","set_time_of_day"]),manage_audio:new Set(["play_sound","stop_sound","pause_sound","resume_sound","set_listener"]),manage_animation:new Set(["load_animation","play_animation","stop_animation"]),manage_physics:new Set(["register_collision_group","set_collidable"]),manage_effects:new Set(["emit_particles","clear_particles","toggle_effect"]),manage_tween:new Set(["create_tween","play_tween","pause_tween","cancel_tween"]),manage_terrain:new Set(["terrain_fill_block","terrain_fill_ball","terrain_fill_cylinder","terrain_fill_wedge","terrain_clear","terrain_clear_region","terrain_replace_material","terrain_set_material_color","terrain_write_voxels","terrain_generate","terrain_smooth"]),manage_sync:new Set(["sync_write_file"])},YS=class{server;httpBridge;config;historyManager;executionContextManager;clientModeContextIdsByScope=new Map;analyticsManager;licenseStateManager;testRunner;stopPromise=null;constructor(e){this.config=e,this.server=new Lp({name:"weppy-roblox-mcp",version:We},{capabilities:{tools:{}}}),this.httpBridge=new MO(e),this.testRunner=new tm(this.httpBridge),this.historyManager=new Zf({enableLocalHistory:e.enableLocalHistory??!1,enableLocalStatistics:e.enableLocalStatistics??!1,dataDir:e.dataDir??gr()}),this.historyManager.setPlaceResolver(i=>{let a=i.placeId,o=typeof a=="number"?a:typeof a=="string"&&a.trim().length>0?Number.parseInt(a,10):null;if(o!==null&&Number.isFinite(o))return{placeId:o,placeName:typeof i.placeName=="string"?i.placeName:null};let s=this.httpBridge.getSyncController()?.getDefaultRuntimePlaceId();return typeof s=="number"&&Number.isFinite(s)?{placeId:s}:{}}),this.executionContextManager=JO({enabled:e.enableContextCapture??!0,dataDir:e.dataDir??gr(),runtimePlaceIdResolver:()=>this.httpBridge.getSyncController()?.getDefaultRuntimePlaceId()??null,runtimePlaceNameResolver:i=>Ut(this.httpBridge.getBridgeContext(),i)}),this.analyticsManager=new Gf({enabled:e.enableTelemetry});let n=new Kf({baseUrl:e.licenseApiBaseUrl||"https://roblox-license-api.hope841026.workers.dev",projectId:e.licenseProjectId||"roblox-mcp",provider:e.licenseProvider||"gumroad",timeoutMs:e.licenseRequestTimeoutMs??8e3,retryCount:e.licenseRetryCount??1,retryDelayMs:e.licenseRetryDelayMs??300}),r=new Yf;this.licenseStateManager=new Qf(n,r,this.httpBridge.getInstanceId(),{fallbackMaxHours:e.licenseFallbackMaxHours??48}),this.httpBridge.setHistoryManager(this.historyManager),this.httpBridge.setExecutionContextManager(this.executionContextManager),this.httpBridge.setAnalyticsManager(this.analyticsManager),this.httpBridge.setLicenseStateManager(this.licenseStateManager),this.httpBridge.setInternalActionExecutor(async(i,a)=>{let o=await this.executeInternalAction(i,a,this.httpBridge.getSyncController()),s={success:o.success};return o.data!==void 0&&(s.data=o.data),o.error&&(s.error=o.error),s}),this.setupHandlers(),y.info("MCP Server initialized",{name:"weppy-roblox-mcp",version:We,tools:qp.length,localHistoryEnabled:e.enableLocalHistory??!1,localStatisticsEnabled:e.enableLocalStatistics??!1,telemetryEnabled:e.enableTelemetry??!1,licenseApiConfigured:this.licenseStateManager.isConfigured()})}setupHandlers(){this.server.setRequestHandler(Jy,async()=>(y.debug("Received tools/list request"),{tools:qp})),this.server.setRequestHandler(Ys,async e=>{let{name:n,arguments:r}=e.params;y.info("Received tool call",{tool:n,args:r});try{let i=await this.executeTool(n,r||{});return i.success?{content:[{type:"text",text:i.data!==void 0?JSON.stringify(i.data,null,2):JSON.stringify({success:!0,message:"Command executed"})}]}:{content:[{type:"text",text:JSON.stringify({success:!1,error:i.error},null,2)}],isError:!0}}catch(i){return y.error("Tool execution failed",i),{content:[{type:"text",text:`Error: ${i instanceof Error?i.message:"Unknown error"}`}],isError:!0}}})}async executeInternalAction(e,n,r){let i=this.resolveExecutionContextSessionId(n);if(e==="get_cached_selection"){let s=n.maxAge,c=this.httpBridge.getCachedSelection(s!==void 0?s:3e4);if(!c)return{success:!0,data:{cached:!1,message:"No cached selection data available. The plugin may not be connected or no selection changes have occurred yet."}};let l=Date.now()-c.timestamp;return{success:!0,data:{cached:!0,selection:c.selection,count:c.count,timestamp:c.timestamp,age:l}}}if(e==="get_connection_info")return{success:!0,data:await this.httpBridge.getConnectionInfo()};if(e==="run_test"){if(this.httpBridge.getIsClientMode()){let s=Object.prototype.hasOwnProperty.call(n,"timeout")?typeof n.timeout=="number"?n.timeout:typeof n.timeout=="string"?Number(n.timeout):Number.NaN:null,c=Number.isFinite(s)&&s!==null&&s>0?await this.httpBridge.executeCommand(e,n,{timeout:s*1e3+1e4}):await this.httpBridge.executeCommand(e,n),l={success:c.success};return c.data!==void 0&&(l.data=c.data),c.error&&(l.error=c.error),c.success||(l.failureCode="PROXY_FAILED"),l}try{let s=await this.enrichRunTestParams(n,i),c=await this.testRunner.runTest(s);if(typeof s.contextId=="string"&&typeof c.timestamp=="string")try{await this.executionContextManager.appendLinks({sessionId:i,placeId:typeof s.placeId=="number"?s.placeId:null,contextId:s.contextId,links:{playtestTimestamps:[c.timestamp]}})}catch(l){y.warn("run_test context link bookkeeping failed",{contextId:s.contextId,placeId:typeof s.placeId=="number"?s.placeId:null,error:l instanceof Error?l.message:String(l)})}return{success:!0,data:c}}catch(s){return{success:!1,error:s instanceof Error?s.message:String(s),failureCode:"INTERNAL_FAILED"}}}if(!e.startsWith("sync_"))return{success:!1,error:`Unknown internal action: ${e}`,failureCode:"INTERNAL_FAILED"};if(!r&&this.httpBridge.getIsClientMode()){let s=await this.httpBridge.executeCommand(e,n),c={success:s.success};return s.data!==void 0&&(c.data=s.data),s.error&&(c.error=s.error),s.success||(c.failureCode="PROXY_FAILED"),c}let a=await FO(e,n,r),o={success:a.success};return a.data!==void 0&&(o.data=a.data),a.error&&(o.error=a.error),a.success||(o.failureCode="INTERNAL_FAILED"),o}async executePluginBatch(e,n){let r={commands:e.map(({action:a,params:o})=>({action:a,params:o})),stopOnError:n},i=await this.httpBridge.executeCommand("batch_execute",r);if(i.success&&Array.isArray(i.data?.results)){let a=i.data.results;return e.map((o,s)=>{let c=a[s];if(!c)return{index:o.index,action:o.action,success:!1,error:"Batch result missing"};let l={index:o.index,action:o.action,success:c.success};return c.data!==void 0&&(l.data=c.data),c.error&&(l.error=c.error),l})}return e.map(a=>({index:a.index,action:a.action,success:!1,error:i.error||"Batch execution failed"}))}async executeTool(e,n){let r=Date.now();if(this.httpBridge.noteLocalCommandActivity(r),!qp.find(p=>p.name===e)){let p=`Unknown tool: ${e}`,d=Date.now()-r;return await this.historyManager.recordFailure(e,n,p,d,"UNKNOWN_TOOL"),this.analyticsManager.trackToolCall(e,"basic",!1,void 0,"execution_failed","unknown_tool"),this.analyticsManager.trackToolError(e,"basic",void 0,"execution_failed","unknown_tool"),{success:!1,error:p}}let{action:a,params:o}=vS(e,n),s=await this.attachContextIfNeeded(e,a,n,o),c=s.args,l=s.params,u=Bo(a);if(u==="pro"){let p=await this.licenseStateManager.evaluateProAccess();if(!p.allowed){if(p.state.reason==="device_temporarily_blocked"){let d=Date.now()-r,f=p.reason||"This device is temporarily blocked";return await this.historyManager.recordFailure(e,c,f,d,"DEVICE_BLOCKED",a),this.analyticsManager.trackToolCall(e,u,!1,a,"device_blocked"),this.analyticsManager.trackToolError(e,u,a,"device_blocked"),{success:!1,error:`${f} (status=${p.state.status})`}}this.analyticsManager.trackToolCall(e,u,!1,a,"delegated_to_plugin"),y.debug("Pro action delegated to plugin for fallback",{toolName:e,action:a})}}y.debug("Executing tool",{toolName:e,action:a,args:l,tier:u});try{let p=this.httpBridge.getSyncController(),d=xS(e,a,p);if(d.target==="internal"){let g=await this.executeInternalAction(a,l,p),x=Date.now()-r,w=this.withResultContextId(c,g.data);return g.success?(await this.historyManager.recordSuccess(e,w,g.data,x,a),await this.refreshImplicitContextIfNeeded(e,a,w,g.data),this.analyticsManager.trackToolCall(e,u,!0,a),{success:!0,data:g.data}):(await this.historyManager.recordFailure(e,w,g.error||"Internal operation failed",x,g.failureCode||"INTERNAL_FAILED",a),this.analyticsManager.trackToolCall(e,u,!1,a,"execution_failed","other"),this.analyticsManager.trackToolError(e,u,a,"execution_failed","other"),{success:!1,error:g.error||"Internal operation failed"})}if(d.target==="disk"){let g=await bS(a,l,p),x=Date.now()-r;if(!g.success)y.warn("Disk execution failed, falling back to plugin",{action:a,error:g.error});else return await this.historyManager.recordSuccess(e,c,g.data,x,a),await this.refreshImplicitContextIfNeeded(e,a,c,g.data),this.analyticsManager.trackToolCall(e,u,!0,a),{success:!0,data:g.data}}if(a==="batch_execute"&&Array.isArray(l.commands)){let g=l.commands,x=l.stopOnError!==!1,w=[],_=[],k=async()=>{if(_.length===0)return!1;let ye=await this.executePluginBatch(_,x);_=[];let ie=!1;for(let De of ye)w[De.index]={action:De.action,success:De.success,data:De.data,error:De.error},!De.success&&x&&(ie=!0);return ie},I=!1;for(let ye=0;ye<g.length;ye++){let ie=g[ye],De=ie.params||{},Ge=(ie.toolName?await this.attachContextIfNeeded(ie.toolName,ie.action,De,De):{args:De,params:De}).params,Ft=xS(ie.toolName||"",ie.action,p).target;if(Ft==="plugin"){_.push({index:ye,action:ie.action,params:Ge,toolName:ie.toolName});continue}if(await k()){I=!0;break}let nt;if(Ft==="internal"){let qt=await this.executeInternalAction(ie.action,Ge,p);nt={success:qt.success,data:qt.data,error:qt.error}}else{let qt=await bS(ie.action,Ge,p);if(qt.success)nt={success:!0,data:qt.data};else{y.warn("Disk execution failed inside batch, falling back to plugin",{action:ie.action,error:qt.error});let ti=await this.httpBridge.executeCommand(ie.action,Ge);nt={success:ti.success,data:ti.data,error:ti.error}}}if(w[ye]={action:ie.action,success:nt.success,data:nt.data,error:nt.error},!nt.success&&x){I=!0;break}}I||(I=await k());let O=w.filter(Boolean),H=Date.now()-r,Be=O.every(ye=>ye.success);return await this.historyManager.recordSuccess(e,c,{results:O},H,a),await this.refreshImplicitContextIfNeeded(e,a,c,{results:O}),this.analyticsManager.trackToolCall(e,u,Be,a),Be||this.analyticsManager.trackToolError(e,u,a,"execution_failed","other"),{success:!0,data:{results:O,totalCommands:O.length}}}let f=await this.httpBridge.executeCommand(a,l),m=Date.now()-r,h=this.withResultContextId(c,f.data);if(!f.success){let g=f.error||"Tool execution failed",{errorType:x,errorDetail:w}=ane(g);return await this.historyManager.recordFailure(e,h,g,m,x.toUpperCase(),a),this.analyticsManager.trackToolCall(e,u,!1,a,x,w),this.analyticsManager.trackToolError(e,u,a,x,w),x==="bridge_error"||x==="command_timeout"?y.warn(`Tool ${x}`,{toolName:e,action:a,errorDetail:w,error:g}):x==="plugin_runtime"&&y.warn("Plugin runtime error",{toolName:e,action:a,error:g}),{success:!1,error:g}}return await this.historyManager.recordSuccess(e,h,f.data,m,a),await this.refreshImplicitContextIfNeeded(e,a,h,f.data),this.analyticsManager.trackToolCall(e,u,!0,a),{success:!0,data:f.data}}catch(p){let{errorType:d,errorDetail:f}=one(p),m=Date.now()-r,h=p instanceof Error?p.message:"Unknown error";return y.error(`Tool ${d}`,{toolName:e,action:a,error:h}),await this.historyManager.recordFailure(e,c,h,m,d.toUpperCase(),a),this.analyticsManager.trackToolCall(e,u,!1,a,d,f),this.analyticsManager.trackToolError(e,u,a,d,f),{success:!1,error:h}}}async start(){y.info("Starting WROX Server"),await this.licenseStateManager.initialize(),await this.httpBridge.start();let e=this.httpBridge.getSessionId();try{await this.historyManager.initialize(e)}catch(a){y.warn("Tool history manager initialization failed, continuing without history/statistics",a)}this.analyticsManager.initialize(e);let n=this.licenseStateManager.getStatus();this.analyticsManager.setTier(n.canUsePro?"pro":"basic"),this.analyticsManager.trackSessionStart();let r=Wf();r&&(this.httpBridge.setAiClientInfo(r.name,r.version),y.info("AI client detected via environment",{name:r.name})),this.server.oninitialized=()=>{try{let a=this.server.getClientVersion();if(a?.name){let o=KO(a.name),s=a.version||"";this.httpBridge.setAiClientInfo(o,s),y.info("AI client detected via MCP protocol",{raw:a.name,display:o,version:s})}}catch(a){y.debug("Could not detect AI client info from protocol",{error:a})}},this.tryAutoOpenDashboard();let i=new Fp;await this.server.connect(i),y.info("MCP Server started successfully",{transport:"stdio",httpBridge:`${this.config.httpHost}:${this.config.httpPort}`,sessionId:e})}tryAutoOpenDashboard(){let e=process.env.DASHBOARD_AUTO_OPEN;if(e==="false"||e==="0"){y.debug("Dashboard auto-open disabled via DASHBOARD_AUTO_OPEN");return}let n=this.httpBridge.getIsClientMode();if(!n){let i=ine(import.meta.url),a=zD.dirname(i);if(!Ff(a)){y.debug("Dashboard auto-open skipped: dist not found");return}}setTimeout(async()=>{let i=!1;if(n)try{let{fetchStatus:o}=await Promise.resolve().then(()=>(Rf(),jN));i=((await o(`http://127.0.0.1:${this.config.httpPort}`,2e3))?.dashboardSseClients??0)>0}catch{y.debug("Dashboard auto-open skipped: upstream status check failed");return}else i=this.httpBridge.getDashboardSseClientCount()>0;if(i){y.debug("Dashboard auto-open skipped: browser tab already connected");return}let a=`http://127.0.0.1:${this.config.httpPort}/dashboard`;Promise.resolve().then(()=>(RD(),TD)).then(o=>o.default(a)).then(()=>y.info("Dashboard auto-opened",{url:a,isClientMode:n})).catch(o=>y.debug("Dashboard auto-open failed (non-critical)",{error:String(o)}))},4e3)}async stop(){return this.stopPromise?this.stopPromise:(this.stopPromise=this.stopInternal(),this.stopPromise)}async stopInternal(){y.info("Stopping MCP Server"),this.analyticsManager.trackSessionEnd(),await this.analyticsManager.shutdown(),await this.executionContextManager.shutdown(),await this.historyManager.shutdown(),await this.httpBridge.stop(),await this.licenseStateManager.shutdown(),await this.server.close(),y.info("MCP Server stopped")}readContextSummary(e){return e&&typeof e=="object"?e:null}readReplayMetadata(e){return e&&typeof e=="object"?e:null}readInlineContextSummary(e){let r={...this.readContextSummary(e.contextSummary)??{}};for(let i of["intent","affectedAreas","testScenario","expectedBehavior","observedBehavior"])e[i]!==void 0&&(r[i]=e[i]);return Object.keys(r).length>0?r:null}readInlineReplayMetadata(e){return this.readReplayMetadata(e.replayMetadata)}isContextAwareTool(e,n){return n==="run_test"||e==="execute_luau"?!0:cne[e]?.has(n)??!1}isContextCaptureEnabledForCurrentProcess(){let e=this.executionContextManager.isEnabled();return e?this.httpBridge.getIsClientMode()?this.httpBridge.getContext().clientModeUpstreamContextCaptureEnabled?e:(this.clientModeContextIdsByScope.clear(),!1):e:(this.clientModeContextIdsByScope.clear(),!1)}resolveExecutionContextSessionId(e){return typeof e.__sessionId=="string"?e.__sessionId:this.httpBridge.getSessionId()}buildContextScopeKey(e,n){let r=typeof n=="number"&&Number.isFinite(n)?n:this.httpBridge.getSyncController()?.getDefaultRuntimePlaceId()??null;return typeof r!="number"||!Number.isFinite(r)?null:`${e}:${r}`}findClientModeContextId(e,n){let r=this.buildContextScopeKey(e,n);if(r&&this.clientModeContextIdsByScope.has(r))return this.clientModeContextIdsByScope.get(r)??null;let i=[...this.clientModeContextIdsByScope.entries()].filter(([a])=>a.startsWith(`${e}:`)).map(([,a])=>a);return i.length===1?i[0]:null}async attachContextIfNeeded(e,n,r,i){if(!this.isContextAwareTool(e,n)||!this.isContextCaptureEnabledForCurrentProcess())return{args:r,params:i};let a=this.resolveExecutionContextSessionId(i),o=typeof i.placeId=="number"?i.placeId:null,s=this.readInlineContextSummary(i),c=this.readInlineReplayMetadata(i),l=typeof i.contextId=="string"?i.contextId:null,u=this.httpBridge.getIsClientMode()?this.findClientModeContextId(a,o):null,p=u?{contextId:u}:await this.executionContextManager.findActiveContext(a,o);try{if(s||c){let m=typeof p=="object"&&p&&"contextId"in p&&typeof p.contextId=="string"?p.contextId:null,h=typeof p=="object"&&p&&"source"in p&&p.source==="implicit"?"implicit":null,g=l||(h==="implicit"?m:null);if(g)try{await this.executionContextManager.update({sessionId:a,placeId:o,placeName:typeof i.placeName=="string"?i.placeName:null,contextId:g,patch:{...h==="implicit"?{source:"explicit"}:{},...s?{contextSummary:s}:{},...c?{replayMetadata:c}:{}}}),p=await this.executionContextManager.getLatestSnapshot(g)}catch{p=null}if(!p){let x=await this.executionContextManager.begin({sessionId:a,placeId:o,placeName:typeof i.placeName=="string"?i.placeName:null,source:"explicit",...s?{contextSummary:s}:{},...c?{replayMetadata:c}:{}});typeof x.contextId=="string"&&(p=await this.executionContextManager.getLatestSnapshot(x.contextId))}}else l&&(p=await this.executionContextManager.getLatestSnapshot(l)??{contextId:l});if(!p){let m=jD(e,n,i);if(m.length>0){let h=await this.executionContextManager.begin({sessionId:a,placeId:o,placeName:typeof i.placeName=="string"?i.placeName:null,source:"implicit",contextSummary:{affectedAreas:m,...n==="run_test"&&typeof r.test_name=="string"&&r.test_name.trim()?{testScenario:r.test_name.trim()}:{}}});typeof h.contextId=="string"&&(p={contextId:h.contextId})}}}catch(m){y.debug("context capture skipped for tool execution",{toolName:e,action:n,error:m instanceof Error?m.message:String(m)}),p=null}if(!p)return{args:r,params:i};let d=n==="run_test"?{...r.contextSummary===void 0&&typeof p=="object"&&"contextSummary"in p?{contextSummary:p.contextSummary}:{},...r.replayMetadata===void 0&&typeof p=="object"&&"replayMetadata"in p?{replayMetadata:p.replayMetadata}:{}}:{},f={...r.contextSummary===void 0&&s?{contextSummary:s}:{},...r.replayMetadata===void 0&&c?{replayMetadata:c}:{}};return{args:{...r,contextId:p.contextId,...f,...d},params:{...i,contextId:p.contextId,...i.contextSummary===void 0&&s?{contextSummary:s}:{},...i.replayMetadata===void 0&&c?{replayMetadata:c}:{},...n==="run_test"&&i.contextSummary===void 0&&"contextSummary"in d?{contextSummary:d.contextSummary}:{},...n==="run_test"&&i.replayMetadata===void 0&&"replayMetadata"in d?{replayMetadata:d.replayMetadata}:{}}}}async refreshImplicitContextIfNeeded(e,n,r,i){if(!this.isContextCaptureEnabledForCurrentProcess())return;let a=typeof r.contextId=="string"?r.contextId:null;if(!a)return;let o=await this.executionContextManager.getLatestSnapshot(a);if(!o||o.source!=="implicit")return;let s=jD(e,n,r,i);if(s.length===0)return;let c=sne(o.contextSummary.affectedAreas,s);c.length===o.contextSummary.affectedAreas.length&&c.every((u,p)=>{let d=o.contextSummary.affectedAreas[p];return d?.target===u.target&&d?.label===u.label&&d?.kind===u.kind})||await this.executionContextManager.update({sessionId:this.resolveExecutionContextSessionId(r),placeId:typeof r.placeId=="number"?r.placeId:null,placeName:typeof r.placeName=="string"?r.placeName:null,contextId:a,patch:{contextSummary:{affectedAreas:c}}})}async enrichRunTestParams(e,n){if(!this.isContextCaptureEnabledForCurrentProcess())return e;let r=typeof e.contextId=="string"?e.contextId:null,i=typeof e.placeId=="number"?e.placeId:null,a=r?await this.executionContextManager.getLatestSnapshot(r):await this.executionContextManager.findActiveContext(n,i);return a?{...e,...r?{}:{contextId:a.contextId},...e.contextSummary===void 0?{contextSummary:a.contextSummary}:{},...e.replayMetadata===void 0?{replayMetadata:a.replayMetadata}:{}}:e}withResultContextId(e,n){if(typeof e.contextId=="string"||!n||typeof n!="object")return e;let r=n.contextId;return typeof r=="string"?{...e,contextId:r}:e}},AD=YS;pe();pe();import{createHash as lne}from"node:crypto";import{copyFileSync as ND,existsSync as am,mkdirSync as une,readFileSync as pne}from"node:fs";import{homedir as dne,platform as fne}from"node:os";import{dirname as mne,join as Rl,resolve as OD}from"node:path";import{fileURLToPath as hne}from"node:url";var gne=hne(import.meta.url),DD=mne(gne),QS="WeppyRobloxMCP.rbxm";function yne(){let t=fne(),e=dne();switch(t){case"win32":return Rl(process.env.LOCALAPPDATA||Rl(e,"AppData","Local"),"Roblox","Plugins");case"darwin":return Rl(e,"Documents","Roblox","Plugins");default:return Rl(e,"Documents","Roblox","Plugins")}}function vne(){let t=OD(DD,"..","roblox-plugin",QS);return am(t)?t:OD(DD,"..","..","roblox-plugin",QS)}function MD(t){let e=pne(t);return lne("md5").update(e).digest("hex")}async function LD(){if(process.env.SKIP_PLUGIN_INSTALL==="true"||process.env.SKIP_PLUGIN_INSTALL==="1")return y.debug("Plugin \uC790\uB3D9\uC124\uCE58 \uC2A4\uD0B5 (SKIP_PLUGIN_INSTALL=true)"),null;let t=vne();if(!am(t))return y.debug("\uBC88\uB4E4\uB41C Plugin \uD30C\uC77C \uC5C6\uC74C, \uC790\uB3D9\uC124\uCE58 \uC2A4\uD0B5",{path:t}),null;let e=yne(),n=Rl(e,QS);if(am(n)){let r=MD(t),i=MD(n);return r===i?(y.debug("Roblox Plugin \uC774\uBBF8 \uCD5C\uC2E0 \uC0C1\uD0DC"),null):(ND(t,n),y.info(`Roblox Plugin \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC \u2192 ${n}`),"updated")}return am(e)||une(e,{recursive:!0}),ND(t,n),y.info(`Roblox Plugin \uC124\uCE58 \uC644\uB8CC \u2192 ${n}`),"installed"}var xne=new Set(["--version","-v"]);function UD(t,e,n=console.log){return t.slice(2).some(i=>xne.has(i))?(n(e),!0):!1}yr();gw();function e0(t,e){return t===void 0||t===""?e:t.toLowerCase()==="true"||t==="1"}function FD(t={}){let e=t.env??process.env,n=t.dataDir??gr(),r=t.appDataDir??hl(),i=Uf(r),a=o=>e[o]!==void 0&&e[o]!=="";return{httpPort:parseInt(e.HTTP_PORT||"3002",10),httpHost:e.HTTP_HOST||"127.0.0.1",logLevel:a("LOG_LEVEL")?e.LOG_LEVEL:i.LOG_LEVEL||"info",requestTimeout:a("REQUEST_TIMEOUT")?parseInt(e.REQUEST_TIMEOUT,10):i.REQUEST_TIMEOUT,enableLocalHistory:a("ENABLE_LOCAL_HISTORY")?e0(e.ENABLE_LOCAL_HISTORY,!0):i.ENABLE_LOCAL_HISTORY,enableLocalStatistics:a("ENABLE_LOCAL_STATISTICS")?e0(e.ENABLE_LOCAL_STATISTICS,!0):i.ENABLE_LOCAL_STATISTICS,enableContextCapture:i.ENABLE_CONTEXT_CAPTURE,dataDir:n,appDataDir:r,enableTelemetry:a("ENABLE_TELEMETRY")?e0(e.ENABLE_TELEMETRY,!1):void 0,licenseApiBaseUrl:e.LICENSE_API_BASE_URL||"https://roblox-license-api.hope841026.workers.dev",licenseProjectId:e.LICENSE_PROJECT_ID||"roblox-mcp",licenseProvider:a("LICENSE_PROVIDER")?e.LICENSE_PROVIDER:i.LICENSE_PROVIDER,licenseRequestTimeoutMs:parseInt(e.LICENSE_REQUEST_TIMEOUT_MS||"8000",10),licenseRetryCount:parseInt(e.LICENSE_RETRY_COUNT||"1",10),licenseRetryDelayMs:parseInt(e.LICENSE_RETRY_DELAY_MS||"300",10),licenseFallbackMaxHours:parseInt(e.LICENSE_FALLBACK_MAX_HOURS||"48",10)}}function qD(t){return t instanceof Error?t:new Error(String(t))}function BD(t){let e=t.timeoutMs??5e3,n=null,r=!1,i=a=>{r||(r=!0,t.exit(a))};return{shutdown(a){return n?(t.logger.warn("Shutdown already in progress, ignoring duplicate request",{reason:a.reason,exitCode:a.exitCode}),n):(n=(async()=>{let o=setTimeout(()=>{t.logger.error("Shutdown timed out, forcing process exit",{reason:a.reason,timeoutMs:e}),i(a.exitCode===0?1:a.exitCode)},e);o.unref?.(),a.error?t.logger.error(`Shutdown requested by ${a.reason}`,a.error):t.logger.info("Shutdown requested",{reason:a.reason,exitCode:a.exitCode});try{await t.stop(),clearTimeout(o),i(a.exitCode)}catch(s){clearTimeout(o),t.logger.error("Error during shutdown cleanup",s),i(a.exitCode===0?1:a.exitCode)}})(),n)},isShuttingDown(){return n!==null}}}function ZD(t){let e=[],n=(o,s,c)=>{o.on(s,c),e.push({target:o,event:s,handler:c})};n(t.stdin,"end",()=>{t.shutdown({reason:"stdin-end",exitCode:0})}),n(t.stdin,"close",()=>{t.shutdown({reason:"stdin-close",exitCode:0})}),n(t.process,"disconnect",()=>{t.shutdown({reason:"disconnect",exitCode:0})});for(let o of["SIGINT","SIGTERM","SIGHUP"])n(t.process,o,()=>{t.shutdown({reason:o,exitCode:0})});n(t.process,"uncaughtException",o=>{t.shutdown({reason:"uncaughtException",exitCode:1,error:qD(o)})}),n(t.process,"unhandledRejection",o=>{t.shutdown({reason:"unhandledRejection",exitCode:1,error:qD(o)})});let r=typeof t.process.ppid=="number"?t.process.ppid:null,i=t.ownerWatchdogIntervalMs??3e3,a=r&&r>1?setInterval(()=>{let o=typeof t.process.ppid=="number"?t.process.ppid:null;o!==null&&(o<=1||o!==r)&&(t.logger.warn("Parent process disappeared, starting graceful shutdown",{initialParentPid:r,currentParentPid:o}),t.shutdown({reason:"parent-exit",exitCode:0}))},i):null;return a?.unref?.(),()=>{for(let{target:o,event:s,handler:c}of e)o.off(s,c);a&&clearInterval(a)}}async function bne(){let t=null,e=null;try{let n=FD();y.info("WROX Server",{version:We,config:{httpHost:n.httpHost,httpPort:n.httpPort,logLevel:n.logLevel,requestTimeout:n.requestTimeout,enableLocalHistory:n.enableLocalHistory,enableLocalStatistics:n.enableLocalStatistics,enableContextCapture:n.enableContextCapture,enableTelemetry:n.enableTelemetry,dataDir:n.dataDir,appDataDir:n.appDataDir,licenseApiBaseUrl:n.licenseApiBaseUrl,licenseProjectId:n.licenseProjectId,licenseProvider:n.licenseProvider,licenseRequestTimeoutMs:n.licenseRequestTimeoutMs,licenseRetryCount:n.licenseRetryCount,licenseRetryDelayMs:n.licenseRetryDelayMs,licenseFallbackMaxHours:n.licenseFallbackMaxHours}});try{await LD()}catch(a){y.warn("Plugin \uC790\uB3D9\uC124\uCE58 \uC2E4\uD328 (\uC11C\uBC84 \uC2DC\uC791\uC5D0 \uC601\uD5A5 \uC5C6\uC74C)",a)}let r=new AD(n);e=BD({stop:async()=>{t?.(),await r.stop()},exit:a=>{process.exit(a)},logger:y}).shutdown,t=ZD({process,stdin:process.stdin,shutdown:e,logger:y}),await r.start()}catch(n){if(e){await e({reason:"startup-failure",exitCode:1,error:n});return}y.error("Failed to start server",n),process.exit(1)}}UD(process.argv,We)||bne();
153
153
  /*! Bundled license information:
154
154
 
155
155
  depd/index.js: