@weppy/roblox-mcp 2.5.0 → 2.5.1
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +8 -0
- package/README.md +2 -1
- package/docs/assets/screenshots/roblox-explorer/roblox-explorer-property-window.png +0 -0
- package/docs/en/explorer/overview.md +104 -0
- package/docs/en/sync/overview.md +1 -1
- package/docs/es/README.md +2 -1
- package/docs/es/explorer/overview.md +104 -0
- package/docs/es/sync/overview.md +1 -1
- package/docs/id/README.md +2 -1
- package/docs/id/explorer/overview.md +104 -0
- package/docs/id/sync/overview.md +1 -1
- package/docs/ja/README.md +2 -1
- package/docs/ja/explorer/overview.md +104 -0
- package/docs/ja/sync/overview.md +1 -1
- package/docs/ko/README.md +2 -1
- package/docs/ko/explorer/overview.md +104 -0
- package/docs/ko/sync/overview.md +1 -1
- package/docs/pt-br/README.md +2 -1
- package/docs/pt-br/explorer/overview.md +104 -0
- package/docs/pt-br/sync/overview.md +1 -1
- package/install.ps1 +16 -5
- package/install.sh +16 -3
- package/package.json +1 -1
- package/plugins/weppy-roblox-mcp/.claude-plugin/plugin.json +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/weppy-icon.png +0 -0
- package/plugins/weppy-roblox-mcp/dist/index.js +1 -1
- package/plugins/weppy-roblox-mcp/roblox-plugin/WeppyRobloxMCP.rbxm +0 -0
- package/docs/assets/screenshots/roblox-explorer/install-from-vsix.png +0 -0
|
@@ -136,7 +136,7 @@ data: ${JSON.stringify(n)}
|
|
|
136
136
|
`)}catch(i){g.warn("Failed to flush change log",{error:i instanceof Error?i.message:String(i),entries:e.length})}}startChangeLogFlusher(){this.flushTimer||(this.flushTimer=setInterval(()=>{this.flushChangeLog(),this.flushHistory()},eoe),this.flushTimer&&typeof this.flushTimer=="object"&&"unref"in this.flushTimer&&this.flushTimer.unref())}async stopChangeLogFlusher(){this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null),await this.flushChangeLog(),await this.flushHistory()}async updateSyncIndex(e,n){this.index.updateBothHashes(e,n)}relativePath(e){return ot.relative(this.config.getSyncRoot(),e)}extractServiceName(e){return Pt(e)[0]??""}findTreeNode(e,n){let i=Pt(n);if(i.length<=1)return null;let r=e.children,a;for(let o=1;o<i.length;o++){if(!r||(a=r.find(s=>s.name===i[o]),!a))return null;r=a.children}return a??null}detachTreeNode(e,n){let i=this.getParentPath(n),r=this.getLeafName(n);if(!i||!r)return null;let a=this.findTreeNode(e,i),o=a?.children??e.children,s=o.findIndex(l=>l.name===r);if(s<0)return null;let[c]=o.splice(s,1);return a?a.childCount=o.length:e.childCount=o.length,c??null}insertTreeNode(e,n,i){let r=this.findTreeNode(e,n);if(r){r.children||(r.children=[]);let o=r.children.findIndex(s=>s.name===i.name);o>=0?r.children[o]=i:r.children.push(i),r.childCount=r.children.length;return}let a=e.children.findIndex(o=>o.name===i.name);a>=0?e.children[a]=i:e.children.push(i),e.childCount=e.children.length}getLeafName(e){let n=Pt(e);return n.length===0?null:n[n.length-1]??null}getParentPath(e){let n=Pt(e);return n.length<=1?null:`game.${n.slice(0,-1).join(".")}`}async copyDirectory(e,n){await this.ensureDir(n);let i=await Ve.readdir(e,{withFileTypes:!0});for(let r of i){let a=ot.join(e,r.name),o=ot.join(n,r.name);r.isDirectory()?await this.copyDirectory(a,o):await Ve.copyFile(a,o)}}async tryRename(e,n){try{return this.onWriteCallback?.(e),this.onWriteCallback?.(n),await Ve.rename(e,n),!0}catch{return!1}}};function hI(t,e){if(t===e)return!0;if(t===null||e===null||typeof t!=typeof e||typeof t!="object")return!1;if(Array.isArray(t))return!Array.isArray(e)||t.length!==e.length?!1:t.every((o,s)=>hI(o,e[s]));let n=t,i=e,r=Object.keys(n),a=Object.keys(i);return r.length!==a.length?!1:r.every(o=>Object.hasOwn(i,o)&&hI(n[o],i[o]))}function toe(t,e){let n={},i=new Set([...Object.keys(t??{}),...Object.keys(e)]);for(let r of i){let a=t?.[r]??null,o=e[r]??null;hI(a,o)||(n[r]={before:a,after:o})}return n}import je from"path";import{promises as Qa}from"fs";oe();var Fy=class{index;placeRoot;constructor(e,n,i){this.index=n,this.placeRoot=i??e.getSyncRoot()}async buildReverseChanges(){let{modified:e,deleted:n}=await this.index.scanForFileModifications(),i=[];for(let a of e){if(this.isForwardOnlyPath(a.fsPath)){this.logForwardOnlySkip(a.fsPath,"buildReverseChanges:modified");continue}try{let o;a.fileType==="script"?o=await this.readModifiedScript(a.fsPath,a.instancePath):a.fileType==="value"?o=await this.readModifiedValue(a.fsPath,a.instancePath):o=await this.readModifiedProps(a.fsPath,a.instancePath),i.push(o)}catch(o){g.warn("Failed to read modified file for reverse sync",{path:a.fsPath,error:o instanceof Error?o.message:String(o)})}}let r=new Set;for(let a of n){if(this.isForwardOnlyPath(a.fsPath)){this.logForwardOnlySkip(a.fsPath,"buildReverseChanges:deleted");continue}if(r.has(a.instancePath))continue;r.add(a.instancePath);let o=je.relative(this.placeRoot,a.fsPath);i.push({type:"instanceRemoved",instancePath:a.instancePath,fsPath:o,fileType:a.fileType,content:null})}return i}async buildChangesFromPending(e){let n=[],i=new Set,r=new Set,a=new Set;for(let o of e)if(o.type==="add"||o.type==="change"){let s=je.dirname(o.fsPath),c=this.getFileType(o.fsPath);(c==="props"||c==="script"||c==="value")&&a.add(s)}for(let o of e)try{if(this.isForwardOnlyPath(o.fsPath)){this.logForwardOnlySkip(o.fsPath,`buildChangesFromPending:${o.type}`);continue}if(o.type==="addDir"||o.type==="unlinkDir"){let d=this.resolveInstancePathFromDirectory(o.fsPath);if(!d)continue;let f=this.getSiblingIndexForDirectory(o.fsPath),m=this.buildPendingDedupeKey(d,f);if(o.type==="unlinkDir"){if(i.has(m)||(i.add(m),this.isServiceInstance(d)))continue;let S=je.relative(this.placeRoot,o.fsPath),_={type:"instanceRemoved",instancePath:d,fsPath:S,fileType:"directory",content:null};f&&(_.siblingIndex=f),n.push(_);continue}if(r.has(m)||this.isServiceInstance(d)||a.has(o.fsPath)||!await this.isContainerDirectory(o.fsPath))continue;let y=je.relative(this.placeRoot,o.fsPath),v={type:"containerCreated",instancePath:d,fsPath:y,fileType:"directory",content:null,className:"Folder",parentPath:this.extractParentPath(d)};f&&(v.siblingIndex=f),r.add(m),n.push(v);continue}let s=this.resolveInstancePathFromFile(o.fsPath);if(!s)continue;let c=this.getSiblingIndexForFile(o.fsPath);if(o.type==="unlink"){let d=this.buildPendingDedupeKey(s,c);if(i.has(d)||(i.add(d),this.isServiceInstance(s)))continue;let f=je.relative(this.placeRoot,o.fsPath),m=this.getFileType(o.fsPath);if(m!=="script"&&m!=="props"&&m!=="value")continue;let h={type:"instanceRemoved",instancePath:s,fsPath:f,fileType:m,content:null};c&&(h.siblingIndex=c),n.push(h);continue}let l=this.getFileType(o.fsPath),u=!this.index.hasEntry(o.fsPath)&&!this.index.hasInstancePath(s),p;if(u&&o.type==="add"){if(this.isServiceInstance(s))continue;if(l==="script")p=await this.readNewScript(o.fsPath,s);else if(l==="value")p=await this.readNewValue(o.fsPath,s);else if(l==="props")p=await this.readNewInstance(o.fsPath,s);else continue}else if(l==="script")p=await this.readModifiedScript(o.fsPath,s);else if(l==="value")p=await this.readModifiedValue(o.fsPath,s);else if(l==="props")p=await this.readModifiedProps(o.fsPath,s);else continue;c&&(p.siblingIndex=c),n.push(p)}catch(s){g.warn("Failed to build change from pending entry",{path:o.fsPath,type:o.type,error:s instanceof Error?s.message:String(s)})}return n}resolveInstancePathFromFile(e){return this.index.resolveInstancePathFromFsPath(e)}getSiblingIndexForFile(e){let n=this.index.getExplorerRoot();if(je.basename(e)==="_tree.json")return;let r=je.relative(n,e);if(r.startsWith(".."))return;let a=r.split(je.sep).filter(p=>p.length>0);if(a.length<2)return;let o=a[a.length-2],s=je.dirname(e),c=je.dirname(s),l=this.index.getOriginalInstance(c,o);return l?l.siblingIndex:Br(o).siblingIndex}isForwardOnlyPath(e){return this.index.isPathUnderClass(e,tc)}logForwardOnlySkip(e,n){g.info("Skipped reverse change for forward-only path",{source:n,path:je.relative(this.placeRoot,e),className:this.index.getClassNameForFsPath(e)??"unknown"})}getFileType(e){return this.index.getFileTypeFromPath(e)}resolveInstancePathFromDirectory(e){let n=this.index.getExplorerRoot(),i=je.relative(n,e);if(i.startsWith("..")||i===""||je.isAbsolute(i))return null;let r=i.split(je.sep).filter(s=>s.length>0);if(r.length===0)return null;let a=["game"],o=n;for(let s of r){o=je.join(o,s);let c=je.dirname(o);a.push(this.index.getOriginalNameForDir(c,s))}return et(a)}getSiblingIndexForDirectory(e){let n=je.basename(e),i=je.dirname(e),r=this.index.getOriginalInstance(i,n);return r?r.siblingIndex:Br(n).siblingIndex}async isContainerDirectory(e){try{let n=await Qa.readdir(e,{withFileTypes:!0});for(let i of n)if(i.isFile()&&Iy.some(r=>i.name.endsWith(r)))return!1;return!0}catch(n){if(n.code==="ENOENT")return!1;throw n}}buildPendingDedupeKey(e,n){return n?`${e}#${n}`:e}async readModifiedScript(e,n){let i=await Qa.readFile(e,"utf-8"),r=je.relative(this.placeRoot,e);return{type:"scriptSource",instancePath:n,fsPath:r,fileType:"script",content:i}}async readModifiedProps(e,n){let i=await Qa.readFile(e,"utf-8"),r=JSON.parse(i),a=je.relative(this.placeRoot,e);return{type:"properties",instancePath:n,fsPath:a,fileType:"props",content:r}}async readModifiedValue(e,n){let i=await Qa.readFile(e,"utf-8"),r=JSON.parse(i),a=je.relative(this.placeRoot,e);return{type:"valueChanged",instancePath:n,fsPath:a,fileType:"value",content:r}}async readNewScript(e,n){let i=await Qa.readFile(e,"utf-8"),r=je.relative(this.placeRoot,e),a=je.basename(e),o="ModuleScript";a.endsWith(".server.luau")?o="Script":a.endsWith(".client.luau")&&(o="LocalScript");let s=this.extractParentPath(n);return{type:"scriptCreated",instancePath:n,fsPath:r,fileType:"script",content:i,className:o,parentPath:s}}async readNewValue(e,n){let i=await Qa.readFile(e,"utf-8"),r=JSON.parse(i),a=je.relative(this.placeRoot,e),o=this.extractParentPath(n);return{type:"valueCreated",instancePath:n,fsPath:a,fileType:"value",content:r,className:r.className,parentPath:o}}async readNewInstance(e,n){let i=await Qa.readFile(e,"utf-8"),r=JSON.parse(i),a=je.relative(this.placeRoot,e),o=this.extractParentPath(n);return{type:"instanceCreated",instancePath:n,fsPath:a,fileType:"props",content:r,className:r.className,parentPath:o}}extractParentPath(e){return Ft(e)}isServiceInstance(e){let n=Hr(e);return n.length!==2||n[0]!=="game"?!1:qC.has(n[1])}};oe();import aoe from"path";oe();import Bi from"path";import{createHash as C2}from"crypto";import{promises as yI}from"fs";var noe=500,gI=class{explorerRoot;syncIndex;scanIntervalMs;previousSnapshot=new Map;pendingChanges=new Map;scanTimer=null;readyResolvers=[];isReady=!1;isPaused=!1;isScanning=!1;refreshSnapshotOnNextScan=!1;scanEpoch=0;pendingSequence=0;suppressedPaths=new Map;directionChecker=null;onForwardViolation=null;getPendingKey(e,n){return`${Bi.resolve(e)}\0${n}`}getPendingSlot(e){return e==="addDir"||e==="unlinkDir"?"dir":"file"}getPendingRank(e){return e==="change"?1:2}isStaleScan(e){return this.isPaused||e!==this.scanEpoch}resolveReadyWaiters(){for(let e of this.readyResolvers)e();this.readyResolvers=[]}queuePendingChange(e){let n=this.getPendingSlot(e.type),i=this.getPendingKey(e.fsPath,n),r=this.getPendingRank(e.type),a=this.pendingChanges.get(i);a&&a.rank>r||this.pendingChanges.set(i,{change:e,rank:r,sequence:++this.pendingSequence})}constructor(e,n,i=noe){this.explorerRoot=Bi.resolve(e),this.syncIndex=n,this.scanIntervalMs=i}setDirectionChecker(e){this.directionChecker=e}setOnForwardViolation(e){this.onForwardViolation=e}suppressPath(e,n){let i=Bi.resolve(e);if(n===void 0){this.suppressedPaths.set(i,{mode:"generic"});return}if(n==="missing"){this.suppressedPaths.set(i,{mode:"missing"});return}this.suppressedPaths.set(i,{mode:"hash",hash:n})}async start(){this.scanTimer&&await this.stop("restart"),g.info("SnapshotChangeScanner: starting",{explorerRoot:this.explorerRoot,scanIntervalMs:this.scanIntervalMs}),this.isPaused=!1,this.refreshSnapshotOnNextScan=!1,this.suppressedPaths.clear(),this.scanEpoch+=1;let e=this.scanEpoch;this.previousSnapshot=await this.syncIndex.buildSnapshotSeed(),!this.isStaleScan(e)&&(this.isReady=!0,this.resolveReadyWaiters(),this.scanTimer=setInterval(()=>{this.scanSnapshotDiffs().catch(n=>{g.warn("SnapshotChangeScanner: background scan failed",{error:n instanceof Error?n.message:String(n)})})},this.scanIntervalMs),typeof this.scanTimer=="object"&&this.scanTimer&&"unref"in this.scanTimer&&this.scanTimer.unref())}async stop(e){g.info("SnapshotChangeScanner: stopping",{reason:e??"unspecified",explorerRoot:this.explorerRoot,isReady:this.isReady,isPaused:this.isPaused,pendingChanges:this.pendingChanges.size}),this.scanTimer&&(clearInterval(this.scanTimer),this.scanTimer=null),this.scanEpoch+=1,this.clearTransientState(),this.previousSnapshot=new Map,this.refreshSnapshotOnNextScan=!1,this.isPaused=!1,this.suppressedPaths.clear(),this.resolveReadyWaiters(),this.isReady=!1,this.isScanning=!1}beginFullSyncReplacement(){this.scanEpoch+=1,this.isPaused=!0,this.isReady=!1,this.refreshSnapshotOnNextScan=!1,this.suppressedPaths.clear(),this.clearTransientState()}async endFullSyncReplacement(){if(this.clearTransientState(),this.scanTimer===null){await this.start();return}let e=++this.scanEpoch;this.isReady=!1,this.finalizeResumedBaseline(e)}isActivelyScanning(){return this.scanTimer!==null&&!this.isPaused}injectPending(e){this.isPaused||(this.queuePendingChange(e),g.debug("SnapshotChangeScanner: injected pending change",{relativePath:e.relativePath,type:e.type}))}drainPendingChanges(){let e=Array.from(this.pendingChanges.values()).sort((n,i)=>n.sequence-i.sequence).map(n=>n.change);return this.pendingChanges.clear(),e}getPendingCount(){return this.pendingChanges.size}getLastDetected(){let e=null;for(let n of this.pendingChanges.values()){let i=n.change;(!e||i.detectedAt>e)&&(e=i.detectedAt)}return e}waitUntilReady(){return this.isReady?Promise.resolve():new Promise(e=>{this.readyResolvers.push(e)})}async rescan(){let e=await this.scanSnapshotDiffs(),n=await this.scanStoredModifications();return e+n}async scanSnapshotDiffs(){if(this.isPaused||this.isScanning)return 0;let e=this.scanEpoch;this.isScanning=!0;try{let n=await this.syncIndex.buildSnapshotSeed();if(this.isStaleScan(e))return 0;if(this.refreshSnapshotOnNextScan)return this.previousSnapshot=n,this.refreshSnapshotOnNextScan=!1,this.suppressedPaths.clear(),0;let i=_2(this.previousSnapshot,n),r=await this.processCandidates(i,e);if(this.isStaleScan(e))return 0;let a=this.commitPendingChanges(r);return this.previousSnapshot=n,this.suppressedPaths.clear(),a>0&&g.info("SnapshotChangeScanner: scan found pending changes",{added:a}),a}finally{this.isScanning=!1}}async scanStoredModifications(){if(this.isPaused)return 0;let e=this.scanEpoch,n=await this.scanStoredModificationsInternal(e);if(!n||this.isStaleScan(e))return 0;for(let i of n.forwardViolations)this.onForwardViolation?.(i);return this.commitPendingChanges(n.pending)}async scanStoredModificationsInternal(e){let n=await this.syncIndex.getModifiedFiles(),i={pending:[],forwardViolations:[]};for(let r of n){if(e!==this.scanEpoch)return null;let a=r.fsPath,o=Bi.relative(this.explorerRoot,a);if(await this.isSuppressedPath(a))continue;if(this.shouldRedirectToForwardRestore(o,a)){i.forwardViolations.push(o);continue}let s;switch(r.changeType){case"modified":s="change";break;case"added":s="add";break;case"deleted":s="unlink";break;default:continue}i.pending.push({fsPath:a,relativePath:o,type:s,detectedAt:new Date().toISOString()})}return i}async processCandidates(e,n){let i=[];for(let r of e){if(this.isStaleScan(n))return[];let a=Bi.join(this.explorerRoot,r.relativePath),o=new Date().toISOString();if(await this.isSuppressedPath(a)||r.kind==="file"&&fI(r.relativePath))continue;if(this.shouldRedirectToForwardRestore(r.relativePath,a)){this.onForwardViolation?.(r.relativePath);continue}if(r.kind==="dir"){i.push({fsPath:a,relativePath:r.relativePath,type:r.changeType,detectedAt:o});continue}if(r.changeType==="unlink"){i.push({fsPath:a,relativePath:r.relativePath,type:"unlink",detectedAt:o});continue}let s=this.syncIndex.getStoredFileHash(a);try{let c=await yI.readFile(a,"utf-8"),l=C2("md5").update(c,"utf-8").digest("hex");if(s&&l===s){g.debug("SnapshotChangeScanner: echo skipped",{relativePath:r.relativePath,changeType:r.changeType});continue}i.push({fsPath:a,relativePath:r.relativePath,type:r.changeType,detectedAt:o})}catch(c){if(c.code==="ENOENT"){i.push({fsPath:a,relativePath:r.relativePath,type:"unlink",detectedAt:o});continue}g.warn("SnapshotChangeScanner: failed to read candidate file",{path:r.relativePath,changeType:r.changeType,error:c instanceof Error?c.message:String(c)})}}return i}commitPendingChanges(e){let n=0;for(let i of e){let r=this.getPendingSlot(i.type),a=this.getPendingKey(i.fsPath,r),o=this.getPendingRank(i.type),s=this.pendingChanges.get(a);s&&s.rank>o||(s||n++,this.pendingChanges.set(a,{change:i,rank:o,sequence:++this.pendingSequence}))}return n}clearTransientState(){this.pendingChanges.clear()}async finalizeResumedBaseline(e){try{let n=await this.syncIndex.buildSnapshotSeed();if(e!==this.scanEpoch)return;this.previousSnapshot=n,this.refreshSnapshotOnNextScan=!1;let i=await this.scanStoredModificationsInternal(e);if(e!==this.scanEpoch)return;if(i){for(let r of i.forwardViolations)this.onForwardViolation?.(r);this.commitPendingChanges(i.pending)}this.suppressedPaths.clear(),this.isPaused=!1,this.isReady=!0,this.resolveReadyWaiters()}catch(n){if(e!==this.scanEpoch)return;g.warn("SnapshotChangeScanner: resume baseline refresh failed",{error:n instanceof Error?n.message:String(n)}),this.refreshSnapshotOnNextScan=!0,this.isPaused=!1,this.isReady=!0,this.resolveReadyWaiters()}}shouldRedirectToForwardRestore(e,n){return this.syncIndex.isPathUnderClass(n,tc)?!0:this.directionChecker?this.directionChecker(e)==="forward":!1}async isSuppressedPath(e){let n=Bi.resolve(e);for(let[i,r]of this.suppressedPaths){if(n===i||n.startsWith(`${i}${Bi.sep}`))return r.mode==="generic"?!0:r.mode==="missing"?!await roe(n):await ioe(n)===r.hash;if(i.startsWith(`${n}${Bi.sep}`))return r.mode==="generic"}return!1}};async function roe(t){try{return await yI.access(t),!0}catch{return!1}}async function ioe(t){try{let e=await yI.readFile(t,"utf-8");return C2("md5").update(e,"utf-8").digest("hex")}catch{return null}}var I2=gI;var Je="snapshot-scanner",qy=class extends I2{placeId;detectorExplorerRoot;lifecycleGeneration=0;constructor(e,n,i={}){super(e,n,i.scanIntervalMs),this.placeId=i.placeId??null,this.detectorExplorerRoot=aoe.resolve(e)}async start(){let e=++this.lifecycleGeneration;g.info("Reverse detector bootstrap scheduled",{placeId:this.placeId,mode:Je,explorerRoot:this.detectorExplorerRoot}),await super.start(),e===this.lifecycleGeneration&&g.info("Reverse detector bootstrap completed",{placeId:this.placeId,mode:Je})}async stop(e){this.lifecycleGeneration+=1,g.info("Reverse detector stopping",{placeId:this.placeId,mode:Je,reason:e??"unspecified"}),await super.stop(e)}beginFullSyncReplacement(){g.info("Reverse detector paused for full sync replacement",{placeId:this.placeId,mode:Je}),super.beginFullSyncReplacement()}async endFullSyncReplacement(){g.info("Reverse detector baseline refresh scheduled",{placeId:this.placeId,mode:Je}),await super.endFullSyncReplacement()}async rescan(){g.debug("Reverse detector manual rescan requested",{placeId:this.placeId,mode:Je});let e=await super.rescan();return g.info("Reverse detector manual rescan completed",{placeId:this.placeId,mode:Je,added:e,pendingCount:this.getPendingCount()}),e}};import mc from"path";import{promises as P2}from"fs";function eo(t,e,n,i,r=!1){if(i&&i>=1){let s=Ft(e);if(s){let c=t.getEffectiveDir(s,i);if(c){let l=Et(e);if(n){let u=t.getResolvedName(e,n);if(u)l=u;else if(r)throw new Error(`Missing collision mapping for ${e}#${n}`)}return vI(t,c,l)}if(r)throw new Error(`Missing parent collision mapping for ${s}#${i}`)}}let a=t.getEffectiveDir(e,n??1);if(a){let s=t.getExplorerRoot(),c=mc.relative(s,a).split(mc.sep).filter(l=>l.length>0);return et(["game",...c])}let o=e;if(n){let s=t.getResolvedName(e,n);if(s)o=Ip(o,s);else if(r)throw new Error(`Missing collision mapping for ${e}#${n}`)}return o}function ooe(t,e){let n=Hr(t),i=Hr(e);if(n.length<=i.length)return!1;for(let r=0;r<i.length;r++)if(n[r]!==i[r])return!1;return!0}function E2(t,e){e=soe(t,e);let n=new Map;for(let r=0;r<e.length;r++){let a=e[r];if(a?.type==="instanceRemoved")try{let o=eo(t.index,a.path,a.siblingIndex,a.parentSiblingIndex,!0);n.set(r,o)}catch{continue}}if(n.size<=1)return e;let i=new Set;for(let[r,a]of n)for(let[o,s]of n)if(r!==o&&ooe(a,s)){i.add(r);break}return i.size===0?e:e.filter((r,a)=>!i.has(a))}function soe(t,e){let n=new Map;for(let a=0;a<e.length;a++){let o=e[a];if(o?.type!=="instanceRemoved"||o.siblingIndex===void 0)continue;let s=n.get(o.path);s||(s=[],n.set(o.path,s)),s.push(a)}let i=!1;for(let a of n.values()){if(a.length<2)continue;let o=new Set;for(let s of a){let c=e[s].siblingIndex;if(o.has(c)){i=!0;break}o.add(c)}if(i)break}if(!i)return e;let r=[...e];for(let[a,o]of n){if(o.length<2)continue;let s=to(t.index,a,void 0,!1),c=new Set(t.index.getRegisteredSiblingIndices(a,s));if(c.size===0)continue;let l=new Set;for(let u of o){let p=e[u];if(p.type!=="instanceRemoved")continue;let d=p.siblingIndex;if(!l.has(d)&&c.has(d))l.add(d);else{let f;for(let m of c)if(!l.has(m)){f=m;break}f!==void 0&&(l.add(f),r[u]={...p,siblingIndex:f})}}}return r}function to(t,e,n,i=!1){if(n&&n>=1){let r=Ft(e);if(r){let a=t.getEffectiveDir(r,n);if(a)return a;if(i)throw new Error(`Missing parent collision mapping for ${r}#${n}`)}}return t.resolveParentDir(e)}async function Zi(t,e,n,i,r){let a=mc.join(e,n),o=mc.join(e,i);try{(await P2.stat(a)).isDirectory()&&(r&&(r.suppressPath(a),r.suppressPath(o)),await P2.rename(a,o),t.renameHashKeysUnder(a,o),t.renameReverseNameMappingsUnder(a,o),t.renameInstanceDirMappingsUnder(a,o))}catch{return}}function vI(t,e,n){let i=mc.relative(t.getExplorerRoot(),e).split(mc.sep).filter(r=>r.length>0);return et(["game",...i,n])}async function By(t,e){let n=t.index.planCollisionReindexAfterBatchRemovals(e.instancePath,[...e.removedSiblingIndices],e.parentDir);for(let i of n.renames)await Zi(t.index,e.parentDir,i.from,i.to,t.fileWatcher),await hc(t.writer,t.index,e.parentDir,i.from,i.to);t.index.applyCollisionReindexAfterBatchRemovals(e.instancePath,e.parentDir,n)}async function hc(t,e,n,i,r){let a=vI(e,n,i),o=vI(e,n,r);await t.renameInTree(a,o,r)}import Zy from"path";var $2=5*60*1e3,T2=100;function bI(t){return t!==null&&typeof t=="object"&&!Array.isArray(t)}function Pp(t){return typeof t=="string"&&t.length>0}function Hy(t,e){let n=Zy.resolve(t),i=Zy.resolve(e),r=Zy.relative(n,i);return r===""||!r.startsWith("..")&&!Zy.isAbsolute(r)}function R2(t){if(!bI(t))return{success:!1,error:"Body must be an object"};let e=t.phase;if(e!=="start"&&e!=="chunk"&&e!=="complete")return{success:!1,error:`Invalid phase: ${String(e)}`};if(!Pp(t.clientId))return{success:!1,error:"clientId is required"};switch(e){case"start":return typeof t.placeId!="number"?{success:!1,error:"placeId must be a number"}:Pp(t.placeName)?typeof t.totalServices!="number"?{success:!1,error:"totalServices must be a number"}:typeof t.totalInstances!="number"?{success:!1,error:"totalInstances must be a number"}:t.previousPlaceId!==void 0&&typeof t.previousPlaceId!="number"?{success:!1,error:"previousPlaceId must be a number"}:{success:!0,data:t}:{success:!1,error:"placeName is required"};case"chunk":return Pp(t.serviceName)?Pp(t.serviceClassName)?Array.isArray(t.instances)?typeof t.chunkIndex!="number"?{success:!1,error:"chunkIndex must be a number"}:typeof t.totalChunks!="number"?{success:!1,error:"totalChunks must be a number"}:{success:!0,data:t}:{success:!1,error:"instances must be an array"}:{success:!1,error:"serviceClassName is required"}:{success:!1,error:"serviceName is required"};case"complete":return typeof t.instanceCount!="number"?{success:!1,error:"instanceCount must be a number"}:typeof t.scriptCount!="number"?{success:!1,error:"scriptCount must be a number"}:{success:!0,data:t}}}function O2(t){if(!bI(t))return{success:!1,error:"Body must be an object"};if(!Pp(t.clientId))return{success:!1,error:"clientId is required"};if(t.placeId!==void 0&&typeof t.placeId!="number")return{success:!1,error:"placeId must be a number"};if(typeof t.timestamp!="number")return{success:!1,error:"timestamp must be a number"};if(!Array.isArray(t.changes)||t.changes.length===0)return{success:!1,error:"changes must be a non-empty array"};let e=new Set(["propertyChanged","instanceAdded","instanceRemoved","instanceRenamed","instanceMoved","scriptSourceChanged"]);for(let n=0;n<t.changes.length;n++){let i=t.changes[n];if(!bI(i)||!e.has(i.type))return{success:!1,error:`changes[${n}]: invalid type '${String(i?.type)}'`}}return{success:!0,data:t}}import Qn from"path";import{promises as no}from"fs";oe();var Vy=class{constructor(e){this.ctx=e}async processChangeForPlace(e,n,i){let r=fL(n.type);if(e.directions[r]==="reverse")return g.info("Reverse enforcement: rejecting Studio change on reverse-only path",{placeId:e.placeId,changeType:n.type,dirKey:r,path:"path"in n?n.path:void 0}),(n.type==="scriptSourceChanged"||n.type==="propertyChanged")&&await this.injectLocalAsReverse(e,n),null;switch(n.type){case"propertyChanged":{let o=eo(e.index,n.path,n.siblingIndex,n.parentSiblingIndex,!0),s=await this.checkPropertyConflictForPlace(e,o,n);if(s)return s;let c=n;if(c.attributes!==void 0||c.tags!==void 0){let l=e.index.resolvePropsPath(o),u=await this.buildPropertyChangedPropsData(l,o,n);await e.writer.writeProps(o,u)}else c.properties&&typeof c.properties=="object"&&!Array.isArray(c.properties)?await e.writer.updateProperties(o,c.properties):n.property!==void 0?await e.writer.updateProperty(o,n.property,n.value):g.warn("propertyChanged: missing both property and properties",{path:o});return null}case"instanceAdded":{let o=ec.has(n.className),s=n.path,c=n.name;{let p=to(e.index,n.path,n.parentSiblingIndex,!0),{resolved:d,retroactiveRename:f}=e.index.registerCollision(n.path,n.siblingIndex,p);f&&(await Zi(e.index,p,f.from,f.to,e.fileWatcher),await hc(e.writer,e.index,p,f.from,f.to));let m=e.index.sanitizeName(n.name);if(d!==m||p!==e.index.resolveParentDir(n.path)){let h=e.index.getExplorerRoot(),y=Qn.relative(h,p).split(Qn.sep).filter(v=>v.length>0);s=et(["game",...y,d]),c=d}}await e.writer.writeInstance({path:s,name:n.name,className:n.className,properties:n.properties,attributes:n.attributes,tags:n.tags,scriptSource:n.scriptSource,hasChildren:!1,childCount:0,siblingIndex:n.siblingIndex}),this.setOriginalClassMapping(e,n.path,n.className,n.siblingIndex);let l={name:c,className:n.className,childCount:0,...c!==n.name?{originalName:n.name}:{}},u=Ft(s)||n.parentPath;return await e.writer.addToTree(u,l),e.instanceCount++,o&&n.scriptSource!==void 0&&e.scriptCount++,null}case"instanceRemoved":{let o=eo(e.index,n.path,n.siblingIndex,n.parentSiblingIndex,!0),s=to(e.index,n.path,n.parentSiblingIndex,!0);g.info("Processing instanceRemoved",{path:o}),await e.writer.removeInstance(o),this.removeOriginalClassMapping(e,n.path,o);let c=Et(o),l=Ft(o);if(l&&c&&await e.writer.removeFromTree(l,c),n.siblingIndex!==void 0){let p=`${s}\0${n.path}`,d=i.get(p);d||(d={instancePath:n.path,parentDir:s,removedSiblingIndices:new Set},i.set(p,d)),d.removedSiblingIndices.add(n.siblingIndex)}let u=Ft(o);if(u)try{let p=e.index.resolveChildrenDir(u);Qn.dirname(p)!==e.index.getExplorerRoot()&&(await no.readdir(p)).length===0&&await no.rmdir(p)}catch{return null}return null}case"instanceRenamed":{let o=eo(e.index,n.oldPath,n.siblingIndex,n.parentSiblingIndex,!0),s=Ft(n.oldPath)===Ft(n.newPath),c=s?to(e.index,n.oldPath,n.parentSiblingIndex,!0):null,l=to(e.index,n.newPath,n.parentSiblingIndex,!0),{resolved:u,retroactiveRename:p}=e.index.registerCollision(n.newPath,n.siblingIndex,l);p&&(await Zi(e.index,l,p.from,p.to,e.fileWatcher),await hc(e.writer,e.index,l,p.from,p.to));let d=u!==e.index.sanitizeName(n.newName)?Ip(n.newPath,u):n.newPath;if(await e.writer.renameInstance(o,d,n.newName),this.renameOriginalClassMapping(e,n.oldPath,n.newPath),await e.writer.renameInTree(o,d,u,n.newName),s&&c&&n.siblingIndex!==void 0){let f=e.index.planCollisionReindexAfterRemoval(n.oldPath,n.siblingIndex,c);for(let m of f.renames)await Zi(e.index,c,m.from,m.to,e.fileWatcher),await hc(e.writer,e.index,c,m.from,m.to);e.index.applyCollisionReindexAfterRemoval(n.oldPath,c,f)}return null}case"instanceMoved":{let o=eo(e.index,n.oldPath,n.siblingIndex,n.parentSiblingIndex,!0),s=to(e.index,n.newPath,n.parentSiblingIndex,!0),{resolved:c,retroactiveRename:l}=e.index.registerCollision(n.newPath,n.siblingIndex,s);l&&(await Zi(e.index,s,l.from,l.to,e.fileWatcher),await hc(e.writer,e.index,s,l.from,l.to));let u=Et(n.newPath)||"",p=c!==e.index.sanitizeName(u)?Ip(n.newPath,c):n.newPath;return await e.writer.moveInstance(o,p),this.renameOriginalClassMapping(e,n.oldPath,n.newPath),await e.writer.moveInTree(o,p),null}case"scriptSourceChanged":{let o=eo(e.index,n.path,n.siblingIndex,n.parentSiblingIndex,!0),s=await this.checkScriptConflictForPlace(e,o,n.source);if(s)return s;let c=await this.resolveScriptClassForPlace(e,o);return await this.cleanupStaleScriptFilesForPlace(e,o,c),await e.writer.writeScript(o,c,n.source,!1),null}}}async injectLocalAsReverse(e,n){if(e.fileWatcher)try{if(n.type==="scriptSourceChanged"){let i=await this.resolveScriptClassForPlace(e,n.path),r=e.index.resolveScriptPath(n.path,i,!1),a=Qn.join(this.ctx.config.getPlaceRoot(e.placeId),"explorer"),o=Qn.relative(a,r);e.fileWatcher.injectPending({fsPath:r,relativePath:o,type:"change",detectedAt:new Date().toISOString()})}else if(n.type==="propertyChanged"){let i=e.index.resolvePropsPath(n.path),r=Qn.join(this.ctx.config.getPlaceRoot(e.placeId),"explorer"),a=Qn.relative(r,i);e.fileWatcher.injectPending({fsPath:i,relativePath:a,type:"change",detectedAt:new Date().toISOString()})}}catch(i){g.debug("injectLocalAsReverse: could not inject",{changeType:n.type,error:i instanceof Error?i.message:String(i)})}}async resolveScriptClassForPlace(e,n){try{let i=e.index.resolvePropsPath(n),r=await no.readFile(i,"utf-8"),a=JSON.parse(r);if(a.className&&ec.has(a.className))return a.className}catch{return"ModuleScript"}return"ModuleScript"}async cleanupStaleScriptFilesForPlace(e,n,i){let r=ky[i],a=Qn.join(this.ctx.config.getPlaceRoot(e.placeId),"explorer");for(let[o,s]of Object.entries(ky)){if(s===r)continue;let c=e.index.resolveScriptPath(n,o,!1);try{await no.unlink(c),e.index.removeHash(c),g.info("Cleaned up stale script file",{placeId:e.placeId,removed:Qn.relative(a,c),currentClassName:i})}catch(l){l.code!=="ENOENT"&&g.warn("Failed to clean up stale script file",{path:c,error:l instanceof Error?l.message:String(l)})}}}async checkPropertyConflictForPlace(e,n,i){try{let r=e.index.resolvePropsPath(n),a=e.index.getFileHash(r);if(!a)return null;let o;try{o=await no.readFile(r,"utf-8")}catch{return null}return e.index.computeHash(o)===a?null:{fsPath:Qn.relative(this.ctx.config.getPlaceRoot(e.placeId),r),instancePath:n,fileType:"props",studioContent:await this.buildStudioPropsConflictContent(r,n,i),fileContent:o}}catch{return null}}async checkScriptConflictForPlace(e,n,i){try{let r=await this.resolveScriptClassForPlace(e,n),a=e.index.resolveScriptPath(n,r,!1),o=e.index.getFileHash(a);if(!o)return null;let s;try{s=await no.readFile(a,"utf-8")}catch{return null}return e.index.computeHash(s)===o?null:{fsPath:Qn.relative(this.ctx.config.getPlaceRoot(e.placeId),a),instancePath:n,fileType:"script",studioContent:i,fileContent:s}}catch{return null}}async applyDeferredCollisionBatchReindexForPlace(e,n){await By(e,n)}async buildStudioPropsConflictContent(e,n,i){let r=await this.buildPropertyChangedPropsData(e,n,i);return JSON.stringify(r,null,2)}async buildPropertyChangedPropsData(e,n,i){let r=null;try{let s=await no.readFile(e,"utf-8");r=JSON.parse(s)}catch{r=null}let a=i.properties?{...i.properties}:{...r?.properties??{}};i.property&&(a[i.property]=i.value);let o={name:r?.name??Et(n)??"",className:i.className??r?.className??"Folder",properties:a};return i.attributes!==void 0?Object.keys(i.attributes).length>0&&(o.attributes={...i.attributes}):r?.attributes&&(o.attributes={...r.attributes}),i.tags!==void 0?i.tags.length>0&&(o.tags=[...i.tags]):r?.tags&&(o.tags=[...r.tags]),o}setOriginalClassMapping(e,n,i,r){e.index.setClassName(n,i,r)}removeOriginalClassMapping(e,n,i){n!==i&&e.index.removeClassMappingsUnderInstancePath(n)}renameOriginalClassMapping(e,n,i){n!==i&&e.index.renameClassMappingKeys(n,i)}};import N2 from"path";import{promises as Wy}from"fs";oe();var Gy=class{constructor(e){this.ctx=e}async handlePreCheck(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({hasPreviousSync:!1,changes:[],summary:{modified:0,deleted:0,added:0,total:0}});return}let r=this.ctx.config.getPlaceRoot(i),a=N2.join(r,".sync-index.json"),o=!1;try{await Wy.access(a),o=!0}catch{o=!1}if(!o){n.status(200).json({hasPreviousSync:!1,changes:[],summary:{modified:0,deleted:0,added:0,total:0}});return}let s=N2.join(r,"explorer"),c=new qi(r,s);await c.loadFromDisk();let l=await c.getModifiedFiles(),u=0,p=0,d=0;for(let m of l)switch(m.changeType){case"modified":u++;break;case"deleted":p++;break;case"added":d++;break}let f=l.map(m=>({relPath:m.relPath,instancePath:m.instancePath,changeType:m.changeType,fileType:m.fileType}));n.status(200).json({hasPreviousSync:!0,changes:f,summary:{modified:u,deleted:p,added:d,total:f.length}})}async handleSyncDirections(e,n){let i=e.body,r=i.placeId??this.ctx.getDefaultRuntimePlaceId();if(r==null){n.status(400).json({error:"Missing placeId"});return}let a=this.ctx.places.get(r);if(!a){n.status(404).json({error:"Place not found",message:`No sync context for place ${r}`});return}if(i.directions&&typeof i.directions=="object"){let o=c=>c==="forward"||c==="reverse"||c==="bidirectional",s=i.directions;o(s.scripts)&&(a.directions.scripts=s.scripts),o(s.values)&&(a.directions.values=s.values),o(s.containers)&&(a.directions.containers=s.containers),o(s.data)&&(a.directions.data=s.data),o(s.services)&&(a.directions.services=s.services),g.info("Sync directions updated",{placeId:r,directions:a.directions})}if(i.applyModes&&typeof i.applyModes=="object"){let o=c=>c==="auto"||c==="manual",s=i.applyModes;o(s.scripts)&&(a.applyModes.scripts=s.scripts),o(s.values)&&(a.applyModes.values=s.values),o(s.containers)&&(a.applyModes.containers=s.containers),o(s.data)&&(a.applyModes.data=s.data),o(s.services)&&(a.applyModes.services=s.services)}this.ctx.touchRuntimePlace(r),n.status(200).json({status:"updated",directions:a.directions,applyModes:a.applyModes})}async handleForwardRestoreList(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({paths:[]});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}let a=r.forwardRestoreQueue.splice(0);this.ctx.touchRuntimePlace(i),g.info("Forward restore list drained",{placeId:i,count:a.length}),n.status(200).json({paths:a})}async handleSyncHistory(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({entries:[],total:0,hasMore:!1});return}let r=tn(e.query.limit,50,1,200),a=tn(e.query.offset,0,0),o=e.query.direction,s=e.query.type,c=this.ctx.places.get(i);c&&(await c.writer.flushHistory(),this.ctx.touchRuntimePlace(i));let l=this.ctx.config.getHistoryPath(i),p=(await wp(l,1e4)).reverse().filter(h=>!(o&&h.direction!==o||s&&h.type!==s)),d=p.length,m={entries:p.slice(a,a+r),total:d,hasMore:a+r<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 r=this.ctx.getDefaultRuntimePlaceId();r!=null&&(n=this.ctx.places.get(r))}if(!n)return"forward";let i=e;return n.directions[i]??"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:{...Wa},reverseDetectorActive:!1,reverseDetectorMode:Je,fileWatcherActive:!1};let i=this.ctx.places.get(n);if(!i)return{state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.ctx.config.getPlaceRoot(n),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0,applyModes:{...Wa},reverseDetectorActive:!1,reverseDetectorMode:Je,fileWatcherActive:!1};let r=i.fileWatcher?.getPendingCount()??0,a=i.fileWatcher?.isActivelyScanning()??!1,o=i.fileWatcher!==null;return{state:i.state,instanceCount:i.instanceCount,scriptCount:i.scriptCount,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,syncRoot:this.ctx.config.getPlaceRoot(n),activeClientId:i.activeClientId,reverseSyncAvailable:r>0,modifiedFileCount:r,applyModes:{...i.applyModes},reverseDetectorActive:a,reverseDetectorMode:Je,fileWatcherActive:o}}async getHistoryDirect(e,n){let i=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(i==null||!Number.isFinite(i))return{entries:[],total:0,hasMore:!1};let r=this.ctx.places.get(i);r&&await r.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(i),l=(await wp(s,1e4)).reverse(),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{...Va};let i=this.ctx.places.get(n);return i?{...i.directions}:{...Va}}getProgressDirect(e){let n=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(n==null)return{state:"idle",isSyncing:!1};let i=this.ctx.places.get(n);if(!i)return{state:"idle",isSyncing:!1};if(!i.syncProgress)return{state:i.state,isSyncing:!1,lastSync:{instanceCount:i.instanceCount,scriptCount:i.scriptCount,completedAt:i.lastFullSync}};let r=i.syncProgress,a=Date.now()-r.syncStartTime,o=r.totalInstances>0?Math.min(100,Math.round(r.processedInstances/r.totalInstances*100)):0,s;if(r.processedInstances>0&&o<100){let l=a/r.processedInstances,u=r.totalInstances-r.processedInstances;s=Math.round(l*u)}let c=a>0?Math.round(r.bytesReceived/a*1e3):0;return{state:i.state,isSyncing:!0,progressPercent:o,currentService:r.currentService,currentChunk:{index:r.currentChunkIndex,total:r.currentTotalChunks},instances:{processed:r.processedInstances,total:r.totalInstances},services:{processed:r.processedServices,total:r.totalServices},elapsedMs:a,estimatedRemainingMs:s,bytesReceived:r.bytesReceived,bytesPerSecond:c}}async readSyncedFile(e,n){let i=parseInt(e,10),r=this.ctx.places.get(i);if(!r)throw new Error(`Place ${e} not found in sync cache`);let a=r.index.resolvePropsPath(n);try{return{content:await Wy.readFile(a,"utf-8"),path:a}}catch(s){if(s.code!=="ENOENT")throw s}for(let s of ec){let c=r.index.resolveScriptPath(n,s,!1);try{return{content:await Wy.readFile(c,"utf-8"),path:c}}catch{continue}}let o=r.index.resolveValuePath(n);try{return{content:await Wy.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,i){let r=parseInt(e,10),a=this.ctx.places.get(r);if(!a)throw new Error(`Place ${e} not found in sync cache`);await a.writer.writeScript(n,"Script",i,!1)}async executeViaDisk(e,n){let i=this.ctx.getDefaultRuntimePlaceId();if(i==null)throw new Error("No active sync place for disk execution");let r=this.ctx.places.get(i);if(!r)throw new Error(`Place ${i} not found in sync cache`);switch(e){case"manage_scripts_set_source":{let a=n.scriptType||n.className||"Script";return await r.writer.writeScript(n.path,a,n.source,!1),{success:!0,path:n.path}}case"manage_properties_set":return await r.writer.writeProps(n.path,{className:n.className||"Instance",name:Et(n.path),properties:{[n.property]:n.value}}),{success:!0,path:n.path};default:throw new Error(`Disk execution not supported for command: ${e}`)}}};import Ie from"path";import{createHash as coe,randomUUID as loe}from"crypto";import{promises as fn}from"fs";oe();function uoe(t){return coe("md5").update(t,"utf-8").digest("hex")}var Jy=class{constructor(e){this.ctx=e}preserveLocalFilesMap=new Map;pendingServiceTrees=new Map;async handleInitStart(e,n,i){if(n.previousPlaceId!==void 0&&n.previousPlaceId!==e&&(g.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){i.status(409).json({error:"Conflict",message:`Place ${this.ctx.activeFullSyncPlaceId} is currently syncing. Only one place can sync at a time.`});return}let r=await this.ctx.getOrCreatePlaceContext(e,n.placeName);if(r.activeClientId&&r.activeClientId!==n.clientId&&r.activeFullSyncSessionId!==null){i.status(409).json({error:"Conflict",message:`Another client (${r.activeClientId}) is currently syncing this place`});return}if(this.ctx.activeFullSyncPlaceId=e,this.ctx.touchRuntimePlace(e),r.activeClientId=n.clientId,r.placeName=n.placeName,n.directions&&typeof n.directions=="object"){let p=n.directions,d=f=>f==="forward"||f==="reverse"||f==="bidirectional";d(p.scripts)&&(r.directions.scripts=p.scripts),d(p.values)&&(r.directions.values=p.values),d(p.containers)&&(r.directions.containers=p.containers),d(p.data)&&(r.directions.data=p.data),d(p.services)&&(r.directions.services=p.services),g.info("Sync directions received",{placeId:e,directions:r.directions})}else r.directions={...Va};if(n.applyModes&&typeof n.applyModes=="object"){let p=n.applyModes,d=f=>f==="auto"||f==="manual";d(p.scripts)&&(r.applyModes.scripts=p.scripts),d(p.values)&&(r.applyModes.values=p.values),d(p.containers)&&(r.applyModes.containers=p.containers),d(p.data)&&(r.applyModes.data=p.data),d(p.services)&&(r.applyModes.services=p.services)}else r.applyModes={...Wa};r.forwardRestoreQueue=[];let a=loe();r.activeFullSyncSessionId=a,this.pendingServiceTrees.set(a,new Map),r.instanceCount=0,r.scriptCount=0;let o=this.ctx.config.getPlaceRoot(e),s=Ie.join(o,`explorer_tmp_${a}`);await fn.mkdir(s,{recursive:!0}),r.tmpIndex=new qi(o,s),r.tmpWriter=new fc(this.ctx.config,r.tmpIndex,e),r.collisionDirMap=new Map;let c=n.preserveLocalFiles;Array.isArray(c)&&c.length>0&&(this.setPreserveLocalFiles(a,c),g.info("PreserveLocalFiles set for sync",{syncId:a,fileCount:c.length}));let l={version:1,placeId:n.placeId,placeName:n.placeName,lastFullSync:null,lastIncrementalSync:null,instanceCount:0,scriptCount:0,syncMode:"mirror"},u=Ie.join(o,".sync-meta.json");await fn.mkdir(Ie.dirname(u),{recursive:!0}),await this.ctx.atomicWriteFile(u,JSON.stringify(l,null,2)+`
|
|
137
137
|
`),this.startTTLTimerForPlace(r,a),r.state="initializing",r.index.resetNameCounters(),r.syncProgress={syncStartTime:Date.now(),totalInstances:n.totalInstances,totalServices:n.totalServices,processedInstances:0,processedServices:0,currentService:null,currentChunkIndex:0,currentTotalChunks:0,bytesReceived:0,processedChunks:0},r.writer.appendChangeLog(`FULL_SYNC_START clientId=${n.clientId} placeId=${n.placeId}`),r.writer.setBootstrapMode(!0),r.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncStart",direction:"forward",source:"studio",path:`place_${n.placeId}`,details:`services:${n.totalServices} instances:${n.totalInstances}`}),g.info("Full sync started",{syncId:a,clientId:n.clientId,placeId:n.placeId,placeName:n.placeName,totalServices:n.totalServices,totalInstances:n.totalInstances}),i.status(200).json({status:"started",syncId:r.activeFullSyncSessionId})}async handleInitChunk(e,n,i){let r=this.ctx.places.get(e);if(!r||!r.activeFullSyncSessionId||!r.tmpIndex||!r.tmpWriter){i.status(400).json({error:"No active sync session",message:"Call sync/init with phase=start first"});return}if(r.activeClientId&&n.clientId&&r.activeClientId!==n.clientId){i.status(409).json({error:"Conflict",message:`This sync session belongs to client ${r.activeClientId}`});return}let a=r.activeFullSyncSessionId,o=this.getOrCreatePendingServiceTree(a,n),s=0,c=r.collisionDirMap;for(let u of n.instances){if(this.ctx.config.isForbiddenPath(u.path))continue;let p=r.tmpIndex.resolveParentDir(u.path),d=c.get(p)??p,{resolved:f,retroactiveRename:m}=r.tmpIndex.registerCollision(u.path,u.siblingIndex??void 0,d);m&&(await Zi(r.tmpIndex,d,m.from,m.to),this.rewritePendingEffectivePaths(o,r.tmpIndex.getExplorerRoot(),Ie.join(d,m.from),Ie.join(d,m.to)));let h=r.tmpIndex.resolveChildrenDir(u.path),y=Ie.join(d,f);y!==h&&c.set(h,y);let v=r.tmpIndex.sanitizeName(u.name),S=u;if(d!==p||f!==v){let w=r.tmpIndex.getExplorerRoot(),I=Ie.relative(w,d).split(Ie.sep).filter(L=>L.length>0),O=et(["game",...I,f]);S={...u,path:O}}let _=await r.tmpWriter.writeInstance(S);r.tmpIndex.setClassName(u.path,u.className,u.siblingIndex),s++,(_.propsWritten||_.valueWritten)&&r.instanceCount++,_.scriptWritten&&r.scriptCount++,o.instances.push({effectivePath:S.path,originalPath:u.path,className:u.className})}let l=this.isLastChunk(o,n.chunkIndex,n.totalChunks);if(l){let u=this.buildServiceTree(r,o);await r.tmpWriter.writeTree(n.serviceName,u);let p=this.pendingServiceTrees.get(a);p?.delete(n.serviceName),p&&p.size===0&&this.pendingServiceTrees.delete(a)}r.syncProgress&&(r.syncProgress.processedInstances+=s,r.syncProgress.currentService=n.serviceName,r.syncProgress.currentChunkIndex=n.chunkIndex,r.syncProgress.currentTotalChunks=n.totalChunks,r.syncProgress.processedChunks++,r.syncProgress.bytesReceived+=JSON.stringify(n).length,l&&r.syncProgress.processedServices++),g.debug("Sync chunk processed",{placeId:e,serviceName:n.serviceName,chunkIndex:n.chunkIndex,totalChunks:n.totalChunks,processed:s}),i.status(200).json({processed:s,service:n.serviceName})}async handleInitComplete(e,n,i){let r=this.ctx.places.get(e);if(!r||!r.activeFullSyncSessionId){i.status(400).json({error:"No active sync session",message:"Call sync/init with phase=start first"});return}if(r.activeClientId&&n.clientId&&r.activeClientId!==n.clientId){i.status(409).json({error:"Conflict",message:`This sync session belongs to client ${r.activeClientId}`});return}let a=r.activeFullSyncSessionId,o=this.ctx.config.getPlaceRoot(e),s=Ie.join(o,"explorer"),c=Ie.join(o,`explorer_tmp_${a}`),l=r.fileWatcher,u=this.getAndClearPreserveLocalFiles(a),p=new Map,d=[];if(u.length>0){for(let f of u){let m=Ie.resolve(o,f);try{let h=await fn.readFile(m,"utf-8");p.set(f,h)}catch{d.push(f)}}g.info("Backed up local files for preservation",{placeId:e,requested:u.length,backed:p.size,deleted:d.length})}l&&l.beginFullSyncReplacement();try{try{await fn.rm(s,{recursive:!0,force:!0})}catch{}await fn.rename(c,s),r.instanceCount=n.instanceCount,r.scriptCount=n.scriptCount,r.lastFullSync=new Date().toISOString();let f={version:1,placeId:r.placeId,placeName:r.placeName,lastFullSync:r.lastFullSync,lastIncrementalSync:r.lastIncrementalSync,instanceCount:r.instanceCount,scriptCount:r.scriptCount,syncMode:"mirror"},m=Ie.join(o,".sync-meta.json");if(await this.ctx.atomicWriteFile(m,JSON.stringify(f,null,2)+`
|
|
138
138
|
`),this.clearTTLTimerForPlace(r,a),r.index.clearAllHashes(),r.index.clearClassMappings(),r.tmpIndex){let h=r.tmpIndex.getExplorerRoot(),y=r.index.getExplorerRoot();for(let[v,S]of r.tmpIndex.getAllHashes()){let _=Ie.relative(h,v),w=Ie.resolve(y,_);r.index.updateHashByValue(w,S)}for(let[v,S]of r.tmpIndex.getAllFileHashes()){let _=Ie.relative(h,v),w=Ie.resolve(y,_);r.index.updateFileHashByValue(w,S)}r.index.resetNameCounters(),r.index.mergeNameMappingsFrom(r.tmpIndex)}if(r.tmpIndex=null,r.tmpWriter&&(r.tmpWriter.stopChangeLogFlusher(),r.tmpWriter=null),this.pendingServiceTrees.delete(a),r.collisionDirMap=null,await r.index.saveToDisk(),r.state="syncing",r.activeFullSyncSessionId=null,r.syncProgress=null,this.ctx.touchRuntimePlace(e),this.ctx.activeFullSyncPlaceId=null,p.size>0){for(let[h,y]of p){let v=Ie.resolve(o,h);try{await fn.mkdir(Ie.dirname(v),{recursive:!0}),await fn.writeFile(v,y,"utf-8"),l?.suppressPath(v,uoe(y))}catch(S){g.warn("Failed to restore preserved file",{path:h,error:S instanceof Error?S.message:String(S)})}}g.info("Restored preserved local files",{placeId:e,count:p.size})}if(d.length>0){let h=0;for(let y of d){let v=Ie.resolve(o,y);try{await fn.unlink(v),r.index.removeHash(v),l?.suppressPath(v,"missing"),h++}catch(S){S.code!=="ENOENT"&&g.warn("Failed to delete preserved-as-deleted file",{path:y,error:S instanceof Error?S.message:String(S)})}}h>0&&(await r.index.saveToDisk(),g.info("Deleted locally-removed files from new sync",{placeId:e,count:h}))}try{let h=this.ctx.config.getSyncRoot();await Ey(h,o)}catch(h){g.warn("Failed to generate place sourcemap after full sync",{placeId:e,error:h instanceof Error?h.message:String(h)})}try{let h=this.ctx.config.getSyncRoot(),y={version:1,lastActivePlaceId:r.placeId,lastActivePlaceName:r.placeName??null,lastActiveAt:new Date().toISOString()},v=Ie.join(h,".project-meta.json");await this.ctx.atomicWriteFile(v,JSON.stringify(y,null,2)+`
|
|
139
|
-
`),await nc(h)}catch(h){g.warn("Failed to refresh root representative sourcemap after full sync",{placeId:e,error:h instanceof Error?h.message:String(h)})}this.ctx.startFileWatcherForPlace(r).catch(h=>{g.warn("Failed to start reverse detector after full sync",{placeId:e,error:h instanceof Error?h.message:String(h)}),r.fileWatcher=null}),r.writer.setBootstrapMode(!1),r.writer.appendChangeLog(`FULL_SYNC_COMPLETE instances=${r.instanceCount} scripts=${r.scriptCount}`),r.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncComplete",direction:"forward",source:"studio",path:`place_${e}`,details:`instances:${r.instanceCount} scripts:${r.scriptCount}`}),g.info("Full sync completed",{placeId:e,instanceCount:r.instanceCount,scriptCount:r.scriptCount}),i.status(200).json({status:"completed",instanceCount:r.instanceCount,scriptCount:r.scriptCount,syncRoot:this.ctx.config.getPlaceRoot(e)})}catch(f){throw l&&l.endFullSyncReplacement(),f}}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 i=setTimeout(async()=>{g.warn("Incomplete sync TTL expired, cleaning up",{placeId:e.placeId,syncId:n});let r=this.ctx.config.getPlaceRoot(e.placeId),a=Ie.join(r,`explorer_tmp_${n}`);try{await fn.rm(a,{recursive:!0,force:!0})}catch(o){g.error("Failed to clean up expired temp dir",o instanceof Error?o:new Error(String(o)))}e.incompleteSyncTimer=null,e.activeFullSyncSessionId===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))},$2);i&&typeof i=="object"&&"unref"in i&&i.unref(),e.incompleteSyncTimer=i}getOrCreatePendingServiceTree(e,n){let i=this.pendingServiceTrees.get(e);i||(i=new Map,this.pendingServiceTrees.set(e,i));let r=i.get(n.serviceName);if(r)return r;let a={serviceName:n.tree?.name??n.serviceName,serviceClassName:n.tree?.className??n.serviceClassName,zeroBasedChunkIndex:n.chunkIndex===0,instances:[]};return i.set(n.serviceName,a),a}isLastChunk(e,n,i){return i<=1?!0:e.zeroBasedChunkIndex?n>=i-1:n>=i}buildServiceTree(e,n){let i={name:n.serviceName,className:n.serviceClassName,childCount:0,children:[],syncedAt:new Date().toISOString()};for(let r of n.instances){let a=this.resolveEffectiveSegments(e,r.effectivePath),o=Pt(r.originalPath);this.upsertTreeNode(i,a,o,r.className)}return this.recomputeTreeChildCounts(i),i.syncedAt=new Date().toISOString(),i}resolveEffectiveSegments(e,n){return e.tmpIndex?Pt(n).map(i=>e.tmpIndex.sanitizeName(i)):Pt(n)}rewritePendingEffectivePaths(e,n,i,r){let a=Ie.relative(n,i).split(Ie.sep).filter(l=>l.length>0),o=Ie.relative(n,r).split(Ie.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=et(["game",...a]),c=et(["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,i,r){if(n.length<=1)return;let a=e.children;for(let o=1;o<n.length;o++){let s=n[o],c=i[o],l=o===n.length-1,u=a.find(p=>p.name===s);u?l&&(u.className=r,c!==void 0&&c!==s&&(u.originalName=c)):(u={name:s,className:l?r:"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=i=>{let r=i.children??[];i.children=r;for(let a of r)n(a);i.childCount=r.length};for(let i of e.children)n(i);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 fn.readdir(e,{withFileTypes:!0});for(let i of n)if(i.isDirectory()){if(i.name.startsWith("explorer_tmp_")){let r=Ie.join(e,i.name);g.warn("Removing stale temp directory from crashed sync",{dir:i.name});try{await fn.rm(r,{recursive:!0,force:!0})}catch(a){g.error(`Failed to remove stale temp dir: ${i.name}`,a instanceof Error?a:new Error(String(a)))}}if(i.name.startsWith("place_")){let r=Ie.join(e,i.name);try{let a=await fn.readdir(r,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=Ie.join(r,o.name);g.warn("Removing stale temp directory from crashed sync",{dir:`${i.name}/${o.name}`});try{await fn.rm(s,{recursive:!0,force:!0})}catch(c){g.error(`Failed to remove stale temp dir: ${i.name}/${o.name}`,c instanceof Error?c:new Error(String(c)))}}}catch{continue}}}}catch(n){if(n.code==="ENOENT")return;g.warn("Failed to scan for stale temp dirs",{error:n instanceof Error?n.message:String(n)})}}};import er from"path";import{promises as Ky}from"fs";oe();var Xy=class{constructor(e){this.ctx=e}async handleReversePending(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({pending:0,hasConflicts:!1,lastDetected:null});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}let a={pending:r.fileWatcher?.getPendingCount()??0,hasConflicts:!1,lastDetected:r.fileWatcher?.getLastDetected()??null,forwardRestoreNeeded:r.forwardRestoreQueue.length};this.ctx.touchRuntimePlace(i),n.status(200).json(a)}async handleReverseSyncChanges(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({changes:[],count:0});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}if(r.state!=="syncing"){n.status(400).json({error:"Not syncing",message:"Reverse sync is only available when sync is active"});return}let a=r.fileWatcher?.drainPendingChanges()??[],o=a.length>0?await r.reader.buildChangesFromPending(a):[];this.ctx.touchRuntimePlace(i),n.status(200).json({changes:o,count:o.length})}async handleReverseSyncResult(e,n){let i=e.body,r=i.placeId??this.ctx.getDefaultRuntimePlaceId();if(r==null){n.status(400).json({error:"Validation error",message:"placeId is required (in body or via active sync session)"});return}let a=i.appliedFiles??i.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(r);if(!o){n.status(404).json({error:"Place not found",message:`No sync context for place ${r}`});return}this.ctx.touchRuntimePlace(r);let s=this.ctx.config.getPlaceRoot(r),c=0,l=[];for(let u of a){let p=er.resolve(s,u);if(!Hy(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await Ky.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=Et(m),y=Ft(m);y&&h&&await o.writer.removeFromTree(y,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",source:"local",path:`place_${r}`,details:`applied:${c} failed:${l.length}`}),n.status(200).json({updated:c,failed:l.length,errors:l})}async handleResolveConflict(e,n){let i=e.body;if(!i.fsPath||!i.resolution){n.status(400).json({error:"Validation error",message:"fsPath and resolution are required"});return}let{fsPath:r,resolution:a}=i,o=this.ctx.config.getSyncRoot();if(!Hy(o,er.resolve(o,r))){n.status(403).json({error:"Forbidden",message:"Path is outside the sync root"});return}if(a==="skip"){n.status(200).json({status:"skipped",fsPath:r});return}let s;if(i.placeId&&(s=this.ctx.places.get(i.placeId)),!s){let d=r.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=er.resolve(c,r);if(!Hy(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"){if(typeof i.studioContent!="string"){n.status(400).json({error:"Validation error",message:"studioContent is required for apply-studio resolution"});return}await Ky.mkdir(er.dirname(l),{recursive:!0});let d=i.studioContent,f=u.computeHash(d);s.fileWatcher?.suppressPath(l,f),await Ky.writeFile(l,d,"utf-8"),u.updateHashByValue(l,f),u.updateFileHashByValue(l,f),await u.saveToDisk(),n.status(200).json({status:"resolved",resolution:"apply-studio",fsPath:r});return}if(a==="apply-file"){let d=await Ky.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:r,instancePath:h,fileType:m,content:d});return}n.status(400).json({error:"Invalid resolution",message:`Unknown resolution: ${a}`})}async handleReverseRescan(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({added:0});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}if(!r.fileWatcher){n.status(200).json({added:0});return}let a=await r.fileWatcher.rescan();this.ctx.touchRuntimePlace(i),g.info("Reverse rescan completed",{placeId:i,added:a}),n.status(200).json({added:a})}resolveInstancePathForAppliedPath(e,n){let i=e.resolveInstancePathFromFsPath(n);if(i)return i;let r=e.getExplorerRoot(),a=er.relative(r,n);if(a.startsWith("..")||a===""||er.isAbsolute(a))return null;let o=a.split(er.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=er.basename(n),c=er.dirname(n),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(Iy.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=r;for(let f of o){d=er.join(d,f);let m=er.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return et(p)}};oe();function D2(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={})}function doe(t){return t.type==="instanceAdded"||t.type==="instanceRemoved"||t.type==="instanceRenamed"||t.type==="instanceMoved"||t.type==="scriptSourceChanged"}var wr=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;reverseDetectorFactory;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;currentConnectedPlaceId=null;currentConnectedPlaceName=null;constructor(e,n={}){this.config=new wy(e,n),this.reverseDetectorFactory=n.reverseDetectorFactory??(i=>new qy(i.explorerRoot,i.syncIndex,{placeId:i.placeId})),this.apiHandler=new Gy(this),this.changeProcessor=new Vy(this),this.initHandler=new Jy(this),this.reverseHandler=new Xy(this),this.places=new Uy({max:3,dispose:(i,r)=>{g.info("Disposing place context (LRU eviction)",{placeId:r}),this.activeFullSyncPlaceId===r&&(this.activeFullSyncPlaceId=null),i.fileWatcher&&(g.info("Disposing place context: stopping reverse detector",{placeId:r,reason:"lru-dispose",mode:Je}),i.fileWatcher.stop("lru-dispose").catch(a=>{g.error("Error stopping reverse detector during dispose",a)}),i.fileWatcher=null),i.writer.stopChangeLogFlusher().catch(a=>{g.error("LRU dispose \uC911 changelog flush \uC2E4\uD328",a)}),i.incompleteSyncTimer&&(clearTimeout(i.incompleteSyncTimer),i.incompleteSyncTimer=null),i.index.clearAllMaps(),i.index.saveToDisk().catch(a=>{g.error("Error saving index during dispose",a)}),i.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(i.activeFullSyncSessionId),i.tmpWriter&&(i.tmpWriter.stopChangeLogFlusher(),i.tmpWriter=null),i.tmpIndex=null,i.collisionDirMap=null}})}getSyncRoot(){return this.config.getSyncRoot()}async getOrCreatePlaceContext(e,n){let i=this.places.get(e);if(i&&n){let r=this.config.getPlaceRoot(e),a=await this.config.resolvePlaceRoot(e,n);a!==r&&(g.info("Place root migrated, recreating context",{placeId:e,from:r,to:a}),this.places.delete(e),i=void 0)}if(!i){let r=await this.config.resolvePlaceRoot(e,n),a=xI.join(r,"explorer");await gc.mkdir(r,{recursive:!0}),await gc.mkdir(a,{recursive:!0});let o=new qi(r,a);await o.loadFromDisk();let s=new fc(this.config,o,e),c=new Fy(this.config,o,r);s.startChangeLogFlusher(),i={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:{...Va},applyModes:{...Wa},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null},this.places.set(e,i),g.info("Created new place context",{placeId:e,placeRoot:r})}return i}async handleSyncInit(e,n){try{D2(e.body);let i=R2(e.body);if(!i.success){n.status(400).json({error:"Validation error",message:i.error});return}let r=i.data,a=r.phase==="start"?r.placeId??null:r.phase==="chunk"||r.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(r.phase){case"start":this.setCurrentConnectedPlace(a,r.placeName),await this.initHandler.handleInitStart(a,r,n);break;case"chunk":await this.initHandler.handleInitChunk(a,r,n);break;case"complete":await this.initHandler.handleInitComplete(a,r,n);break}}catch(i){this.sendError(n,i,"handleSyncInit")}}async handleSyncUpdate(e,n){try{D2(e.body);let i=O2(e.body);if(!i.success){n.status(400).json({error:"Validation error",message:i.error});return}let r=i.data,a=r.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(this.setCurrentConnectedPlace(a,o.placeName||this.currentConnectedPlaceName),o.activeFullSyncSessionId!==null||o.state==="initializing"){n.status(409).json({error:"Conflict",message:`Full sync in progress for place ${a}`});return}o.activeClientId=r.clientId,this.touchRuntimePlace(a);let s=E2(o,r.changes);g.info("Sync update received",{placeId:a,clientId:r.clientId,changeCount:s.length,receivedCount:r.changes.length,types:s.map(m=>m.type)});let c=[],l=[],u=0,p=!1,d=new Map;for(let m of s)try{let h=await this.changeProcessor.processChangeForPlace(o,m,d);h?l.push(h):(u++,doe(m)&&(p=!0))}catch(h){let y="path"in m?m.path:"oldPath"in m?m.oldPath:"unknown";c.push({path:y,error:h instanceof Error?h.message:String(h)})}for(let m of d.values())try{await By(o,m)}catch(h){c.push({path:m.instancePath,error:h instanceof Error?h.message:String(h)})}if(p)try{let m=this.config.getSyncRoot(),h=this.config.getPlaceRoot(a);await Ey(m,h),await nc(m)}catch(m){g.warn("Failed to refresh sourcemaps after incremental sync update",{placeId:a,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=T2&&(await o.index.saveToDisk(),o.changesSinceLastSave=0),o.lastIncrementalSync=new Date().toISOString();let f={processed:u,failed:c.length,errors:c,syncedAt:o.lastIncrementalSync};l.length>0&&(f.conflicts=l),n.status(200).json(f)}catch(i){this.sendError(n,i,"handleSyncUpdate")}}getDefaultRuntimePlaceId(){return this.currentConnectedPlaceId}getDefaultRuntimePlaceName(){if(this.currentConnectedPlaceName)return this.currentConnectedPlaceName;if(this.currentConnectedPlaceId!==null&&this.currentConnectedPlaceId!==void 0){let e=this.places.get(this.currentConnectedPlaceId);if(e?.placeName)return e.placeName}return null}setCurrentConnectedPlace(e,n){if(this.currentConnectedPlaceId=e,e==null){this.currentConnectedPlaceName=null;return}if(typeof n=="string"&&n.length>0){this.currentConnectedPlaceName=n;let r=this.places.get(e);r&&(r.placeName=n);return}let i=this.places.get(e);i?.placeName&&(this.currentConnectedPlaceName=i.placeName)}clearCurrentConnectedPlaceIfMatch(e){this.currentConnectedPlaceId===e&&(this.currentConnectedPlaceId=null,this.currentConnectedPlaceName=null)}touchRuntimePlace(e){this.activeRuntimeSyncPlaceId=e}clearRuntimePlaceIfMatch(e){this.activeRuntimeSyncPlaceId===e&&(this.activeRuntimeSyncPlaceId=null)}resolveQueryPlaceId(e,n="runtime"){let i=e.query.placeId;if(i){let r=parseInt(i,10);if(!isNaN(r))return r}return n==="full"?this.activeFullSyncPlaceId:this.getDefaultRuntimePlaceId()}async handleSyncStatus(e,n){try{let i=u=>({state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:u,activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0,reverseDetectorActive:!1,reverseDetectorMode:Je,fileWatcherActive:!1}),r=this.resolveQueryPlaceId(e);if(r==null){let u=i(this.config.getSyncRoot());n.status(200).json(u);return}let a=this.places.get(r);if(!a){let u=i(this.config.getPlaceRoot(r));n.status(200).json(u);return}let o=a.fileWatcher?.getPendingCount()??0,s=a.fileWatcher?.isActivelyScanning()??!1,c=a.fileWatcher!==null;this.touchRuntimePlace(r);let l={state:a.state,instanceCount:a.instanceCount,scriptCount:a.scriptCount,lastFullSync:a.lastFullSync,lastIncrementalSync:a.lastIncrementalSync,syncRoot:this.config.getPlaceRoot(r),activeClientId:a.activeClientId,reverseSyncAvailable:o>0,modifiedFileCount:o,applyModes:a.applyModes,directions:a.directions,reverseDetectorActive:s,reverseDetectorMode:Je,fileWatcherActive:c,forwardOnlyClasses:[...tc]};n.status(200).json(l)}catch(i){this.sendError(n,i,"handleSyncStatus")}}async handleSyncStop(e,n){try{let i=e.body,r=i.placeId??this.getDefaultRuntimePlaceId();if(g.info("handleSyncStop requested",{placeId:r,clientId:i.clientId??null,reason:i.reason??"requested"}),r==null){n.status(200).json({status:"idle",state:"idle",placeId:null,message:"No active sync place"});return}let a=this.places.get(r);if(!a){g.info("handleSyncStop no place context",{placeId:r,clientId:i.clientId??null}),n.status(200).json({status:"idle",state:"idle",placeId:r,message:`No sync context for place ${r}`});return}if(i.clientId&&a.activeClientId&&i.clientId!==a.activeClientId&&(a.activeFullSyncSessionId!==null||a.state==="syncing"||a.state==="initializing")){g.warn("handleSyncStop rejected due to active client mismatch",{placeId:r,requestedClientId:i.clientId,activeClientId:a.activeClientId,state:a.state,activeFullSyncSessionId:a.activeFullSyncSessionId}),n.status(409).json({error:"Conflict",message:`This sync session belongs to client ${a.activeClientId}`});return}let o=a.activeFullSyncSessionId,s=a.state,c=a.fileWatcher!==null;if(o&&(this.initHandler.clearPreserveLocalFiles(o),this.initHandler.clearPendingServiceTrees(o)),a.fileWatcher&&(g.info("handleSyncStop suspending reverse detector",{placeId:r,state:a.state,activeClientId:a.activeClientId,mode:Je}),a.fileWatcher.beginFullSyncReplacement()),a.incompleteSyncTimer&&(clearTimeout(a.incompleteSyncTimer),a.incompleteSyncTimer=null),o){let l=this.config.getPlaceRoot(r),u=xI.join(l,`explorer_tmp_${o}`);await gc.rm(u,{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,a.forwardRestoreQueue=[],this.activeFullSyncPlaceId===r&&(this.activeFullSyncPlaceId=null),this.clearRuntimePlaceIfMatch(r),g.info("handleSyncStop responding idle",{placeId:r,previousState:s,activeFullSyncSessionId:o,hadWatcher:c}),g.info("Sync stopped",{placeId:r,reason:i.reason??"requested"}),n.status(200).json({status:"stopped",state:"idle",placeId:r})}catch(i){this.sendError(n,i,"handleSyncStop")}}async initialize(){try{await this.config.loadFromMeta(),await this.initHandler.cleanupStaleTempDirs(),g.info("SyncController initialized")}catch(e){g.error("SyncController initialization failed",e instanceof Error?e:new Error(String(e)))}}async shutdown(){try{g.info("SyncController shutdown: clearing place contexts",{placeCount:this.places.size}),this.places.clear(),g.info("SyncController shut down")}catch(e){g.error("SyncController shutdown error",e instanceof Error?e:new Error(String(e)))}}async atomicWriteFile(e,n){let i=e+".tmp."+poe().slice(0,8);try{await gc.writeFile(i,n,"utf-8"),await gc.rename(i,e)}catch(r){throw await gc.unlink(i).catch(()=>{}),r}}async handlePreCheck(e,n){try{await this.apiHandler.handlePreCheck(e,n)}catch(i){this.sendError(n,i,"handlePreCheck")}}async handleSyncDirections(e,n){try{await this.apiHandler.handleSyncDirections(e,n)}catch(i){this.sendError(n,i,"handleSyncDirections")}}async handleForwardRestoreList(e,n){try{await this.apiHandler.handleForwardRestoreList(e,n)}catch(i){this.sendError(n,i,"handleForwardRestoreList")}}async handleReversePending(e,n){try{await this.reverseHandler.handleReversePending(e,n)}catch(i){this.sendError(n,i,"handleReversePending")}}async handleReverseSyncChanges(e,n){try{await this.reverseHandler.handleReverseSyncChanges(e,n)}catch(i){this.sendError(n,i,"handleReverseSyncChanges")}}async handleReverseSyncResult(e,n){try{await this.reverseHandler.handleReverseSyncResult(e,n)}catch(i){this.sendError(n,i,"handleReverseSyncResult")}}async handleResolveConflict(e,n){try{await this.reverseHandler.handleResolveConflict(e,n)}catch(i){this.sendError(n,i,"handleResolveConflict")}}async handleReverseRescan(e,n){try{await this.reverseHandler.handleReverseRescan(e,n)}catch(i){this.sendError(n,i,"handleReverseRescan")}}async handleSyncHistory(e,n){try{await this.apiHandler.handleSyncHistory(e,n)}catch(i){this.sendError(n,i,"handleSyncHistory")}}async startFileWatcherForPlace(e){if(e.state!=="syncing"){g.debug("Skipping reverse detector start - place not syncing",{placeId:e.placeId,state:e.state});return}if(e.fileWatcher){if(e.fileWatcher.isActivelyScanning()){g.info("startFileWatcherForPlace: reusing active reverse detector",{placeId:e.placeId,mode:Je});return}g.info("startFileWatcherForPlace: resuming paused reverse detector",{placeId:e.placeId,mode:Je}),e.fileWatcher.endFullSyncReplacement();return}let n=this.createReverseChangeDetector(e);this.configureReverseChangeDetector(e,n),e.fileWatcher=n,n.start().then(()=>{e.fileWatcher===n&&g.info("Reverse detector started for reverse sync",{placeId:e.placeId,mode:Je})}).catch(i=>{g.warn("Failed to start reverse detector",{placeId:e.placeId,mode:Je,error:i instanceof Error?i.message:String(i)}),e.fileWatcher===n&&(e.fileWatcher=null)})}createReverseChangeDetector(e){return this.reverseDetectorFactory({explorerRoot:xI.join(this.config.getPlaceRoot(e.placeId),"explorer"),syncIndex:e.index,placeId:e.placeId})}configureReverseChangeDetector(e,n){e.writer.setOnWriteCallback(i=>{n.suppressPath(i)}),n.setDirectionChecker(i=>{let r=mL(i);return e.directions[r]}),n.setOnForwardViolation(i=>{e.forwardRestoreQueue.includes(i)||(e.forwardRestoreQueue.push(i),g.info("Forward violation queued for restore",{placeId:e.placeId,relativePath:i,queueSize:e.forwardRestoreQueue.length}))})}getStatusSummary(){return this.apiHandler.getStatusSummary()}getDirectionForCategory(e){return this.apiHandler.getDirectionForCategory(e)}getStatusDirect(e){return this.apiHandler.getStatusDirect(e)}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,i){await this.apiHandler.writeSyncedFile(e,n,i)}async executeViaDisk(e,n){return this.apiHandler.executeViaDisk(e,n)}sendError(e,n,i){let r=n instanceof Error?n.message:String(n);if(r.includes("Path traversal detected")){e.status(403).json({error:"Forbidden",message:r});return}let a=n.code;if(a==="ENOSPC"||a==="EPERM"||a==="EACCES"){e.status(500).json({error:"Disk error",message:r});return}g.error(`SyncController.${i} failed`,n instanceof Error?n:new Error(r)),e.status(500).json({error:"Internal error",message:`${i}: ${r}`})}};import{randomUUID as foe}from"crypto";function j2(t,e){let n=foe(),i=n.replace(/-/g,"").substring(0,8).toUpperCase(),r=`${i.substring(0,4)}-${i.substring(4,8)}`;return{config:t,app:e,instanceId:n,sessionId:r,startTime:Date.now(),baseUrl:`http://${t.httpHost}:${t.httpPort}`,commandQueue:new Map,pendingCommands:new Map,globalPendingCommands:[],totalCommandsProcessed:0,pluginClients:new Map,pluginCommandSessionsByClientId:new Map,pluginCommandClientIdsByProcessToken:new Map,mcpInstances:new Map,cachedSelectionMap:new Map,isClientMode:!1,clientModeHealthTimer:null,clientModeConsecutiveHealthFailures:0,clientModeUpstreamReachable:!0,clientModeUpstreamContextCaptureEnabled:!0,clientModeLastHealthSuccessAt:null,clientModeLastHealthFailureAt:null,clientModeLastHealthError:null,onUpstreamPermanentlyDown:null,clientModeIdleTimeoutMs:t.clientModeIdleTimeoutMs??36e5,clientModeIdleWatchdogTimer:null,shutdownFn:null,historyManager:null,analyticsManager:null,executionContextManager:null,licenseState:null,syncController:null,internalCommandExecutor:null,dashboardFolderPicker:null,activeSyncOwnerInstanceId:null,activeProjectRoot:null,pendingDashboardSyncRootPin:null,playtestControlCommand:null,dashboardSyncRootSwitchInFlight:!1,aiClientName:"",pluginVersion:"",syncedSessionToken:null,serverLastCommandAt:null}}var A2=Rt(Qs(),1);oe();function z2(t){let e=A2.default.json({limit:"5mb"});t.app.use((n,i,r)=>{if(n.path.startsWith("/sync/")){r();return}e(n,i,r)}),t.app.use((n,i,r)=>{i.setHeader("Access-Control-Allow-Origin","http://localhost:3002"),i.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),i.setHeader("Access-Control-Allow-Headers","Content-Type"),r()}),t.app.use((n,i,r)=>{g.debug(`${n.method} ${n.path}`,{ip:n.ip}),r()})}gp();import{randomUUID as e6}from"crypto";oe();function moe(){let t=process.env.WEPPY_ROBLOX_MCP_VERSION?.trim();return t||null}var Ke=moe()??"2.5.0";Rp();function ev(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 B2(t){let n=Date.now();for(let[i,r]of t.mcpInstances)r.lastSeen&&n-r.lastSeen>15e3&&(t.mcpInstances.delete(i),r.sessionId&&t.executionContextManager?.endSession(r.sessionId),g.debug("Removed stale MCP instance",{instanceId:i,lastSeen:r.lastSeen}))}function Z2(t,e,n){let i=e.query.clientId;if(i&&t.pluginClients.has(i)){let r=t.pluginClients.get(i);r.lastSeen=Date.now()}try{let r=e.body;if(!r||!Array.isArray(r.selection)||typeof r.count!="number"){g.warn("Invalid selection update request",{body:r}),n.status(400).json({error:"Invalid request body"});return}let a=i||"unknown",o=Date.now();t.cachedSelectionMap.set(a,{selection:r.selection,count:r.count,timestamp:o,clientId:a}),g.debug("Selection cache updated",{count:r.count,clientId:a,timestamp:o}),n.json({status:"ok",timestamp:o})}catch(r){g.error("Error handling selection update",r),n.status(500).json({error:"Internal server error"})}}function H2(t,e,n){let i=parseInt(e.query.maxAge)||3e4,r=Op(t,i);r?n.json({cached:!0,...r}):n.json({cached:!1,message:"No cached selection available"})}function Op(t,e=3e4,n){if(t.cachedSelectionMap.size===0)return null;let i;if(n)i=t.cachedSelectionMap.get(n);else for(let a of t.cachedSelectionMap.values())(!i||a.timestamp>i.timestamp)&&(i=a);if(!i)return null;let r=Date.now()-i.timestamp;return e===0||r<=e?i:null}function EI(t,e){let n=t.pluginClients.get(e.clientId),i=Date.now(),r={clientId:e.clientId,projectName:e.projectName,placeName:e.placeName,placeId:e.placeId,pluginVersion:e.pluginVersion,connectedAt:n?.connectedAt||i,lastSeen:i,commandsProcessed:n?.commandsProcessed||0,connectionType:n?.connectionType||"polling",inFlightRequestId:n?.inFlightRequestId,...n?.commandSessionState?{commandSessionState:n.commandSessionState}:{},...n?.processToken?{processToken:n.processToken}:{}};return t.pluginClients.set(e.clientId,r),t.syncController&&typeof t.syncController.setCurrentConnectedPlace=="function"&&typeof e.placeId=="number"&&Number.isFinite(e.placeId)&&t.syncController.setCurrentConnectedPlace(e.placeId,e.placeName??null),t.pendingCommands.has(e.clientId)||t.pendingCommands.set(e.clientId,[]),e.pluginVersion&&(t.pluginVersion=e.pluginVersion),typeof r.placeId=="number"&&Number.isFinite(r.placeId)&&QC(t,r.placeId,r.placeName??null),t.analyticsManager&&(e.pluginVersion&&t.analyticsManager.setPluginVersion(e.pluginVersion),t.analyticsManager.trackPluginConnected()),g.info("Plugin client registered",{clientId:e.clientId,projectName:e.projectName,placeName:e.placeName,isReconnect:!!n}),{clientInfo:r,sessionId:t.sessionId,mcpVersion:Ke,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:ev(t)}}function $I(t,e){let n=t.pluginClients.get(e),i=t.pluginClients.has(e);if(t.pluginClients.delete(e),t.pendingCommands.delete(e),i&&t.syncController&&typeof t.syncController.clearCurrentConnectedPlaceIfMatch=="function"&&typeof n?.placeId=="number"&&Number.isFinite(n.placeId)){let r=[...t.pluginClients.values()].filter(a=>typeof a.placeId=="number"&&Number.isFinite(a.placeId)).sort((a,o)=>o.lastSeen-a.lastSeen)[0];r?t.syncController.setCurrentConnectedPlace?.(r.placeId??null,r.placeName??null):t.syncController.clearCurrentConnectedPlaceIfMatch(n.placeId)}return i&&(Qe(t,"connection",{clientId:e,status:"disconnected"}),oc(t,{timestamp:new Date().toISOString(),type:"plugin",status:"disconnected",clientId:e,message:`Plugin disconnected \u2014 ${e}`})),g.info("Plugin client unregistered",{clientId:e,existed:i}),i}function tv(t,e,n){try{let i=e.body;if(!i.clientId){n.status(400).json({error:"Missing clientId"});return}let r=EI(t,{clientId:i.clientId,projectName:i.projectName,placeName:i.placeName,placeId:i.placeId,pluginVersion:i.pluginVersion});n.json({status:"ok",clientId:i.clientId,serverInstanceId:t.instanceId,sessionId:r.sessionId,mcpVersion:r.mcpVersion,connectedAt:r.clientInfo.connectedAt,aiClientNames:r.aiClientNames,serverStartTime:t.startTime,mcpInstanceCount:r.mcpInstanceCount})}catch(i){g.error("Error registering plugin client",i),n.status(500).json({error:"Internal server error"})}}function nv(t,e,n){let i=e.body?.clientId;if(!i){n.status(400).json({error:"Missing clientId"});return}let r=$I(t,i);n.json({status:"ok",existed:r})}function V2(t,e,n){try{let i=e.body;if(!i.instanceId){n.status(400).json({error:"Missing instanceId"});return}let r=Date.now(),a={instanceId:i.instanceId,...typeof i.sessionId=="string"?{sessionId:i.sessionId}:{},pid:i.pid,connectedAt:r,isServer:!1,lastSeen:r};i.aiClientName&&(a.aiClientName=i.aiClientName),i.cwd&&(a.cwd=i.cwd),"projectRoot"in i&&(a.projectRoot=i.projectRoot),t.mcpInstances.set(i.instanceId,a),Qe(t,"mcp_status",{aiClientName:a.aiClientName??"Unknown",instanceId:i.instanceId,status:"registered"}),oc(t,{timestamp:new Date().toISOString(),type:"mcp",status:"registered",instanceId:i.instanceId,message:`MCP registered \u2014 ${a.aiClientName??i.instanceId}`,...a.aiClientName?{aiClientName:a.aiClientName}:{}}),g.info("MCP instance registered (client mode)",{instanceId:i.instanceId,pid:i.pid,cwd:i.cwd}),n.json({status:"ok",instanceId:i.instanceId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Ke,mcpInstanceCount:t.mcpInstances.size+1})}catch(i){g.error("Error registering MCP instance",i),n.status(500).json({error:"Internal server error"})}}function W2(t,e,n){let i=e.body?.instanceId;if(!i){n.status(400).json({error:"Missing instanceId"});return}let r=t.mcpInstances.get(i),a=!!r;t.mcpInstances.delete(i),r?.sessionId&&t.executionContextManager?.endSession(r.sessionId),a&&(Qe(t,"mcp_status",{aiClientName:r?.aiClientName??"Unknown",instanceId:i,status:"unregistered"}),oc(t,{timestamp:new Date().toISOString(),type:"mcp",status:"unregistered",instanceId:i,message:`MCP unregistered \u2014 ${r?.aiClientName??i}`,...r?.aiClientName?{aiClientName:r.aiClientName}:{}})),g.info("MCP instance unregistered",{instanceId:i,existed:a}),n.json({status:"ok",existed:a})}function G2(t,e,n){let{instanceId:i,aiClientName:r}=e.body;if(i&&r){let a=t.mcpInstances.get(i);a&&(a.aiClientName=r)}n.json({status:"ok"})}function J2(t){let e=3e4,n=Date.now(),i=Jn({appDataDir:t.config.appDataDir});for(let[r,a]of t.pluginClients)n-a.lastSeen>e&&(t.pluginClients.delete(r),t.pendingCommands.delete(r));return B2(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Ke,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:i,lastSeen:n,...t.aiClientName?{aiClientName:t.aiClientName}:{}},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function rv(t,e){e.json(J2(t))}function iv(t,e,n){let i=e.query.instanceId;i&&t.mcpInstances.has(i)&&(t.mcpInstances.get(i).lastSeen=Date.now()),B2(t);let r=Ja(t),s={...{status:"online",connectedClients:r.length,queuedCommands:qL(t),uptime:Date.now()-t.startTime,version:Ke,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:ev(t),pluginVersion:t.pluginVersion||void 0,dashboardSseClients:t.dashboardSseClients?.size??0,websocketClients:r.length,pluginClients:r.map(c=>({clientId:c.clientId,projectName:c.projectName,placeName:c.placeName,pluginVersion:c.pluginVersion,connectionType:c.connectionType,commandSessionState:c.commandSessionState,lastSeen:c.lastSeen})),...t.isClientMode?{upstream:{reachable:t.clientModeUpstreamReachable,consecutiveFailures:t.clientModeConsecutiveHealthFailures,lastSuccessAt:t.clientModeLastHealthSuccessAt,lastFailureAt:t.clientModeLastHealthFailureAt,lastError:t.clientModeLastHealthError,baseUrl:t.baseUrl}}:{}};n.json(s)}function K2(t,e,n,i){let r=e.ip||e.socket.remoteAddress||"";if(!(r==="127.0.0.1"||r==="::1"||r==="::ffff:127.0.0.1"||r==="localhost")){g.warn("Shutdown request rejected from non-localhost",{ip:r}),n.status(403).json({error:"Forbidden: localhost only"});return}g.info("Shutdown request received, initiating graceful shutdown",{requestedBy:r,uptime:Date.now()-t.startTime}),n.json({status:"shutting_down",message:"Server will shutdown gracefully",pid:process.pid}),setTimeout(async()=>{try{await i(),g.info("Graceful shutdown completed"),process.exit(0)}catch(o){g.error("Error during graceful shutdown",o),process.exit(1)}},100)}async function av(t){if(t.isClientMode)try{let e=await fetch(`${t.baseUrl}/connection-info`);if(e.ok)return await e.json()}catch(e){g.warn("Failed to fetch connection info from server",{error:e})}return J2(t)}oe();Ep();var Np={query_instances:{discriminator:"action",validActions:new Set(["get","children","find_child","find_descendant","wait_for_child","class_info","search_name","search_class","search_property","search_tag","file_tree","project_structure","descendants","ancestors"]),paramAliases:{query_instances_search_name:{query:"pattern"},query_instances_search_property:{root:"rootPath"},query_instances_search_tag:{root:"rootPath"},query_instances_project_structure:{root:"rootPath"}}},mutate_instances:{discriminator:"action",validActions:new Set(["create","create_with_props","delete","clone","move","rename","pivot","create_tree","mass_create","mass_delete","mass_duplicate","smart_duplicate"]),paramAliases:{mutate_instances_clone:{path:"sourcePath"}}},manage_properties:{discriminator:"action",validActions:new Set(["get","set","get_all","set_multiple","get_attr","set_attr","get_all_attrs","delete_attr","add_tag","remove_tag","check_tag","get_tags","get_tagged","set_calculated","set_relative","mass_set","mass_get","modify_children"]),paramAliases:{manage_properties_get_tagged:{tagName:"tag",root:"rootPath"},manage_properties_set_relative:{amount:"value"}}},manage_scripts:{discriminator:"action",validActions:new Set(["get_source","set_source","create","delete","edit_replace","edit_insert","edit_delete","search","replace","get_dependencies"]),paramAliases:{manage_scripts_edit_replace:{newLines:"newContent"},manage_scripts_edit_insert:{lines:"content"},manage_scripts_replace:{pattern:"searchPattern"}}},manage_lighting:{discriminator:"action",validActions:new Set(["lighting","atmosphere","sky","terrain_props","time"])},manage_selection:{discriminator:"action",validActions:new Set(["get","set","clear","cached","context","details","add","remove","watch"])},manage_camera:{discriminator:"action",validActions:new Set(["info","focus_path","focus_position","suggest"]),paramAliases:{manage_camera_suggest:{path:"targetPath"}}},manage_tween:{discriminator:"action",validActions:new Set(["create","play","pause","cancel"])},manage_audio:{discriminator:"action",validActions:new Set(["play","stop","pause","resume","set_listener"])},manage_animation:{discriminator:"action",validActions:new Set(["load","play","stop","get_tracks"])},manage_physics:{discriminator:"action",validActions:new Set(["register_group","set_collidable","get_groups"])},manage_effects:{discriminator:"action",validActions:new Set(["emit","clear","toggle"])},manage_terrain:{discriminator:"action",validActions:new Set(["fill_block","fill_ball","fill_cylinder","fill_wedge","clear_region","clear_bounds","replace_material","colors_get","colors_set","read_voxel","read_voxels","write_voxels","generate","smooth"])},spatial_query:{discriminator:"action",validActions:new Set(["raycast","find_ground","check_placement","multi_raycast","scan_area","find_flat","find_spawn","analyze_walkable","spatial_map","find_space","bounds","snap_grid","collision"]),paramAliases:{spatial_query_spatial_map:{path:"rootPath"}}},manage_assets:{discriminator:"action",validActions:new Set(["insert","info","search","search_insert","insert_free","insert_package","export"]),paramAliases:{manage_assets_search:{maxResults:"limit"}}},manage_sync:{discriminator:"action",validActions:new Set(["status_current_place","history","directions","read_file","write_file","progress"])},workspace_state:{discriminator:"action",validActions:new Set(["sync","snapshot","changes","viewport","clear_history","metadata","scripts","selection_info","clear_cache"])},manage_logs:{discriminator:"action",validActions:new Set(["get","clear","errors"]),paramAliases:{manage_logs_get:{level:"type"}}},system_info:{discriminator:"action",validActions:new Set(["ping","connection","usage","place_info","services","studio_settings","play","stop","pause","resume","play_status","run_test"])}};var yoe={manage_selection_cached:"internal",manage_sync_status_current_place:"internal",manage_sync_history:"internal",manage_sync_directions:"internal",manage_sync_read_file:"internal",manage_sync_write_file:"internal",manage_sync_progress:"internal",system_info_connection:"internal",system_info_run_test:"internal"};function ov(t){return yoe[t]||"plugin"}var X2=new Set(["batch_execute","execute_luau","extended_call_method","extended_call_methods","extended_get_properties","extended_get_property","extended_set_properties","extended_set_property","manage_animation_get_tracks","manage_animation_load","manage_animation_play","manage_animation_stop","manage_assets_export","manage_assets_info","manage_assets_insert","manage_assets_insert_free","manage_assets_insert_package","manage_assets_search","manage_assets_search_insert","manage_audio_pause","manage_audio_play","manage_audio_resume","manage_audio_set_listener","manage_audio_stop","manage_effects_clear","manage_effects_emit","manage_effects_toggle","manage_lighting_atmosphere","manage_lighting_lighting","manage_lighting_sky","manage_lighting_terrain_props","manage_lighting_time","manage_physics_get_groups","manage_physics_register_group","manage_physics_set_collidable","manage_properties_mass_get","manage_properties_mass_set","manage_properties_modify_children","manage_properties_set_calculated","manage_properties_set_relative","manage_scripts_replace","manage_selection_add","manage_selection_context","manage_selection_details","manage_selection_remove","manage_selection_watch","manage_sync_directions","manage_sync_history","manage_sync_progress","manage_sync_read_file","manage_sync_status_current_place","manage_sync_write_file","manage_terrain_clear_bounds","manage_terrain_clear_region","manage_terrain_colors_get","manage_terrain_colors_set","manage_terrain_fill_ball","manage_terrain_fill_block","manage_terrain_fill_cylinder","manage_terrain_fill_wedge","manage_terrain_generate","manage_terrain_read_voxel","manage_terrain_read_voxels","manage_terrain_replace_material","manage_terrain_smooth","manage_terrain_write_voxels","manage_tween_cancel","manage_tween_create","manage_tween_pause","manage_tween_play","mutate_instances_create_tree","mutate_instances_mass_create","mutate_instances_mass_delete","mutate_instances_mass_duplicate","mutate_instances_smart_duplicate","query_instances_ancestors","query_instances_descendants","query_instances_file_tree","query_instances_project_structure","query_instances_search_property","query_instances_search_tag","spatial_query_analyze_walkable","spatial_query_bounds","spatial_query_check_placement","spatial_query_collision","spatial_query_find_flat","spatial_query_find_ground","spatial_query_find_space","spatial_query_find_spawn","spatial_query_multi_raycast","spatial_query_raycast","spatial_query_scan_area","spatial_query_snap_grid","spatial_query_spatial_map","system_info_pause","system_info_place_info","system_info_play","system_info_play_status","system_info_resume","system_info_run_test","system_info_services","system_info_stop","system_info_studio_settings","workspace_state_changes","workspace_state_clear_cache","workspace_state_clear_history","workspace_state_metadata","workspace_state_scripts","workspace_state_selection_info","workspace_state_snapshot","workspace_state_sync","workspace_state_viewport"]),TI=new Set(["system_info_connection","system_info_ping","system_info_usage"]);function Vr(t){return X2.has(t)?"pro":"basic"}function voe(t){if(t===null||typeof t!="object")return;let e=t.proFallback;if(e===null||typeof e!="object")return;let n=e;if(typeof n.executedCommand!="string")return;let i=Array.isArray(n.alternatives)?n.alternatives.filter(r=>typeof r=="string"):void 0;return{executedCommand:n.executedCommand,...typeof n.requestedCommand=="string"?{requestedCommand:n.requestedCommand}:{},...i?{alternatives:i}:{}}}var boe=/^Pro action '[^']+' is blocked in Basic mode\. Basic fallback '([^']+)' failed: (.*)$/s,xoe=/^Pro action '[^']+' is blocked in Basic mode\.(?! Basic fallback ')/,_oe=/instance not found|not found in|parent not found/,Soe=/is required|missing required/,woe=/unknown action|unknown command/,koe=/must be|invalid|cannot/;function Coe(t){let e=boe.exec(t);return e?{fallbackCommand:e[1],reason:e[2]}:null}function Ioe(t){return xoe.test(t)}function Poe(t){let e=t.toLowerCase();return _oe.test(e)?"not_found":Soe.test(e)?"missing_param":woe.test(e)?"unknown_action":koe.test(e)?"validation":"other"}function bc(t){if(t.resultSuccess){let n=voe(t.resultData);return n?{kind:"success_fallback",fallbackCommand:n.executedCommand,...n.alternatives?{alternatives:n.alternatives}:{}}:{kind:"success_ok"}}if(t.tier!=="pro"||!t.resultError)return{kind:"error"};let e=Coe(t.resultError);return e?{kind:"blocked_fallback_failed",fallbackCommand:e.fallbackCommand,blockedDetail:Poe(e.reason)}:Ioe(t.resultError)?{kind:"blocked_unsupported"}:{kind:"error"}}var RI=100,Eoe=12e4,$oe=Object.entries(Np).reduce((t,[e,n])=>{for(let i of n.validActions)t[`${e}_${i}`]=e;return t},{});function Y2(t){return{toolName:$oe[t]||t,commandName:t}}function Q2(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 sv(t,e){if(t!=="manage_sync_status_current_place")return e;let{placeId:n,...i}=e;return i}async function OI(t,e,n,i,r,a){if(!t.historyManager)return;let o=bc({resultSuccess:r.success,resultData:r.data,resultError:r.error,tier:Vr(n)});if(r.success){let s=o.kind==="success_fallback"&&r.data&&typeof r.data=="object"?{...r.data,outcomeStatus:"fallback",requestedCommand:n,executedCommand:o.fallbackCommand,...o.alternatives?{alternatives:o.alternatives}:{}}:r.data;await t.historyManager.recordSuccess(e,sv(n,i),s,a,n);return}if(o.kind==="blocked_unsupported"){await t.historyManager.recordBlockedOutcome(e,sv(n,i),r.error||"Pro action blocked in Basic mode","blocked_unsupported",{executionTimeMs:a,command:n});return}if(o.kind==="blocked_fallback_failed"){await t.historyManager.recordBlockedOutcome(e,sv(n,i),r.error||"Pro action blocked in Basic mode","blocked_fallback_failed",{executionTimeMs:a,command:n,...o.fallbackCommand?{fallbackCommand:o.fallbackCommand}:{},...o.blockedDetail?{blockedDetail:o.blockedDetail}:{}});return}await t.historyManager.recordFailure(e,sv(n,i),r.error||"Unknown error",a,void 0,n)}function Toe(t,e,n){if(!e)return;let i=t.pluginClients.get(e);i?.inFlightRequestId===n&&(i.inFlightRequestId=void 0)}function Roe(t,e,n){if(g.debug("Broadcasting command",{command:e.data.command,requestId:e.data.requestId,targetClientId:n||"all",activeWebsocketClients:Ja(t).length}),n){let i=t.pendingCommands.get(n)||[];i.length>=RI&&i.shift(),i.push(e),t.pendingCommands.set(n,i)}else{let i=Ja(t).sort((o,s)=>s.lastSeen-o.lastSeen),r=e.data.params?.placeId,a;if(r!==void 0&&i.length>1&&(a=i.find(o=>o.placeId===r),a&&g.debug("Routed command by placeId",{clientId:a.clientId,placeId:r})),a||(a=i[0],i.length>1&&g.warn("Multiple plugin clients connected, routing to most recent",{targetClientId:a?.clientId,targetPlaceId:a?.placeId,totalClients:i.length,command:e.data.command})),a){let o=t.pendingCommands.get(a.clientId)||[];o.length>=RI&&o.shift(),o.push(e),t.pendingCommands.set(a.clientId,o),g.debug("Routed command to client",{clientId:a.clientId,projectName:a.projectName,placeId:a.placeId})}else t.globalPendingCommands.length>=RI&&t.globalPendingCommands.shift(),t.globalPendingCommands.push(e)}JC(t,n)}async function t6(t,e,n){let i=Date.now(),r="",a={};try{let o=e.body;r=o.command??"",a=o.params||{};let s=o.requestId;if(o.instanceId||(t.serverLastCommandAt=Date.now()),o.instanceId){let m=t.mcpInstances.get(o.instanceId);m&&(m.lastCommandAt=Date.now(),m.lastSeen=m.lastCommandAt)}let c=Y2(r),l=typeof o.timeout=="number"&&Number.isFinite(o.timeout)&&o.timeout>0?{timeout:o.timeout}:void 0,u=o.instanceId?t.mcpInstances.get(o.instanceId)?.sessionId??t.sessionId:t.sessionId;if(!r){n.status(400).json({error:"Missing command"});return}if(g.debug("Received execute request",{command:r,requestId:s}),r==="get_cached_selection"){let m=a.maxAge,h=Op(t,m!==void 0?m:3e4),y=Date.now()-i;if(!h){let _={requestId:s,success:!0,data:{cached:!1,message:"No cached selection data available. The plugin may not be connected or no selection changes have occurred yet."}};t.historyManager&&await t.historyManager.recordSuccess(c.toolName,a,_.data,y,c.commandName),n.json(_);return}let v=Date.now()-h.timestamp,S={cached:!0,selection:h.selection,count:h.count,timestamp:h.timestamp,age:v};t.historyManager&&await t.historyManager.recordSuccess(c.toolName,a,S,y,c.commandName),n.json({requestId:s,success:!0,data:S});return}if(r==="get_connection_info"){let m=await av(t),h=Date.now()-i;t.historyManager&&await t.historyManager.recordSuccess(c.toolName,a,m,h,c.commandName),n.json({requestId:s,success:!0,data:m});return}if(ov(r)==="internal"){if(!t.internalCommandExecutor){n.status(503).json({requestId:s,success:!1,error:`Internal command executor is not initialized for command: ${r}`});return}let m={...a,__sessionId:u},h=await t.internalCommandExecutor(r,m),y=Date.now()-i,v=Q2(m,h.data);t.historyManager&&await OI(t,c.toolName,c.commandName,v,{success:h.success,data:h.data,error:h.error||"Internal command failed"},y),n.json({requestId:s,...h});return}let p=await xc(t,r,a,s,l),d=Date.now()-i,f={...Q2(a,p.data),__sessionId:u};t.historyManager&&await OI(t,c.toolName,c.commandName,f,p,d),n.json(p)}catch(o){let s=Date.now()-i,c=tr(o);if(t.historyManager&&r){let l=Y2(r);await t.historyManager.recordFailure(l.toolName,a,c,s,"EXCEPTION",l.commandName)}g.error("Execute request failed",o),n.status(500).json({success:!1,error:c})}}async function n6(t,e,n){let i=Date.now(),r=e.body,a=r.action;if(!a||typeof a!="string"){n.status(400).json({success:!1,error:"Missing required field: action"});return}if(!new Set(["get","set","get_all","set_multiple","get_attr","set_attr","get_all_attrs","delete_attr","add_tag","remove_tag","check_tag","get_tags","get_tagged"]).has(a)){n.status(400).json({success:!1,error:`Unsupported action: ${a}`});return}let s=`manage_properties_${a}`,c=e6(),{action:l,...u}=r;try{t.serverLastCommandAt=Date.now();let p=await xc(t,s,u,c),d=Date.now()-i;t.historyManager&&await OI(t,"manage_properties",s,u,p,d),n.json(p)}catch(p){let d=Date.now()-i,f=tr(p);t.historyManager&&await t.historyManager.recordFailure("manage_properties",u,f,d,"EXCEPTION",s),g.error("/api/properties \uC694\uCCAD \uCC98\uB9AC \uC2E4\uD328",p),n.status(500).json({success:!1,error:f})}}async function xc(t,e,n,i,r){g.debug("Executing command locally",{command:e,requestId:i});let a=t.config.requestTimeout,o=r?.timeout??Math.max(t.config.requestTimeout,Eoe),s=new Promise((c,l)=>{let u={requestId:i,command:e,params:n,timestamp:Date.now(),queueTimeoutMs:a,ackedTimeoutMs:o,state:pn.QUEUED,resolve:c,reject:l,timeoutId:null};t.commandQueue.set(i,u),u.timeoutId=setTimeout(()=>{t.commandQueue.delete(u.requestId),Toe(t,u.targetClientId,u.requestId),u.reject(new Error(`Command timeout after ${a}ms: ${u.command}`))},a)});return Roe(t,{event:"command",id:e6(),data:{command:e,requestId:i,params:n}}),s}oe();function cv(t,e){let n={};e.provider!==void 0&&(n.provider=e.provider),e.tier!==void 0&&(n.tier=e.tier),e.status!==void 0&&(n.status=e.status),e.canUsePro!==void 0&&(n.canUsePro=e.canUsePro),e.cleared===!0&&(n.cleared=!0),Qe(t,"license",n)}function nr(t,e,n,i){t.status(e).json({success:!1,error:{code:n,message:i}})}function Ooe(t){if(typeof t!="string")return;let e=t.trim().toLowerCase();return e.length>0?e:void 0}function lv(t,e){if(e==="plugin")return t;let n={...t};return delete n.sessionToken,n}function Dp(t,e,n,i){let r=Ooe(i??e.query.provider);return r?t.licenseState?.supportsProvider(r)===!1?(nr(n,400,"LICENSE_PROVIDER_UNSUPPORTED",`provider "${r}" is not supported`),null):r:(nr(n,400,"LICENSE_PROVIDER_REQUIRED","provider is required"),null)}function jp(t,e){return t.licenseState?!0:(nr(e,503,"LICENSE_NOT_INITIALIZED","License system is not initialized"),!1)}function Noe(t){if(!t||typeof t!="object")return!1;let e=t;return(e.canUsePro===!0||e.canUsePro===!1)&&typeof e.status=="string"&&typeof e.checkedAt=="number"}function Ap(t,e,n,i="plugin"){if(!jp(t,n))return;let r=e.body,a=typeof r?.licenseKey=="string"?r.licenseKey:"";if(!a.trim()){nr(n,400,"LICENSE_KEY_REQUIRED","licenseKey is required");return}let o=Dp(t,e,n,r?.provider);if(!o)return;let s=typeof r?.pluginClientId=="string"?r.pluginClientId:typeof e.query.clientId=="string"?e.query.clientId:void 0,c=typeof r?.deviceId=="string"?r.deviceId:void 0,l={licenseKey:a,provider:o,clientType:i};s&&(l.pluginClientId=s),c&&(l.deviceId=c),t.licenseState.activateGateway(l).then(u=>{t.analyticsManager?.setTier(u.canUsePro?"pro":"basic"),cv(t,{provider:o,tier:u.canUsePro?"pro":"basic",status:u.status,canUsePro:u.canUsePro}),n.json(lv(u,i))}).catch(u=>{g.warn("License activate failed",{error:u instanceof Error?u.message:"unknown_error"}),nr(n,502,"LICENSE_ACTIVATE_FAILED",u instanceof Error?u.message:"License activation failed")})}function zp(t,e,n,i="plugin"){if(!jp(t,n))return;let r=Dp(t,e,n);if(r)try{let a=t.licenseState.getStatus(r);t.analyticsManager?.setTier(a.canUsePro?"pro":"basic"),n.json(lv(a,i))}catch(a){g.warn("License status check failed",{error:a instanceof Error?a.message:"unknown_error"}),nr(n,502,"LICENSE_STATUS_FAILED",a instanceof Error?a.message:"License status check failed")}}function Mp(t,e,n,i="plugin"){if(!jp(t,n))return;let r=e.body,a=Dp(t,e,n,r?.provider);if(!a)return;let o={provider:a,clientType:i};i==="plugin"&&typeof r?.sessionToken=="string"&&r.sessionToken.trim()&&(o.sessionToken=r.sessionToken.trim()),typeof r?.pluginClientId=="string"&&(o.pluginClientId=r.pluginClientId),typeof r?.deviceId=="string"&&(o.deviceId=r.deviceId),t.licenseState.refreshGateway(o).then(s=>{t.analyticsManager?.setTier(s.canUsePro?"pro":"basic"),cv(t,{provider:a,tier:s.canUsePro?"pro":"basic",status:s.status,canUsePro:s.canUsePro}),n.json(lv(s,i))}).catch(s=>{g.warn("License refresh failed",{error:s instanceof Error?s.message:"unknown_error"}),nr(n,502,"LICENSE_REFRESH_FAILED",s instanceof Error?s.message:"License refresh failed")})}function Lp(t,e,n,i="plugin"){if(!jp(t,n))return;let r=e.body,a=Dp(t,e,n,r?.provider);a&&t.licenseState.resetGateway({provider:a,clientType:i}).then(o=>{t.analyticsManager?.setTier("basic"),cv(t,{provider:a,tier:"basic",status:o.status,canUsePro:o.canUsePro,cleared:!0}),n.json(lv(o,i))}).catch(o=>{g.warn("License reset failed",{error:o instanceof Error?o.message:"unknown_error"}),nr(n,502,"LICENSE_RESET_FAILED",o instanceof Error?o.message:"License reset failed")})}function NI(t,e,n){if(!jp(t,n))return;let i=e.body,r=Dp(t,e,n,i?.provider);if(!r)return;if(i?.clientType!=="plugin"){nr(n,400,"LICENSE_CLIENT_TYPE_INVALID","clientType must be plugin");return}let a=typeof i?.sessionToken=="string"&&i.sessionToken.trim()?i.sessionToken.trim():void 0,o=Noe(i?.snapshot)?i.snapshot:void 0;if(!o&&!a){nr(n,400,"LICENSE_BOOTSTRAP_REQUIRED","snapshot or sessionToken is required");return}let s={provider:r,clientType:"plugin"};o&&(s.snapshot=o),a&&(s.sessionToken=a),t.licenseState.bootstrap(s).then(c=>{t.analyticsManager?.setTier(c.canUsePro?"pro":"basic"),cv(t,{provider:r,tier:c.canUsePro?"pro":"basic",status:c.status,canUsePro:c.canUsePro}),n.json({ok:!0})}).catch(c=>{g.warn("License bootstrap failed",{error:c instanceof Error?c.message:"unknown_error"}),nr(n,502,"LICENSE_BOOTSTRAP_FAILED",c instanceof Error?c.message:"License bootstrap failed")})}function r6(t){t.app.post("/license/bootstrap",(e,n)=>NI(t,e,n)),t.app.post("/license/activate",(e,n)=>Ap(t,e,n,"plugin")),t.app.post("/license/refresh",(e,n)=>Mp(t,e,n,"plugin")),t.app.post("/license/reset",(e,n)=>Lp(t,e,n,"plugin")),t.app.get("/license/status",(e,n)=>zp(t,e,n,"plugin"))}It();oe();import{promises as uv}from"fs";import DI from"path";function Doe(t){let e=t.config.appDataDir??nn();return DI.join(e,"observability","logs")}async function _c(t,e,n){let i=e.query.clientId;if(i&&t.pluginClients.has(i)){let r=t.pluginClients.get(i);r.lastSeen=Date.now()}try{let r=e.body;if(!r||!Array.isArray(r.logs)){g.warn("Invalid logs request",{body:r}),n.status(400).json({error:"Invalid request body"});return}let a=Doe(t);await uv.mkdir(a,{recursive:!0});let c=`plugin-${new Date().toISOString().split("T")[0]}.log`,l=DI.join(a,c),u=DI.join(a,"current.log"),p=r.logs.map(d=>`[${new Date(d.timestamp*1e3).toISOString().replace("T"," ").substring(0,19)}] [${d.level}] ${d.message}`).join(`
|
|
139
|
+
`),await nc(h)}catch(h){g.warn("Failed to refresh root representative sourcemap after full sync",{placeId:e,error:h instanceof Error?h.message:String(h)})}this.ctx.startFileWatcherForPlace(r).catch(h=>{g.warn("Failed to start reverse detector after full sync",{placeId:e,error:h instanceof Error?h.message:String(h)}),r.fileWatcher=null}),r.writer.setBootstrapMode(!1),r.writer.appendChangeLog(`FULL_SYNC_COMPLETE instances=${r.instanceCount} scripts=${r.scriptCount}`),r.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncComplete",direction:"forward",source:"studio",path:`place_${e}`,details:`instances:${r.instanceCount} scripts:${r.scriptCount}`}),g.info("Full sync completed",{placeId:e,instanceCount:r.instanceCount,scriptCount:r.scriptCount}),i.status(200).json({status:"completed",instanceCount:r.instanceCount,scriptCount:r.scriptCount,syncRoot:this.ctx.config.getPlaceRoot(e)})}catch(f){throw l&&l.endFullSyncReplacement(),f}}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 i=setTimeout(async()=>{g.warn("Incomplete sync TTL expired, cleaning up",{placeId:e.placeId,syncId:n});let r=this.ctx.config.getPlaceRoot(e.placeId),a=Ie.join(r,`explorer_tmp_${n}`);try{await fn.rm(a,{recursive:!0,force:!0})}catch(o){g.error("Failed to clean up expired temp dir",o instanceof Error?o:new Error(String(o)))}e.incompleteSyncTimer=null,e.activeFullSyncSessionId===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))},$2);i&&typeof i=="object"&&"unref"in i&&i.unref(),e.incompleteSyncTimer=i}getOrCreatePendingServiceTree(e,n){let i=this.pendingServiceTrees.get(e);i||(i=new Map,this.pendingServiceTrees.set(e,i));let r=i.get(n.serviceName);if(r)return r;let a={serviceName:n.tree?.name??n.serviceName,serviceClassName:n.tree?.className??n.serviceClassName,zeroBasedChunkIndex:n.chunkIndex===0,instances:[]};return i.set(n.serviceName,a),a}isLastChunk(e,n,i){return i<=1?!0:e.zeroBasedChunkIndex?n>=i-1:n>=i}buildServiceTree(e,n){let i={name:n.serviceName,className:n.serviceClassName,childCount:0,children:[],syncedAt:new Date().toISOString()};for(let r of n.instances){let a=this.resolveEffectiveSegments(e,r.effectivePath),o=Pt(r.originalPath);this.upsertTreeNode(i,a,o,r.className)}return this.recomputeTreeChildCounts(i),i.syncedAt=new Date().toISOString(),i}resolveEffectiveSegments(e,n){return e.tmpIndex?Pt(n).map(i=>e.tmpIndex.sanitizeName(i)):Pt(n)}rewritePendingEffectivePaths(e,n,i,r){let a=Ie.relative(n,i).split(Ie.sep).filter(l=>l.length>0),o=Ie.relative(n,r).split(Ie.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=et(["game",...a]),c=et(["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,i,r){if(n.length<=1)return;let a=e.children;for(let o=1;o<n.length;o++){let s=n[o],c=i[o],l=o===n.length-1,u=a.find(p=>p.name===s);u?l&&(u.className=r,c!==void 0&&c!==s&&(u.originalName=c)):(u={name:s,className:l?r:"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=i=>{let r=i.children??[];i.children=r;for(let a of r)n(a);i.childCount=r.length};for(let i of e.children)n(i);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 fn.readdir(e,{withFileTypes:!0});for(let i of n)if(i.isDirectory()){if(i.name.startsWith("explorer_tmp_")){let r=Ie.join(e,i.name);g.warn("Removing stale temp directory from crashed sync",{dir:i.name});try{await fn.rm(r,{recursive:!0,force:!0})}catch(a){g.error(`Failed to remove stale temp dir: ${i.name}`,a instanceof Error?a:new Error(String(a)))}}if(i.name.startsWith("place_")){let r=Ie.join(e,i.name);try{let a=await fn.readdir(r,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=Ie.join(r,o.name);g.warn("Removing stale temp directory from crashed sync",{dir:`${i.name}/${o.name}`});try{await fn.rm(s,{recursive:!0,force:!0})}catch(c){g.error(`Failed to remove stale temp dir: ${i.name}/${o.name}`,c instanceof Error?c:new Error(String(c)))}}}catch{continue}}}}catch(n){if(n.code==="ENOENT")return;g.warn("Failed to scan for stale temp dirs",{error:n instanceof Error?n.message:String(n)})}}};import er from"path";import{promises as Ky}from"fs";oe();var Xy=class{constructor(e){this.ctx=e}async handleReversePending(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({pending:0,hasConflicts:!1,lastDetected:null});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}let a={pending:r.fileWatcher?.getPendingCount()??0,hasConflicts:!1,lastDetected:r.fileWatcher?.getLastDetected()??null,forwardRestoreNeeded:r.forwardRestoreQueue.length};this.ctx.touchRuntimePlace(i),n.status(200).json(a)}async handleReverseSyncChanges(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({changes:[],count:0});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}if(r.state!=="syncing"){n.status(400).json({error:"Not syncing",message:"Reverse sync is only available when sync is active"});return}let a=r.fileWatcher?.drainPendingChanges()??[],o=a.length>0?await r.reader.buildChangesFromPending(a):[];this.ctx.touchRuntimePlace(i),n.status(200).json({changes:o,count:o.length})}async handleReverseSyncResult(e,n){let i=e.body,r=i.placeId??this.ctx.getDefaultRuntimePlaceId();if(r==null){n.status(400).json({error:"Validation error",message:"placeId is required (in body or via active sync session)"});return}let a=i.appliedFiles??i.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(r);if(!o){n.status(404).json({error:"Place not found",message:`No sync context for place ${r}`});return}this.ctx.touchRuntimePlace(r);let s=this.ctx.config.getPlaceRoot(r),c=0,l=[];for(let u of a){let p=er.resolve(s,u);if(!Hy(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await Ky.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=Et(m),y=Ft(m);y&&h&&await o.writer.removeFromTree(y,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",source:"local",path:`place_${r}`,details:`applied:${c} failed:${l.length}`}),n.status(200).json({updated:c,failed:l.length,errors:l})}async handleResolveConflict(e,n){let i=e.body;if(!i.fsPath||!i.resolution){n.status(400).json({error:"Validation error",message:"fsPath and resolution are required"});return}let{fsPath:r,resolution:a}=i,o=this.ctx.config.getSyncRoot();if(!Hy(o,er.resolve(o,r))){n.status(403).json({error:"Forbidden",message:"Path is outside the sync root"});return}if(a==="skip"){n.status(200).json({status:"skipped",fsPath:r});return}let s;if(i.placeId&&(s=this.ctx.places.get(i.placeId)),!s){let d=r.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=er.resolve(c,r);if(!Hy(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"){if(typeof i.studioContent!="string"){n.status(400).json({error:"Validation error",message:"studioContent is required for apply-studio resolution"});return}await Ky.mkdir(er.dirname(l),{recursive:!0});let d=i.studioContent,f=u.computeHash(d);s.fileWatcher?.suppressPath(l,f),await Ky.writeFile(l,d,"utf-8"),u.updateHashByValue(l,f),u.updateFileHashByValue(l,f),await u.saveToDisk(),n.status(200).json({status:"resolved",resolution:"apply-studio",fsPath:r});return}if(a==="apply-file"){let d=await Ky.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:r,instancePath:h,fileType:m,content:d});return}n.status(400).json({error:"Invalid resolution",message:`Unknown resolution: ${a}`})}async handleReverseRescan(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({added:0});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}if(!r.fileWatcher){n.status(200).json({added:0});return}let a=await r.fileWatcher.rescan();this.ctx.touchRuntimePlace(i),g.info("Reverse rescan completed",{placeId:i,added:a}),n.status(200).json({added:a})}resolveInstancePathForAppliedPath(e,n){let i=e.resolveInstancePathFromFsPath(n);if(i)return i;let r=e.getExplorerRoot(),a=er.relative(r,n);if(a.startsWith("..")||a===""||er.isAbsolute(a))return null;let o=a.split(er.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=er.basename(n),c=er.dirname(n),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(Iy.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=r;for(let f of o){d=er.join(d,f);let m=er.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return et(p)}};oe();function D2(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={})}function doe(t){return t.type==="instanceAdded"||t.type==="instanceRemoved"||t.type==="instanceRenamed"||t.type==="instanceMoved"||t.type==="scriptSourceChanged"}var wr=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;reverseDetectorFactory;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;currentConnectedPlaceId=null;currentConnectedPlaceName=null;constructor(e,n={}){this.config=new wy(e,n),this.reverseDetectorFactory=n.reverseDetectorFactory??(i=>new qy(i.explorerRoot,i.syncIndex,{placeId:i.placeId})),this.apiHandler=new Gy(this),this.changeProcessor=new Vy(this),this.initHandler=new Jy(this),this.reverseHandler=new Xy(this),this.places=new Uy({max:3,dispose:(i,r)=>{g.info("Disposing place context (LRU eviction)",{placeId:r}),this.activeFullSyncPlaceId===r&&(this.activeFullSyncPlaceId=null),i.fileWatcher&&(g.info("Disposing place context: stopping reverse detector",{placeId:r,reason:"lru-dispose",mode:Je}),i.fileWatcher.stop("lru-dispose").catch(a=>{g.error("Error stopping reverse detector during dispose",a)}),i.fileWatcher=null),i.writer.stopChangeLogFlusher().catch(a=>{g.error("LRU dispose \uC911 changelog flush \uC2E4\uD328",a)}),i.incompleteSyncTimer&&(clearTimeout(i.incompleteSyncTimer),i.incompleteSyncTimer=null),i.index.clearAllMaps(),i.index.saveToDisk().catch(a=>{g.error("Error saving index during dispose",a)}),i.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(i.activeFullSyncSessionId),i.tmpWriter&&(i.tmpWriter.stopChangeLogFlusher(),i.tmpWriter=null),i.tmpIndex=null,i.collisionDirMap=null}})}getSyncRoot(){return this.config.getSyncRoot()}async getOrCreatePlaceContext(e,n){let i=this.places.get(e);if(i&&n){let r=this.config.getPlaceRoot(e),a=await this.config.resolvePlaceRoot(e,n);a!==r&&(g.info("Place root migrated, recreating context",{placeId:e,from:r,to:a}),this.places.delete(e),i=void 0)}if(!i){let r=await this.config.resolvePlaceRoot(e,n),a=xI.join(r,"explorer");await gc.mkdir(r,{recursive:!0}),await gc.mkdir(a,{recursive:!0});let o=new qi(r,a);await o.loadFromDisk();let s=new fc(this.config,o,e),c=new Fy(this.config,o,r);s.startChangeLogFlusher(),i={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:{...Va},applyModes:{...Wa},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null},this.places.set(e,i),g.info("Created new place context",{placeId:e,placeRoot:r})}return i}async handleSyncInit(e,n){try{D2(e.body);let i=R2(e.body);if(!i.success){n.status(400).json({error:"Validation error",message:i.error});return}let r=i.data,a=r.phase==="start"?r.placeId??null:r.phase==="chunk"||r.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(r.phase){case"start":this.setCurrentConnectedPlace(a,r.placeName),await this.initHandler.handleInitStart(a,r,n);break;case"chunk":await this.initHandler.handleInitChunk(a,r,n);break;case"complete":await this.initHandler.handleInitComplete(a,r,n);break}}catch(i){this.sendError(n,i,"handleSyncInit")}}async handleSyncUpdate(e,n){try{D2(e.body);let i=O2(e.body);if(!i.success){n.status(400).json({error:"Validation error",message:i.error});return}let r=i.data,a=r.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(this.setCurrentConnectedPlace(a,o.placeName||this.currentConnectedPlaceName),o.activeFullSyncSessionId!==null||o.state==="initializing"){n.status(409).json({error:"Conflict",message:`Full sync in progress for place ${a}`});return}o.activeClientId=r.clientId,this.touchRuntimePlace(a);let s=E2(o,r.changes);g.info("Sync update received",{placeId:a,clientId:r.clientId,changeCount:s.length,receivedCount:r.changes.length,types:s.map(m=>m.type)});let c=[],l=[],u=0,p=!1,d=new Map;for(let m of s)try{let h=await this.changeProcessor.processChangeForPlace(o,m,d);h?l.push(h):(u++,doe(m)&&(p=!0))}catch(h){let y="path"in m?m.path:"oldPath"in m?m.oldPath:"unknown";c.push({path:y,error:h instanceof Error?h.message:String(h)})}for(let m of d.values())try{await By(o,m)}catch(h){c.push({path:m.instancePath,error:h instanceof Error?h.message:String(h)})}if(p)try{let m=this.config.getSyncRoot(),h=this.config.getPlaceRoot(a);await Ey(m,h),await nc(m)}catch(m){g.warn("Failed to refresh sourcemaps after incremental sync update",{placeId:a,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=T2&&(await o.index.saveToDisk(),o.changesSinceLastSave=0),o.lastIncrementalSync=new Date().toISOString();let f={processed:u,failed:c.length,errors:c,syncedAt:o.lastIncrementalSync};l.length>0&&(f.conflicts=l),n.status(200).json(f)}catch(i){this.sendError(n,i,"handleSyncUpdate")}}getDefaultRuntimePlaceId(){return this.currentConnectedPlaceId}getDefaultRuntimePlaceName(){if(this.currentConnectedPlaceName)return this.currentConnectedPlaceName;if(this.currentConnectedPlaceId!==null&&this.currentConnectedPlaceId!==void 0){let e=this.places.get(this.currentConnectedPlaceId);if(e?.placeName)return e.placeName}return null}setCurrentConnectedPlace(e,n){if(this.currentConnectedPlaceId=e,e==null){this.currentConnectedPlaceName=null;return}if(typeof n=="string"&&n.length>0){this.currentConnectedPlaceName=n;let r=this.places.get(e);r&&(r.placeName=n);return}let i=this.places.get(e);i?.placeName&&(this.currentConnectedPlaceName=i.placeName)}clearCurrentConnectedPlaceIfMatch(e){this.currentConnectedPlaceId===e&&(this.currentConnectedPlaceId=null,this.currentConnectedPlaceName=null)}touchRuntimePlace(e){this.activeRuntimeSyncPlaceId=e}clearRuntimePlaceIfMatch(e){this.activeRuntimeSyncPlaceId===e&&(this.activeRuntimeSyncPlaceId=null)}resolveQueryPlaceId(e,n="runtime"){let i=e.query.placeId;if(i){let r=parseInt(i,10);if(!isNaN(r))return r}return n==="full"?this.activeFullSyncPlaceId:this.getDefaultRuntimePlaceId()}async handleSyncStatus(e,n){try{let i=u=>({state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:u,activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0,reverseDetectorActive:!1,reverseDetectorMode:Je,fileWatcherActive:!1}),r=this.resolveQueryPlaceId(e);if(r==null){let u=i(this.config.getSyncRoot());n.status(200).json(u);return}let a=this.places.get(r);if(!a){let u=i(this.config.getPlaceRoot(r));n.status(200).json(u);return}let o=a.fileWatcher?.getPendingCount()??0,s=a.fileWatcher?.isActivelyScanning()??!1,c=a.fileWatcher!==null;this.touchRuntimePlace(r);let l={state:a.state,instanceCount:a.instanceCount,scriptCount:a.scriptCount,lastFullSync:a.lastFullSync,lastIncrementalSync:a.lastIncrementalSync,syncRoot:this.config.getPlaceRoot(r),activeClientId:a.activeClientId,reverseSyncAvailable:o>0,modifiedFileCount:o,applyModes:a.applyModes,directions:a.directions,reverseDetectorActive:s,reverseDetectorMode:Je,fileWatcherActive:c,forwardOnlyClasses:[...tc]};n.status(200).json(l)}catch(i){this.sendError(n,i,"handleSyncStatus")}}async handleSyncStop(e,n){try{let i=e.body,r=i.placeId??this.getDefaultRuntimePlaceId();if(g.info("handleSyncStop requested",{placeId:r,clientId:i.clientId??null,reason:i.reason??"requested"}),r==null){n.status(200).json({status:"idle",state:"idle",placeId:null,message:"No active sync place"});return}let a=this.places.get(r);if(!a){g.info("handleSyncStop no place context",{placeId:r,clientId:i.clientId??null}),n.status(200).json({status:"idle",state:"idle",placeId:r,message:`No sync context for place ${r}`});return}if(i.clientId&&a.activeClientId&&i.clientId!==a.activeClientId&&(a.activeFullSyncSessionId!==null||a.state==="syncing"||a.state==="initializing")){g.warn("handleSyncStop rejected due to active client mismatch",{placeId:r,requestedClientId:i.clientId,activeClientId:a.activeClientId,state:a.state,activeFullSyncSessionId:a.activeFullSyncSessionId}),n.status(409).json({error:"Conflict",message:`This sync session belongs to client ${a.activeClientId}`});return}let o=a.activeFullSyncSessionId,s=a.state,c=a.fileWatcher!==null;if(o&&(this.initHandler.clearPreserveLocalFiles(o),this.initHandler.clearPendingServiceTrees(o)),a.fileWatcher&&(g.info("handleSyncStop suspending reverse detector",{placeId:r,state:a.state,activeClientId:a.activeClientId,mode:Je}),a.fileWatcher.beginFullSyncReplacement()),a.incompleteSyncTimer&&(clearTimeout(a.incompleteSyncTimer),a.incompleteSyncTimer=null),o){let l=this.config.getPlaceRoot(r),u=xI.join(l,`explorer_tmp_${o}`);await gc.rm(u,{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,a.forwardRestoreQueue=[],this.activeFullSyncPlaceId===r&&(this.activeFullSyncPlaceId=null),this.clearRuntimePlaceIfMatch(r),g.info("handleSyncStop responding idle",{placeId:r,previousState:s,activeFullSyncSessionId:o,hadWatcher:c}),g.info("Sync stopped",{placeId:r,reason:i.reason??"requested"}),n.status(200).json({status:"stopped",state:"idle",placeId:r})}catch(i){this.sendError(n,i,"handleSyncStop")}}async initialize(){try{await this.config.loadFromMeta(),await this.initHandler.cleanupStaleTempDirs(),g.info("SyncController initialized")}catch(e){g.error("SyncController initialization failed",e instanceof Error?e:new Error(String(e)))}}async shutdown(){try{g.info("SyncController shutdown: clearing place contexts",{placeCount:this.places.size}),this.places.clear(),g.info("SyncController shut down")}catch(e){g.error("SyncController shutdown error",e instanceof Error?e:new Error(String(e)))}}async atomicWriteFile(e,n){let i=e+".tmp."+poe().slice(0,8);try{await gc.writeFile(i,n,"utf-8"),await gc.rename(i,e)}catch(r){throw await gc.unlink(i).catch(()=>{}),r}}async handlePreCheck(e,n){try{await this.apiHandler.handlePreCheck(e,n)}catch(i){this.sendError(n,i,"handlePreCheck")}}async handleSyncDirections(e,n){try{await this.apiHandler.handleSyncDirections(e,n)}catch(i){this.sendError(n,i,"handleSyncDirections")}}async handleForwardRestoreList(e,n){try{await this.apiHandler.handleForwardRestoreList(e,n)}catch(i){this.sendError(n,i,"handleForwardRestoreList")}}async handleReversePending(e,n){try{await this.reverseHandler.handleReversePending(e,n)}catch(i){this.sendError(n,i,"handleReversePending")}}async handleReverseSyncChanges(e,n){try{await this.reverseHandler.handleReverseSyncChanges(e,n)}catch(i){this.sendError(n,i,"handleReverseSyncChanges")}}async handleReverseSyncResult(e,n){try{await this.reverseHandler.handleReverseSyncResult(e,n)}catch(i){this.sendError(n,i,"handleReverseSyncResult")}}async handleResolveConflict(e,n){try{await this.reverseHandler.handleResolveConflict(e,n)}catch(i){this.sendError(n,i,"handleResolveConflict")}}async handleReverseRescan(e,n){try{await this.reverseHandler.handleReverseRescan(e,n)}catch(i){this.sendError(n,i,"handleReverseRescan")}}async handleSyncHistory(e,n){try{await this.apiHandler.handleSyncHistory(e,n)}catch(i){this.sendError(n,i,"handleSyncHistory")}}async startFileWatcherForPlace(e){if(e.state!=="syncing"){g.debug("Skipping reverse detector start - place not syncing",{placeId:e.placeId,state:e.state});return}if(e.fileWatcher){if(e.fileWatcher.isActivelyScanning()){g.info("startFileWatcherForPlace: reusing active reverse detector",{placeId:e.placeId,mode:Je});return}g.info("startFileWatcherForPlace: resuming paused reverse detector",{placeId:e.placeId,mode:Je}),e.fileWatcher.endFullSyncReplacement();return}let n=this.createReverseChangeDetector(e);this.configureReverseChangeDetector(e,n),e.fileWatcher=n,n.start().then(()=>{e.fileWatcher===n&&g.info("Reverse detector started for reverse sync",{placeId:e.placeId,mode:Je})}).catch(i=>{g.warn("Failed to start reverse detector",{placeId:e.placeId,mode:Je,error:i instanceof Error?i.message:String(i)}),e.fileWatcher===n&&(e.fileWatcher=null)})}createReverseChangeDetector(e){return this.reverseDetectorFactory({explorerRoot:xI.join(this.config.getPlaceRoot(e.placeId),"explorer"),syncIndex:e.index,placeId:e.placeId})}configureReverseChangeDetector(e,n){e.writer.setOnWriteCallback(i=>{n.suppressPath(i)}),n.setDirectionChecker(i=>{let r=mL(i);return e.directions[r]}),n.setOnForwardViolation(i=>{e.forwardRestoreQueue.includes(i)||(e.forwardRestoreQueue.push(i),g.info("Forward violation queued for restore",{placeId:e.placeId,relativePath:i,queueSize:e.forwardRestoreQueue.length}))})}getStatusSummary(){return this.apiHandler.getStatusSummary()}getDirectionForCategory(e){return this.apiHandler.getDirectionForCategory(e)}getStatusDirect(e){return this.apiHandler.getStatusDirect(e)}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,i){await this.apiHandler.writeSyncedFile(e,n,i)}async executeViaDisk(e,n){return this.apiHandler.executeViaDisk(e,n)}sendError(e,n,i){let r=n instanceof Error?n.message:String(n);if(r.includes("Path traversal detected")){e.status(403).json({error:"Forbidden",message:r});return}let a=n.code;if(a==="ENOSPC"||a==="EPERM"||a==="EACCES"){e.status(500).json({error:"Disk error",message:r});return}g.error(`SyncController.${i} failed`,n instanceof Error?n:new Error(r)),e.status(500).json({error:"Internal error",message:`${i}: ${r}`})}};import{randomUUID as foe}from"crypto";function j2(t,e){let n=foe(),i=n.replace(/-/g,"").substring(0,8).toUpperCase(),r=`${i.substring(0,4)}-${i.substring(4,8)}`;return{config:t,app:e,instanceId:n,sessionId:r,startTime:Date.now(),baseUrl:`http://${t.httpHost}:${t.httpPort}`,commandQueue:new Map,pendingCommands:new Map,globalPendingCommands:[],totalCommandsProcessed:0,pluginClients:new Map,pluginCommandSessionsByClientId:new Map,pluginCommandClientIdsByProcessToken:new Map,mcpInstances:new Map,cachedSelectionMap:new Map,isClientMode:!1,clientModeHealthTimer:null,clientModeConsecutiveHealthFailures:0,clientModeUpstreamReachable:!0,clientModeUpstreamContextCaptureEnabled:!0,clientModeLastHealthSuccessAt:null,clientModeLastHealthFailureAt:null,clientModeLastHealthError:null,onUpstreamPermanentlyDown:null,clientModeIdleTimeoutMs:t.clientModeIdleTimeoutMs??36e5,clientModeIdleWatchdogTimer:null,shutdownFn:null,historyManager:null,analyticsManager:null,executionContextManager:null,licenseState:null,syncController:null,internalCommandExecutor:null,dashboardFolderPicker:null,activeSyncOwnerInstanceId:null,activeProjectRoot:null,pendingDashboardSyncRootPin:null,playtestControlCommand:null,dashboardSyncRootSwitchInFlight:!1,aiClientName:"",pluginVersion:"",syncedSessionToken:null,serverLastCommandAt:null}}var A2=Rt(Qs(),1);oe();function z2(t){let e=A2.default.json({limit:"5mb"});t.app.use((n,i,r)=>{if(n.path.startsWith("/sync/")){r();return}e(n,i,r)}),t.app.use((n,i,r)=>{i.setHeader("Access-Control-Allow-Origin","http://localhost:3002"),i.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),i.setHeader("Access-Control-Allow-Headers","Content-Type"),r()}),t.app.use((n,i,r)=>{g.debug(`${n.method} ${n.path}`,{ip:n.ip}),r()})}gp();import{randomUUID as e6}from"crypto";oe();function moe(){let t=process.env.WEPPY_ROBLOX_MCP_VERSION?.trim();return t||null}var Ke=moe()??"2.5.1";Rp();function ev(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 B2(t){let n=Date.now();for(let[i,r]of t.mcpInstances)r.lastSeen&&n-r.lastSeen>15e3&&(t.mcpInstances.delete(i),r.sessionId&&t.executionContextManager?.endSession(r.sessionId),g.debug("Removed stale MCP instance",{instanceId:i,lastSeen:r.lastSeen}))}function Z2(t,e,n){let i=e.query.clientId;if(i&&t.pluginClients.has(i)){let r=t.pluginClients.get(i);r.lastSeen=Date.now()}try{let r=e.body;if(!r||!Array.isArray(r.selection)||typeof r.count!="number"){g.warn("Invalid selection update request",{body:r}),n.status(400).json({error:"Invalid request body"});return}let a=i||"unknown",o=Date.now();t.cachedSelectionMap.set(a,{selection:r.selection,count:r.count,timestamp:o,clientId:a}),g.debug("Selection cache updated",{count:r.count,clientId:a,timestamp:o}),n.json({status:"ok",timestamp:o})}catch(r){g.error("Error handling selection update",r),n.status(500).json({error:"Internal server error"})}}function H2(t,e,n){let i=parseInt(e.query.maxAge)||3e4,r=Op(t,i);r?n.json({cached:!0,...r}):n.json({cached:!1,message:"No cached selection available"})}function Op(t,e=3e4,n){if(t.cachedSelectionMap.size===0)return null;let i;if(n)i=t.cachedSelectionMap.get(n);else for(let a of t.cachedSelectionMap.values())(!i||a.timestamp>i.timestamp)&&(i=a);if(!i)return null;let r=Date.now()-i.timestamp;return e===0||r<=e?i:null}function EI(t,e){let n=t.pluginClients.get(e.clientId),i=Date.now(),r={clientId:e.clientId,projectName:e.projectName,placeName:e.placeName,placeId:e.placeId,pluginVersion:e.pluginVersion,connectedAt:n?.connectedAt||i,lastSeen:i,commandsProcessed:n?.commandsProcessed||0,connectionType:n?.connectionType||"polling",inFlightRequestId:n?.inFlightRequestId,...n?.commandSessionState?{commandSessionState:n.commandSessionState}:{},...n?.processToken?{processToken:n.processToken}:{}};return t.pluginClients.set(e.clientId,r),t.syncController&&typeof t.syncController.setCurrentConnectedPlace=="function"&&typeof e.placeId=="number"&&Number.isFinite(e.placeId)&&t.syncController.setCurrentConnectedPlace(e.placeId,e.placeName??null),t.pendingCommands.has(e.clientId)||t.pendingCommands.set(e.clientId,[]),e.pluginVersion&&(t.pluginVersion=e.pluginVersion),typeof r.placeId=="number"&&Number.isFinite(r.placeId)&&QC(t,r.placeId,r.placeName??null),t.analyticsManager&&(e.pluginVersion&&t.analyticsManager.setPluginVersion(e.pluginVersion),t.analyticsManager.trackPluginConnected()),g.info("Plugin client registered",{clientId:e.clientId,projectName:e.projectName,placeName:e.placeName,isReconnect:!!n}),{clientInfo:r,sessionId:t.sessionId,mcpVersion:Ke,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:ev(t)}}function $I(t,e){let n=t.pluginClients.get(e),i=t.pluginClients.has(e);if(t.pluginClients.delete(e),t.pendingCommands.delete(e),i&&t.syncController&&typeof t.syncController.clearCurrentConnectedPlaceIfMatch=="function"&&typeof n?.placeId=="number"&&Number.isFinite(n.placeId)){let r=[...t.pluginClients.values()].filter(a=>typeof a.placeId=="number"&&Number.isFinite(a.placeId)).sort((a,o)=>o.lastSeen-a.lastSeen)[0];r?t.syncController.setCurrentConnectedPlace?.(r.placeId??null,r.placeName??null):t.syncController.clearCurrentConnectedPlaceIfMatch(n.placeId)}return i&&(Qe(t,"connection",{clientId:e,status:"disconnected"}),oc(t,{timestamp:new Date().toISOString(),type:"plugin",status:"disconnected",clientId:e,message:`Plugin disconnected \u2014 ${e}`})),g.info("Plugin client unregistered",{clientId:e,existed:i}),i}function tv(t,e,n){try{let i=e.body;if(!i.clientId){n.status(400).json({error:"Missing clientId"});return}let r=EI(t,{clientId:i.clientId,projectName:i.projectName,placeName:i.placeName,placeId:i.placeId,pluginVersion:i.pluginVersion});n.json({status:"ok",clientId:i.clientId,serverInstanceId:t.instanceId,sessionId:r.sessionId,mcpVersion:r.mcpVersion,connectedAt:r.clientInfo.connectedAt,aiClientNames:r.aiClientNames,serverStartTime:t.startTime,mcpInstanceCount:r.mcpInstanceCount})}catch(i){g.error("Error registering plugin client",i),n.status(500).json({error:"Internal server error"})}}function nv(t,e,n){let i=e.body?.clientId;if(!i){n.status(400).json({error:"Missing clientId"});return}let r=$I(t,i);n.json({status:"ok",existed:r})}function V2(t,e,n){try{let i=e.body;if(!i.instanceId){n.status(400).json({error:"Missing instanceId"});return}let r=Date.now(),a={instanceId:i.instanceId,...typeof i.sessionId=="string"?{sessionId:i.sessionId}:{},pid:i.pid,connectedAt:r,isServer:!1,lastSeen:r};i.aiClientName&&(a.aiClientName=i.aiClientName),i.cwd&&(a.cwd=i.cwd),"projectRoot"in i&&(a.projectRoot=i.projectRoot),t.mcpInstances.set(i.instanceId,a),Qe(t,"mcp_status",{aiClientName:a.aiClientName??"Unknown",instanceId:i.instanceId,status:"registered"}),oc(t,{timestamp:new Date().toISOString(),type:"mcp",status:"registered",instanceId:i.instanceId,message:`MCP registered \u2014 ${a.aiClientName??i.instanceId}`,...a.aiClientName?{aiClientName:a.aiClientName}:{}}),g.info("MCP instance registered (client mode)",{instanceId:i.instanceId,pid:i.pid,cwd:i.cwd}),n.json({status:"ok",instanceId:i.instanceId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Ke,mcpInstanceCount:t.mcpInstances.size+1})}catch(i){g.error("Error registering MCP instance",i),n.status(500).json({error:"Internal server error"})}}function W2(t,e,n){let i=e.body?.instanceId;if(!i){n.status(400).json({error:"Missing instanceId"});return}let r=t.mcpInstances.get(i),a=!!r;t.mcpInstances.delete(i),r?.sessionId&&t.executionContextManager?.endSession(r.sessionId),a&&(Qe(t,"mcp_status",{aiClientName:r?.aiClientName??"Unknown",instanceId:i,status:"unregistered"}),oc(t,{timestamp:new Date().toISOString(),type:"mcp",status:"unregistered",instanceId:i,message:`MCP unregistered \u2014 ${r?.aiClientName??i}`,...r?.aiClientName?{aiClientName:r.aiClientName}:{}})),g.info("MCP instance unregistered",{instanceId:i,existed:a}),n.json({status:"ok",existed:a})}function G2(t,e,n){let{instanceId:i,aiClientName:r}=e.body;if(i&&r){let a=t.mcpInstances.get(i);a&&(a.aiClientName=r)}n.json({status:"ok"})}function J2(t){let e=3e4,n=Date.now(),i=Jn({appDataDir:t.config.appDataDir});for(let[r,a]of t.pluginClients)n-a.lastSeen>e&&(t.pluginClients.delete(r),t.pendingCommands.delete(r));return B2(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Ke,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:i,lastSeen:n,...t.aiClientName?{aiClientName:t.aiClientName}:{}},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function rv(t,e){e.json(J2(t))}function iv(t,e,n){let i=e.query.instanceId;i&&t.mcpInstances.has(i)&&(t.mcpInstances.get(i).lastSeen=Date.now()),B2(t);let r=Ja(t),s={...{status:"online",connectedClients:r.length,queuedCommands:qL(t),uptime:Date.now()-t.startTime,version:Ke,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:ev(t),pluginVersion:t.pluginVersion||void 0,dashboardSseClients:t.dashboardSseClients?.size??0,websocketClients:r.length,pluginClients:r.map(c=>({clientId:c.clientId,projectName:c.projectName,placeName:c.placeName,pluginVersion:c.pluginVersion,connectionType:c.connectionType,commandSessionState:c.commandSessionState,lastSeen:c.lastSeen})),...t.isClientMode?{upstream:{reachable:t.clientModeUpstreamReachable,consecutiveFailures:t.clientModeConsecutiveHealthFailures,lastSuccessAt:t.clientModeLastHealthSuccessAt,lastFailureAt:t.clientModeLastHealthFailureAt,lastError:t.clientModeLastHealthError,baseUrl:t.baseUrl}}:{}};n.json(s)}function K2(t,e,n,i){let r=e.ip||e.socket.remoteAddress||"";if(!(r==="127.0.0.1"||r==="::1"||r==="::ffff:127.0.0.1"||r==="localhost")){g.warn("Shutdown request rejected from non-localhost",{ip:r}),n.status(403).json({error:"Forbidden: localhost only"});return}g.info("Shutdown request received, initiating graceful shutdown",{requestedBy:r,uptime:Date.now()-t.startTime}),n.json({status:"shutting_down",message:"Server will shutdown gracefully",pid:process.pid}),setTimeout(async()=>{try{await i(),g.info("Graceful shutdown completed"),process.exit(0)}catch(o){g.error("Error during graceful shutdown",o),process.exit(1)}},100)}async function av(t){if(t.isClientMode)try{let e=await fetch(`${t.baseUrl}/connection-info`);if(e.ok)return await e.json()}catch(e){g.warn("Failed to fetch connection info from server",{error:e})}return J2(t)}oe();Ep();var Np={query_instances:{discriminator:"action",validActions:new Set(["get","children","find_child","find_descendant","wait_for_child","class_info","search_name","search_class","search_property","search_tag","file_tree","project_structure","descendants","ancestors"]),paramAliases:{query_instances_search_name:{query:"pattern"},query_instances_search_property:{root:"rootPath"},query_instances_search_tag:{root:"rootPath"},query_instances_project_structure:{root:"rootPath"}}},mutate_instances:{discriminator:"action",validActions:new Set(["create","create_with_props","delete","clone","move","rename","pivot","create_tree","mass_create","mass_delete","mass_duplicate","smart_duplicate"]),paramAliases:{mutate_instances_clone:{path:"sourcePath"}}},manage_properties:{discriminator:"action",validActions:new Set(["get","set","get_all","set_multiple","get_attr","set_attr","get_all_attrs","delete_attr","add_tag","remove_tag","check_tag","get_tags","get_tagged","set_calculated","set_relative","mass_set","mass_get","modify_children"]),paramAliases:{manage_properties_get_tagged:{tagName:"tag",root:"rootPath"},manage_properties_set_relative:{amount:"value"}}},manage_scripts:{discriminator:"action",validActions:new Set(["get_source","set_source","create","delete","edit_replace","edit_insert","edit_delete","search","replace","get_dependencies"]),paramAliases:{manage_scripts_edit_replace:{newLines:"newContent"},manage_scripts_edit_insert:{lines:"content"},manage_scripts_replace:{pattern:"searchPattern"}}},manage_lighting:{discriminator:"action",validActions:new Set(["lighting","atmosphere","sky","terrain_props","time"])},manage_selection:{discriminator:"action",validActions:new Set(["get","set","clear","cached","context","details","add","remove","watch"])},manage_camera:{discriminator:"action",validActions:new Set(["info","focus_path","focus_position","suggest"]),paramAliases:{manage_camera_suggest:{path:"targetPath"}}},manage_tween:{discriminator:"action",validActions:new Set(["create","play","pause","cancel"])},manage_audio:{discriminator:"action",validActions:new Set(["play","stop","pause","resume","set_listener"])},manage_animation:{discriminator:"action",validActions:new Set(["load","play","stop","get_tracks"])},manage_physics:{discriminator:"action",validActions:new Set(["register_group","set_collidable","get_groups"])},manage_effects:{discriminator:"action",validActions:new Set(["emit","clear","toggle"])},manage_terrain:{discriminator:"action",validActions:new Set(["fill_block","fill_ball","fill_cylinder","fill_wedge","clear_region","clear_bounds","replace_material","colors_get","colors_set","read_voxel","read_voxels","write_voxels","generate","smooth"])},spatial_query:{discriminator:"action",validActions:new Set(["raycast","find_ground","check_placement","multi_raycast","scan_area","find_flat","find_spawn","analyze_walkable","spatial_map","find_space","bounds","snap_grid","collision"]),paramAliases:{spatial_query_spatial_map:{path:"rootPath"}}},manage_assets:{discriminator:"action",validActions:new Set(["insert","info","search","search_insert","insert_free","insert_package","export"]),paramAliases:{manage_assets_search:{maxResults:"limit"}}},manage_sync:{discriminator:"action",validActions:new Set(["status_current_place","history","directions","read_file","write_file","progress"])},workspace_state:{discriminator:"action",validActions:new Set(["sync","snapshot","changes","viewport","clear_history","metadata","scripts","selection_info","clear_cache"])},manage_logs:{discriminator:"action",validActions:new Set(["get","clear","errors"]),paramAliases:{manage_logs_get:{level:"type"}}},system_info:{discriminator:"action",validActions:new Set(["ping","connection","usage","place_info","services","studio_settings","play","stop","pause","resume","play_status","run_test"])}};var yoe={manage_selection_cached:"internal",manage_sync_status_current_place:"internal",manage_sync_history:"internal",manage_sync_directions:"internal",manage_sync_read_file:"internal",manage_sync_write_file:"internal",manage_sync_progress:"internal",system_info_connection:"internal",system_info_run_test:"internal"};function ov(t){return yoe[t]||"plugin"}var X2=new Set(["batch_execute","execute_luau","extended_call_method","extended_call_methods","extended_get_properties","extended_get_property","extended_set_properties","extended_set_property","manage_animation_get_tracks","manage_animation_load","manage_animation_play","manage_animation_stop","manage_assets_export","manage_assets_info","manage_assets_insert","manage_assets_insert_free","manage_assets_insert_package","manage_assets_search","manage_assets_search_insert","manage_audio_pause","manage_audio_play","manage_audio_resume","manage_audio_set_listener","manage_audio_stop","manage_effects_clear","manage_effects_emit","manage_effects_toggle","manage_lighting_atmosphere","manage_lighting_lighting","manage_lighting_sky","manage_lighting_terrain_props","manage_lighting_time","manage_physics_get_groups","manage_physics_register_group","manage_physics_set_collidable","manage_properties_mass_get","manage_properties_mass_set","manage_properties_modify_children","manage_properties_set_calculated","manage_properties_set_relative","manage_scripts_replace","manage_selection_add","manage_selection_context","manage_selection_details","manage_selection_remove","manage_selection_watch","manage_sync_directions","manage_sync_history","manage_sync_progress","manage_sync_read_file","manage_sync_status_current_place","manage_sync_write_file","manage_terrain_clear_bounds","manage_terrain_clear_region","manage_terrain_colors_get","manage_terrain_colors_set","manage_terrain_fill_ball","manage_terrain_fill_block","manage_terrain_fill_cylinder","manage_terrain_fill_wedge","manage_terrain_generate","manage_terrain_read_voxel","manage_terrain_read_voxels","manage_terrain_replace_material","manage_terrain_smooth","manage_terrain_write_voxels","manage_tween_cancel","manage_tween_create","manage_tween_pause","manage_tween_play","mutate_instances_create_tree","mutate_instances_mass_create","mutate_instances_mass_delete","mutate_instances_mass_duplicate","mutate_instances_smart_duplicate","query_instances_ancestors","query_instances_descendants","query_instances_file_tree","query_instances_project_structure","query_instances_search_property","query_instances_search_tag","spatial_query_analyze_walkable","spatial_query_bounds","spatial_query_check_placement","spatial_query_collision","spatial_query_find_flat","spatial_query_find_ground","spatial_query_find_space","spatial_query_find_spawn","spatial_query_multi_raycast","spatial_query_raycast","spatial_query_scan_area","spatial_query_snap_grid","spatial_query_spatial_map","system_info_pause","system_info_place_info","system_info_play","system_info_play_status","system_info_resume","system_info_run_test","system_info_services","system_info_stop","system_info_studio_settings","workspace_state_changes","workspace_state_clear_cache","workspace_state_clear_history","workspace_state_metadata","workspace_state_scripts","workspace_state_selection_info","workspace_state_snapshot","workspace_state_sync","workspace_state_viewport"]),TI=new Set(["system_info_connection","system_info_ping","system_info_usage"]);function Vr(t){return X2.has(t)?"pro":"basic"}function voe(t){if(t===null||typeof t!="object")return;let e=t.proFallback;if(e===null||typeof e!="object")return;let n=e;if(typeof n.executedCommand!="string")return;let i=Array.isArray(n.alternatives)?n.alternatives.filter(r=>typeof r=="string"):void 0;return{executedCommand:n.executedCommand,...typeof n.requestedCommand=="string"?{requestedCommand:n.requestedCommand}:{},...i?{alternatives:i}:{}}}var boe=/^Pro action '[^']+' is blocked in Basic mode\. Basic fallback '([^']+)' failed: (.*)$/s,xoe=/^Pro action '[^']+' is blocked in Basic mode\.(?! Basic fallback ')/,_oe=/instance not found|not found in|parent not found/,Soe=/is required|missing required/,woe=/unknown action|unknown command/,koe=/must be|invalid|cannot/;function Coe(t){let e=boe.exec(t);return e?{fallbackCommand:e[1],reason:e[2]}:null}function Ioe(t){return xoe.test(t)}function Poe(t){let e=t.toLowerCase();return _oe.test(e)?"not_found":Soe.test(e)?"missing_param":woe.test(e)?"unknown_action":koe.test(e)?"validation":"other"}function bc(t){if(t.resultSuccess){let n=voe(t.resultData);return n?{kind:"success_fallback",fallbackCommand:n.executedCommand,...n.alternatives?{alternatives:n.alternatives}:{}}:{kind:"success_ok"}}if(t.tier!=="pro"||!t.resultError)return{kind:"error"};let e=Coe(t.resultError);return e?{kind:"blocked_fallback_failed",fallbackCommand:e.fallbackCommand,blockedDetail:Poe(e.reason)}:Ioe(t.resultError)?{kind:"blocked_unsupported"}:{kind:"error"}}var RI=100,Eoe=12e4,$oe=Object.entries(Np).reduce((t,[e,n])=>{for(let i of n.validActions)t[`${e}_${i}`]=e;return t},{});function Y2(t){return{toolName:$oe[t]||t,commandName:t}}function Q2(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 sv(t,e){if(t!=="manage_sync_status_current_place")return e;let{placeId:n,...i}=e;return i}async function OI(t,e,n,i,r,a){if(!t.historyManager)return;let o=bc({resultSuccess:r.success,resultData:r.data,resultError:r.error,tier:Vr(n)});if(r.success){let s=o.kind==="success_fallback"&&r.data&&typeof r.data=="object"?{...r.data,outcomeStatus:"fallback",requestedCommand:n,executedCommand:o.fallbackCommand,...o.alternatives?{alternatives:o.alternatives}:{}}:r.data;await t.historyManager.recordSuccess(e,sv(n,i),s,a,n);return}if(o.kind==="blocked_unsupported"){await t.historyManager.recordBlockedOutcome(e,sv(n,i),r.error||"Pro action blocked in Basic mode","blocked_unsupported",{executionTimeMs:a,command:n});return}if(o.kind==="blocked_fallback_failed"){await t.historyManager.recordBlockedOutcome(e,sv(n,i),r.error||"Pro action blocked in Basic mode","blocked_fallback_failed",{executionTimeMs:a,command:n,...o.fallbackCommand?{fallbackCommand:o.fallbackCommand}:{},...o.blockedDetail?{blockedDetail:o.blockedDetail}:{}});return}await t.historyManager.recordFailure(e,sv(n,i),r.error||"Unknown error",a,void 0,n)}function Toe(t,e,n){if(!e)return;let i=t.pluginClients.get(e);i?.inFlightRequestId===n&&(i.inFlightRequestId=void 0)}function Roe(t,e,n){if(g.debug("Broadcasting command",{command:e.data.command,requestId:e.data.requestId,targetClientId:n||"all",activeWebsocketClients:Ja(t).length}),n){let i=t.pendingCommands.get(n)||[];i.length>=RI&&i.shift(),i.push(e),t.pendingCommands.set(n,i)}else{let i=Ja(t).sort((o,s)=>s.lastSeen-o.lastSeen),r=e.data.params?.placeId,a;if(r!==void 0&&i.length>1&&(a=i.find(o=>o.placeId===r),a&&g.debug("Routed command by placeId",{clientId:a.clientId,placeId:r})),a||(a=i[0],i.length>1&&g.warn("Multiple plugin clients connected, routing to most recent",{targetClientId:a?.clientId,targetPlaceId:a?.placeId,totalClients:i.length,command:e.data.command})),a){let o=t.pendingCommands.get(a.clientId)||[];o.length>=RI&&o.shift(),o.push(e),t.pendingCommands.set(a.clientId,o),g.debug("Routed command to client",{clientId:a.clientId,projectName:a.projectName,placeId:a.placeId})}else t.globalPendingCommands.length>=RI&&t.globalPendingCommands.shift(),t.globalPendingCommands.push(e)}JC(t,n)}async function t6(t,e,n){let i=Date.now(),r="",a={};try{let o=e.body;r=o.command??"",a=o.params||{};let s=o.requestId;if(o.instanceId||(t.serverLastCommandAt=Date.now()),o.instanceId){let m=t.mcpInstances.get(o.instanceId);m&&(m.lastCommandAt=Date.now(),m.lastSeen=m.lastCommandAt)}let c=Y2(r),l=typeof o.timeout=="number"&&Number.isFinite(o.timeout)&&o.timeout>0?{timeout:o.timeout}:void 0,u=o.instanceId?t.mcpInstances.get(o.instanceId)?.sessionId??t.sessionId:t.sessionId;if(!r){n.status(400).json({error:"Missing command"});return}if(g.debug("Received execute request",{command:r,requestId:s}),r==="get_cached_selection"){let m=a.maxAge,h=Op(t,m!==void 0?m:3e4),y=Date.now()-i;if(!h){let _={requestId:s,success:!0,data:{cached:!1,message:"No cached selection data available. The plugin may not be connected or no selection changes have occurred yet."}};t.historyManager&&await t.historyManager.recordSuccess(c.toolName,a,_.data,y,c.commandName),n.json(_);return}let v=Date.now()-h.timestamp,S={cached:!0,selection:h.selection,count:h.count,timestamp:h.timestamp,age:v};t.historyManager&&await t.historyManager.recordSuccess(c.toolName,a,S,y,c.commandName),n.json({requestId:s,success:!0,data:S});return}if(r==="get_connection_info"){let m=await av(t),h=Date.now()-i;t.historyManager&&await t.historyManager.recordSuccess(c.toolName,a,m,h,c.commandName),n.json({requestId:s,success:!0,data:m});return}if(ov(r)==="internal"){if(!t.internalCommandExecutor){n.status(503).json({requestId:s,success:!1,error:`Internal command executor is not initialized for command: ${r}`});return}let m={...a,__sessionId:u},h=await t.internalCommandExecutor(r,m),y=Date.now()-i,v=Q2(m,h.data);t.historyManager&&await OI(t,c.toolName,c.commandName,v,{success:h.success,data:h.data,error:h.error||"Internal command failed"},y),n.json({requestId:s,...h});return}let p=await xc(t,r,a,s,l),d=Date.now()-i,f={...Q2(a,p.data),__sessionId:u};t.historyManager&&await OI(t,c.toolName,c.commandName,f,p,d),n.json(p)}catch(o){let s=Date.now()-i,c=tr(o);if(t.historyManager&&r){let l=Y2(r);await t.historyManager.recordFailure(l.toolName,a,c,s,"EXCEPTION",l.commandName)}g.error("Execute request failed",o),n.status(500).json({success:!1,error:c})}}async function n6(t,e,n){let i=Date.now(),r=e.body,a=r.action;if(!a||typeof a!="string"){n.status(400).json({success:!1,error:"Missing required field: action"});return}if(!new Set(["get","set","get_all","set_multiple","get_attr","set_attr","get_all_attrs","delete_attr","add_tag","remove_tag","check_tag","get_tags","get_tagged"]).has(a)){n.status(400).json({success:!1,error:`Unsupported action: ${a}`});return}let s=`manage_properties_${a}`,c=e6(),{action:l,...u}=r;try{t.serverLastCommandAt=Date.now();let p=await xc(t,s,u,c),d=Date.now()-i;t.historyManager&&await OI(t,"manage_properties",s,u,p,d),n.json(p)}catch(p){let d=Date.now()-i,f=tr(p);t.historyManager&&await t.historyManager.recordFailure("manage_properties",u,f,d,"EXCEPTION",s),g.error("/api/properties \uC694\uCCAD \uCC98\uB9AC \uC2E4\uD328",p),n.status(500).json({success:!1,error:f})}}async function xc(t,e,n,i,r){g.debug("Executing command locally",{command:e,requestId:i});let a=t.config.requestTimeout,o=r?.timeout??Math.max(t.config.requestTimeout,Eoe),s=new Promise((c,l)=>{let u={requestId:i,command:e,params:n,timestamp:Date.now(),queueTimeoutMs:a,ackedTimeoutMs:o,state:pn.QUEUED,resolve:c,reject:l,timeoutId:null};t.commandQueue.set(i,u),u.timeoutId=setTimeout(()=>{t.commandQueue.delete(u.requestId),Toe(t,u.targetClientId,u.requestId),u.reject(new Error(`Command timeout after ${a}ms: ${u.command}`))},a)});return Roe(t,{event:"command",id:e6(),data:{command:e,requestId:i,params:n}}),s}oe();function cv(t,e){let n={};e.provider!==void 0&&(n.provider=e.provider),e.tier!==void 0&&(n.tier=e.tier),e.status!==void 0&&(n.status=e.status),e.canUsePro!==void 0&&(n.canUsePro=e.canUsePro),e.cleared===!0&&(n.cleared=!0),Qe(t,"license",n)}function nr(t,e,n,i){t.status(e).json({success:!1,error:{code:n,message:i}})}function Ooe(t){if(typeof t!="string")return;let e=t.trim().toLowerCase();return e.length>0?e:void 0}function lv(t,e){if(e==="plugin")return t;let n={...t};return delete n.sessionToken,n}function Dp(t,e,n,i){let r=Ooe(i??e.query.provider);return r?t.licenseState?.supportsProvider(r)===!1?(nr(n,400,"LICENSE_PROVIDER_UNSUPPORTED",`provider "${r}" is not supported`),null):r:(nr(n,400,"LICENSE_PROVIDER_REQUIRED","provider is required"),null)}function jp(t,e){return t.licenseState?!0:(nr(e,503,"LICENSE_NOT_INITIALIZED","License system is not initialized"),!1)}function Noe(t){if(!t||typeof t!="object")return!1;let e=t;return(e.canUsePro===!0||e.canUsePro===!1)&&typeof e.status=="string"&&typeof e.checkedAt=="number"}function Ap(t,e,n,i="plugin"){if(!jp(t,n))return;let r=e.body,a=typeof r?.licenseKey=="string"?r.licenseKey:"";if(!a.trim()){nr(n,400,"LICENSE_KEY_REQUIRED","licenseKey is required");return}let o=Dp(t,e,n,r?.provider);if(!o)return;let s=typeof r?.pluginClientId=="string"?r.pluginClientId:typeof e.query.clientId=="string"?e.query.clientId:void 0,c=typeof r?.deviceId=="string"?r.deviceId:void 0,l={licenseKey:a,provider:o,clientType:i};s&&(l.pluginClientId=s),c&&(l.deviceId=c),t.licenseState.activateGateway(l).then(u=>{t.analyticsManager?.setTier(u.canUsePro?"pro":"basic"),cv(t,{provider:o,tier:u.canUsePro?"pro":"basic",status:u.status,canUsePro:u.canUsePro}),n.json(lv(u,i))}).catch(u=>{g.warn("License activate failed",{error:u instanceof Error?u.message:"unknown_error"}),nr(n,502,"LICENSE_ACTIVATE_FAILED",u instanceof Error?u.message:"License activation failed")})}function zp(t,e,n,i="plugin"){if(!jp(t,n))return;let r=Dp(t,e,n);if(r)try{let a=t.licenseState.getStatus(r);t.analyticsManager?.setTier(a.canUsePro?"pro":"basic"),n.json(lv(a,i))}catch(a){g.warn("License status check failed",{error:a instanceof Error?a.message:"unknown_error"}),nr(n,502,"LICENSE_STATUS_FAILED",a instanceof Error?a.message:"License status check failed")}}function Mp(t,e,n,i="plugin"){if(!jp(t,n))return;let r=e.body,a=Dp(t,e,n,r?.provider);if(!a)return;let o={provider:a,clientType:i};i==="plugin"&&typeof r?.sessionToken=="string"&&r.sessionToken.trim()&&(o.sessionToken=r.sessionToken.trim()),typeof r?.pluginClientId=="string"&&(o.pluginClientId=r.pluginClientId),typeof r?.deviceId=="string"&&(o.deviceId=r.deviceId),t.licenseState.refreshGateway(o).then(s=>{t.analyticsManager?.setTier(s.canUsePro?"pro":"basic"),cv(t,{provider:a,tier:s.canUsePro?"pro":"basic",status:s.status,canUsePro:s.canUsePro}),n.json(lv(s,i))}).catch(s=>{g.warn("License refresh failed",{error:s instanceof Error?s.message:"unknown_error"}),nr(n,502,"LICENSE_REFRESH_FAILED",s instanceof Error?s.message:"License refresh failed")})}function Lp(t,e,n,i="plugin"){if(!jp(t,n))return;let r=e.body,a=Dp(t,e,n,r?.provider);a&&t.licenseState.resetGateway({provider:a,clientType:i}).then(o=>{t.analyticsManager?.setTier("basic"),cv(t,{provider:a,tier:"basic",status:o.status,canUsePro:o.canUsePro,cleared:!0}),n.json(lv(o,i))}).catch(o=>{g.warn("License reset failed",{error:o instanceof Error?o.message:"unknown_error"}),nr(n,502,"LICENSE_RESET_FAILED",o instanceof Error?o.message:"License reset failed")})}function NI(t,e,n){if(!jp(t,n))return;let i=e.body,r=Dp(t,e,n,i?.provider);if(!r)return;if(i?.clientType!=="plugin"){nr(n,400,"LICENSE_CLIENT_TYPE_INVALID","clientType must be plugin");return}let a=typeof i?.sessionToken=="string"&&i.sessionToken.trim()?i.sessionToken.trim():void 0,o=Noe(i?.snapshot)?i.snapshot:void 0;if(!o&&!a){nr(n,400,"LICENSE_BOOTSTRAP_REQUIRED","snapshot or sessionToken is required");return}let s={provider:r,clientType:"plugin"};o&&(s.snapshot=o),a&&(s.sessionToken=a),t.licenseState.bootstrap(s).then(c=>{t.analyticsManager?.setTier(c.canUsePro?"pro":"basic"),cv(t,{provider:r,tier:c.canUsePro?"pro":"basic",status:c.status,canUsePro:c.canUsePro}),n.json({ok:!0})}).catch(c=>{g.warn("License bootstrap failed",{error:c instanceof Error?c.message:"unknown_error"}),nr(n,502,"LICENSE_BOOTSTRAP_FAILED",c instanceof Error?c.message:"License bootstrap failed")})}function r6(t){t.app.post("/license/bootstrap",(e,n)=>NI(t,e,n)),t.app.post("/license/activate",(e,n)=>Ap(t,e,n,"plugin")),t.app.post("/license/refresh",(e,n)=>Mp(t,e,n,"plugin")),t.app.post("/license/reset",(e,n)=>Lp(t,e,n,"plugin")),t.app.get("/license/status",(e,n)=>zp(t,e,n,"plugin"))}It();oe();import{promises as uv}from"fs";import DI from"path";function Doe(t){let e=t.config.appDataDir??nn();return DI.join(e,"observability","logs")}async function _c(t,e,n){let i=e.query.clientId;if(i&&t.pluginClients.has(i)){let r=t.pluginClients.get(i);r.lastSeen=Date.now()}try{let r=e.body;if(!r||!Array.isArray(r.logs)){g.warn("Invalid logs request",{body:r}),n.status(400).json({error:"Invalid request body"});return}let a=Doe(t);await uv.mkdir(a,{recursive:!0});let c=`plugin-${new Date().toISOString().split("T")[0]}.log`,l=DI.join(a,c),u=DI.join(a,"current.log"),p=r.logs.map(d=>`[${new Date(d.timestamp*1e3).toISOString().replace("T"," ").substring(0,19)}] [${d.level}] ${d.message}`).join(`
|
|
140
140
|
`)+`
|
|
141
141
|
`;await uv.appendFile(l,p,"utf-8");try{let d="";try{d=await uv.readFile(u,"utf-8")}catch{}let m=(d+p).split(`
|
|
142
142
|
`).slice(-500).join(`
|
|
Binary file
|
|
Binary file
|