@weppy/roblox-mcp 2.1.2 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Weppy Roblox MCP — MCP server that lets AI coding agents control a live Roblox Studio session with 21 tools, 140+ actions, bidirectional sync, automated playtest, and multi-place support",
|
|
9
|
-
"version": "2.1.
|
|
9
|
+
"version": "2.1.3"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "weppy-roblox-mcp",
|
|
14
14
|
"source": "./plugins/weppy-roblox-mcp",
|
|
15
15
|
"description": "Weppy Roblox MCP — MCP server that lets AI coding agents control a live Roblox Studio session with 21 tools, 140+ actions, bidirectional sync, automated playtest, and multi-place support",
|
|
16
|
-
"version": "2.1.
|
|
16
|
+
"version": "2.1.3",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "hope1026"
|
|
19
19
|
},
|
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weppy/roblox-mcp",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
4
4
|
"description": "MCP (Model Context Protocol) server for Roblox Studio integration - enables AI coding agents to interact with Roblox Studio in real-time",
|
|
5
5
|
"main": "plugins/weppy-roblox-mcp/dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -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:{...Yi}};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:{...Yi}};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 Zo.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{...Xi};let r=this.ctx.places.get(n);return r?{...r.directions}:{...Xi}}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 Zo.readFile(a,"utf-8"),path:a}}catch(s){if(s.code!=="ENOENT")throw s}for(let s of Oo){let c=i.index.resolveScriptPath(n,s,!1);try{return{content:await Zo.readFile(c,"utf-8"),path:c}}catch{continue}}let o=i.index.resolveValuePath(n);try{return{content:await Zo.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:Rt(n.path),properties:{[n.property]:n.value}}),{success:!0,path:n.path};default:throw new Error(`Disk execution not supported for action: ${e}`)}}};import Ce from"path";import{randomUUID as dee}from"crypto";import{promises as nn}from"fs";pe();var Of=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={...Xi};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={...Yi};i.forwardRestoreQueue=[];let a=dee();i.activeFullSyncSessionId=a,this.pendingServiceTrees.set(a,new Map),i.instanceCount=0,i.scriptCount=0;let o=this.ctx.config.getPlaceRoot(e),s=Ce.join(o,`explorer_tmp_${a}`);await nn.mkdir(s,{recursive:!0}),i.tmpIndex=new ai(o,s),i.tmpWriter=new Mo(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=Ce.join(o,".sync-meta.json");await nn.mkdir(Ce.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 oi(i.tmpIndex,d,m.from,m.to),this.rewritePendingEffectivePaths(o,i.tmpIndex.getExplorerRoot(),Ce.join(d,m.from),Ce.join(d,m.to)));let h=i.tmpIndex.resolveChildrenDir(u.path),g=Ce.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=Ce.relative(k,d).split(Ce.sep).filter(H=>H.length>0),D=Ye(["game",...I,f]);w={...u,path:D}}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=Ce.join(o,"explorer"),c=Ce.join(o,`explorer_tmp_${a}`),l=this.getAndClearPreserveLocalFiles(a),u=new Map,p=[];if(l.length>0){for(let m of l){let h=Ce.resolve(o,m);try{let g=await nn.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 nn.rm(s,{recursive:!0,force:!0})}catch{}await nn.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=Ce.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=Ce.relative(m,g),_=Ce.resolve(h,w);i.index.updateHashByValue(_,x)}for(let[g,x]of i.tmpIndex.getAllFileHashes()){let w=Ce.relative(m,g),_=Ce.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=Ce.resolve(o,m);try{await nn.mkdir(Ce.dirname(g),{recursive:!0}),await nn.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=Ce.resolve(o,h);try{await nn.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=Ce.join(i,`explorer_tmp_${n}`);try{await nn.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))},zO);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=Ce.relative(n,r).split(Ce.sep).filter(l=>l.length>0),o=Ce.relative(n,i).split(Ce.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=Ye(["game",...a]),c=Ye(["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 nn.readdir(e,{withFileTypes:!0});for(let r of n)if(r.isDirectory()){if(r.name.startsWith("explorer_tmp_")){let i=Ce.join(e,r.name);y.warn("Removing stale temp directory from crashed sync",{dir:r.name});try{await nn.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=Ce.join(e,r.name);try{let a=await nn.readdir(i,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=Ce.join(i,o.name);y.warn("Removing stale temp directory from crashed sync",{dir:`${r.name}/${o.name}`});try{await nn.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 tr from"path";import{promises as BO}from"fs";pe();var Df=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=tr.resolve(s,u);if(!jf(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await BO.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=Rt(m),g=jt(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(!jf(o,tr.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=tr.resolve(c,i);if(!jf(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 BO.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=tr.relative(i,n);if(a.startsWith("..")||a===""||tr.isAbsolute(a))return null;let o=a.split(tr.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=tr.basename(n),c=tr.dirname(n),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(No.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=i;for(let f of o){d=tr.join(d,f);let m=tr.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return Ye(p)}};pe();function ZO(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 si=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;constructor(e,n={}){this.config=new gf(e,n),this.apiHandler=new Nf(this),this.changeProcessor=new Af(this),this.initHandler=new Of(this),this.reverseHandler=new Df(this),this.places=new hf({max:3,dispose:(r,i)=>{y.info("Disposing place context (LRU eviction)",{placeId:i}),this.activeFullSyncPlaceId===i&&(this.activeFullSyncPlaceId=null),this.activeRuntimeSyncPlaceId===i&&(this.activeRuntimeSyncPlaceId=null),r.fileWatcher&&(r.fileWatcher.stop().catch(a=>{y.error("Error stopping file watcher during dispose",a)}),r.fileWatcher=null),r.writer.stopChangeLogFlusher(),r.incompleteSyncTimer&&(clearTimeout(r.incompleteSyncTimer),r.incompleteSyncTimer=null),r.index.saveToDisk().catch(a=>{y.error("Error saving index during dispose",a)}),r.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(r.activeFullSyncSessionId),r.tmpWriter&&(r.tmpWriter.stopChangeLogFlusher(),r.tmpWriter=null),r.tmpIndex=null,r.collisionDirMap=null}})}getSyncRoot(){return this.config.getSyncRoot()}async getOrCreatePlaceContext(e,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=pS.join(i,"explorer");await Ho.mkdir(i,{recursive:!0}),await Ho.mkdir(a,{recursive:!0});let o=new ai(i,a);await o.loadFromDisk();let s=new Mo(this.config,o,e),c=new xf(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:{...Xi},applyModes:{...Yi},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{ZO(e.body);let r=LO(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{ZO(e.body);let r=UO(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=DO(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 Ef(o,f)}catch(m){c.push({path:f.instancePath,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=MO&&(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:[...Do]};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=pS.join(s,`explorer_tmp_${o}`);await Ho.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=FO(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."+fee().slice(0,8);try{await Ho.writeFile(r,n,"utf-8"),await Ho.rename(r,e)}catch(i){throw await Ho.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=pS.join(this.config.getPlaceRoot(e.placeId),"explorer");e.fileWatcher=new $f(n,e.index),e.writer.setOnWriteCallback(r=>{e.fileWatcher?.suppressPath(r)}),e.fileWatcher.setDirectionChecker(r=>{let i=oO(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 mee}from"crypto";function HO(t,e){let n=mee(),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,dashboardFolderPicker:null,activeSyncOwnerInstanceId:null,activeProjectRoot:null,pendingDashboardSyncRootPin:null,playtestControlCommand:null,dashboardSyncRootSwitchInFlight:!1,aiClientName:"",pluginVersion:"",syncedSessionToken:null,serverLastCommandAt:null}}var VO=fi(Po(),1);pe();function WO(t){let e=VO.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 Tl}from"crypto";pe();function hee(){let t=process.env.WEPPY_ROBLOX_MCP_VERSION?.trim();return t||null}var We=hee()??"2.1.2";Lf();function YO(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 yee(t){let n=Date.now();return Array.from(t.pluginClients.values()).filter(r=>n-r.lastSeen<1e4)}function QO(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 eD(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 tD(t,e,n){let r=parseInt(e.query.maxAge)||3e4,i=El(t,r);i?n.json({cached:!0,...i}):n.json({cached:!1,message:"No cached selection available"})}function El(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 nD(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"}),bl(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}:{}}),typeof o.placeId=="number"&&Number.isFinite(o.placeId)&&Nw(t,o.placeId,o.placeName??null),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:YO(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 rD(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"}),bl(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 iD(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"}),bl(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 aD(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"}),bl(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 oD(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 sD(t){let n=Date.now(),r=Tn({appDataDir:t.config.appDataDir});for(let[i,a]of t.pluginClients)n-a.lastSeen>3e4&&(t.pluginClients.delete(i),t.pendingCommands.delete(i));return QO(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:r,...t.aiClientName?{aiClientName:t.aiClientName}:{}},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function cD(t,e){e.json(sD(t))}function lD(t,e,n){let r=e.query.instanceId;r&&t.mcpInstances.has(r)&&(t.mcpInstances.get(r).lastSeen=Date.now()),QO(t);let i=yee(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:YO(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 uD(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 Uf(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 sD(t)}pe();var Ff={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 vee={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 qf(t){return vee[t]||"plugin"}var Bf=100,xee=Object.entries(Ff).reduce((t,[e,n])=>{for(let r of Object.values(n.mapping))t[r]=e;return t},{});function pD(t){return{toolName:xee[t]||t,actionName:t}}function dD(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 fD(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),xS(n,{event:"command",id:Tl(),data:{action:"connected",requestId:Tl(),params:{serverVersion:We,timestamp:Date.now()}}});let r=setInterval(()=>{xS(n,{event:"command",id:Tl(),data:{action:"keepalive",requestId:Tl(),params:{timestamp:Date.now()}}})},3e4);n.on("close",()=>{y.info("Plugin disconnected from SSE"),clearInterval(r),t.sseClients.delete(n)})}function xS(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=Ce.relative(m,g),_=Ce.resolve(h,w);i.index.updateHashByValue(_,x)}for(let[g,x]of i.tmpIndex.getAllFileHashes()){let w=Ce.relative(m,g),_=Ce.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=Ce.resolve(o,m);try{await nn.mkdir(Ce.dirname(g),{recursive:!0}),await nn.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=Ce.resolve(o,h);try{await nn.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=Ce.join(i,`explorer_tmp_${n}`);try{await nn.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))},zO);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=Ce.relative(n,r).split(Ce.sep).filter(l=>l.length>0),o=Ce.relative(n,i).split(Ce.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=Ye(["game",...a]),c=Ye(["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 nn.readdir(e,{withFileTypes:!0});for(let r of n)if(r.isDirectory()){if(r.name.startsWith("explorer_tmp_")){let i=Ce.join(e,r.name);y.warn("Removing stale temp directory from crashed sync",{dir:r.name});try{await nn.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=Ce.join(e,r.name);try{let a=await nn.readdir(i,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=Ce.join(i,o.name);y.warn("Removing stale temp directory from crashed sync",{dir:`${r.name}/${o.name}`});try{await nn.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 tr from"path";import{promises as BO}from"fs";pe();var Df=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=tr.resolve(s,u);if(!jf(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await BO.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=Rt(m),g=jt(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(!jf(o,tr.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=tr.resolve(c,i);if(!jf(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 BO.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=tr.relative(i,n);if(a.startsWith("..")||a===""||tr.isAbsolute(a))return null;let o=a.split(tr.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=tr.basename(n),c=tr.dirname(n),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(No.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=i;for(let f of o){d=tr.join(d,f);let m=tr.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return Ye(p)}};pe();function ZO(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 si=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;constructor(e,n={}){this.config=new gf(e,n),this.apiHandler=new Nf(this),this.changeProcessor=new Af(this),this.initHandler=new Of(this),this.reverseHandler=new Df(this),this.places=new hf({max:3,dispose:(r,i)=>{y.info("Disposing place context (LRU eviction)",{placeId:i}),this.activeFullSyncPlaceId===i&&(this.activeFullSyncPlaceId=null),this.activeRuntimeSyncPlaceId===i&&(this.activeRuntimeSyncPlaceId=null),r.fileWatcher&&(r.fileWatcher.stop().catch(a=>{y.error("Error stopping file watcher during dispose",a)}),r.fileWatcher=null),r.writer.stopChangeLogFlusher(),r.incompleteSyncTimer&&(clearTimeout(r.incompleteSyncTimer),r.incompleteSyncTimer=null),r.index.saveToDisk().catch(a=>{y.error("Error saving index during dispose",a)}),r.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(r.activeFullSyncSessionId),r.tmpWriter&&(r.tmpWriter.stopChangeLogFlusher(),r.tmpWriter=null),r.tmpIndex=null,r.collisionDirMap=null}})}getSyncRoot(){return this.config.getSyncRoot()}async getOrCreatePlaceContext(e,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=pS.join(i,"explorer");await Ho.mkdir(i,{recursive:!0}),await Ho.mkdir(a,{recursive:!0});let o=new ai(i,a);await o.loadFromDisk();let s=new Mo(this.config,o,e),c=new xf(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:{...Xi},applyModes:{...Yi},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{ZO(e.body);let r=LO(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{ZO(e.body);let r=UO(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=DO(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 Ef(o,f)}catch(m){c.push({path:f.instancePath,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=MO&&(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:[...Do]};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=pS.join(s,`explorer_tmp_${o}`);await Ho.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=FO(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."+fee().slice(0,8);try{await Ho.writeFile(r,n,"utf-8"),await Ho.rename(r,e)}catch(i){throw await Ho.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=pS.join(this.config.getPlaceRoot(e.placeId),"explorer");e.fileWatcher=new $f(n,e.index),e.writer.setOnWriteCallback(r=>{e.fileWatcher?.suppressPath(r)}),e.fileWatcher.setDirectionChecker(r=>{let i=oO(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 mee}from"crypto";function HO(t,e){let n=mee(),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,dashboardFolderPicker:null,activeSyncOwnerInstanceId:null,activeProjectRoot:null,pendingDashboardSyncRootPin:null,playtestControlCommand:null,dashboardSyncRootSwitchInFlight:!1,aiClientName:"",pluginVersion:"",syncedSessionToken:null,serverLastCommandAt:null}}var VO=fi(Po(),1);pe();function WO(t){let e=VO.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 Tl}from"crypto";pe();function hee(){let t=process.env.WEPPY_ROBLOX_MCP_VERSION?.trim();return t||null}var We=hee()??"2.1.3";Lf();function YO(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 yee(t){let n=Date.now();return Array.from(t.pluginClients.values()).filter(r=>n-r.lastSeen<1e4)}function QO(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 eD(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 tD(t,e,n){let r=parseInt(e.query.maxAge)||3e4,i=El(t,r);i?n.json({cached:!0,...i}):n.json({cached:!1,message:"No cached selection available"})}function El(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 nD(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"}),bl(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}:{}}),typeof o.placeId=="number"&&Number.isFinite(o.placeId)&&Nw(t,o.placeId,o.placeName??null),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:YO(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 rD(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"}),bl(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 iD(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"}),bl(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 aD(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"}),bl(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 oD(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 sD(t){let n=Date.now(),r=Tn({appDataDir:t.config.appDataDir});for(let[i,a]of t.pluginClients)n-a.lastSeen>3e4&&(t.pluginClients.delete(i),t.pendingCommands.delete(i));return QO(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:r,...t.aiClientName?{aiClientName:t.aiClientName}:{}},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function cD(t,e){e.json(sD(t))}function lD(t,e,n){let r=e.query.instanceId;r&&t.mcpInstances.has(r)&&(t.mcpInstances.get(r).lastSeen=Date.now()),QO(t);let i=yee(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:YO(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 uD(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 Uf(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 sD(t)}pe();var Ff={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 vee={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 qf(t){return vee[t]||"plugin"}var Bf=100,xee=Object.entries(Ff).reduce((t,[e,n])=>{for(let r of Object.values(n.mapping))t[r]=e;return t},{});function pD(t){return{toolName:xee[t]||t,actionName:t}}function dD(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 fD(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),xS(n,{event:"command",id:Tl(),data:{action:"connected",requestId:Tl(),params:{serverVersion:We,timestamp:Date.now()}}});let r=setInterval(()=>{xS(n,{event:"command",id:Tl(),data:{action:"keepalive",requestId:Tl(),params:{timestamp:Date.now()}}})},3e4);n.on("close",()=>{y.info("Plugin disconnected from SSE"),clearInterval(r),t.sseClients.delete(n)})}function xS(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
|
|
|
Binary file
|