@weppy/roblox-mcp 2.7.13 → 2.7.14
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/CHANGELOG.md +11 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/roblox-plugin/WeppyRobloxMCP.rbxm +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
## [2.7.14] - 2026-06-03
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
|
|
12
|
+
- **More reliable sync for imported models and VFX assets** — Project sync no longer stops with `Can't convert to JSON` when a Roblox model contains non-UTF8 string data in values or attributes. WEPPY now preserves that data safely and continues syncing the rest of the place.
|
|
13
|
+
|
|
14
|
+
### Stability
|
|
15
|
+
|
|
16
|
+
- **Clearer chunk-sync troubleshooting in Studio** — When sync encounters unusual data, WEPPY now points to the affected Studio object, field, and byte length in the plugin logs without exposing the raw value. Generic `Internal error` responses from the server also surface the detailed message when one is available, making support logs easier to share from the WEPPY UI.
|
|
17
|
+
|
|
7
18
|
## [2.7.13] - 2026-06-02
|
|
8
19
|
|
|
9
20
|
### Stability
|
package/dist/index.js
CHANGED
|
@@ -501,7 +501,7 @@ data: ${JSON.stringify(n)}
|
|
|
501
501
|
`)}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()},Eue),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 vt.relative(this.config.getSyncRoot(),e)}extractServiceName(e){return Mt(e)[0]??""}findTreeNode(e,n){let i=Mt(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=Mt(e);return n.length===0?null:n[n.length-1]??null}getParentPath(e){let n=Mt(e);return n.length<=1?null:`game.${n.slice(0,-1).join(".")}`}async copyDirectory(e,n){await this.ensureDir(n);let i=await nt.readdir(e,{withFileTypes:!0});for(let r of i){let a=vt.join(e,r.name),o=vt.join(n,r.name);r.isDirectory()?await this.copyDirectory(a,o):await nt.copyFile(a,o)}}async tryRename(e,n){try{return this.onWriteCallback?.(e),this.onWriteCallback?.(n),await nt.rename(e,n),!0}catch{return!1}}};function XP(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)=>XP(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)&&XP(n[o],i[o]))}function Tue(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;XP(a,o)||(n[r]={before:a,after:o})}return n}import Ze from"path";import{promises as Co}from"fs";ae();var Xv=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=Ze.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=Ze.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 p=this.resolveInstancePathFromDirectory(o.fsPath);if(!p)continue;let f=this.getSiblingIndexForDirectory(o.fsPath),m=this.buildPendingDedupeKey(p,f);if(o.type==="unlinkDir"){if(i.has(m)||(i.add(m),this.isServiceInstance(p)))continue;let S=Ze.relative(this.placeRoot,o.fsPath),x={type:"instanceRemoved",instancePath:p,fsPath:S,fileType:"directory",content:null};f&&(x.siblingIndex=f),n.push(x);continue}if(r.has(m)||this.isServiceInstance(p)||a.has(o.fsPath)||!await this.isContainerDirectory(o.fsPath))continue;let y=Ze.relative(this.placeRoot,o.fsPath),v={type:"containerCreated",instancePath:p,fsPath:y,fileType:"directory",content:null,className:"Folder",parentPath:this.extractParentPath(p)};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 p=this.buildPendingDedupeKey(s,c);if(i.has(p)||(i.add(p),this.isServiceInstance(s)))continue;let f=Ze.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),d;if(u&&o.type==="add"){if(this.isServiceInstance(s))continue;if(l==="script")d=await this.readNewScript(o.fsPath,s);else if(l==="value")d=await this.readNewValue(o.fsPath,s);else if(l==="props")d=await this.readNewInstance(o.fsPath,s);else continue}else if(l==="script")d=await this.readModifiedScript(o.fsPath,s);else if(l==="value")d=await this.readModifiedValue(o.fsPath,s);else if(l==="props")d=await this.readModifiedProps(o.fsPath,s);else continue;c&&(d.siblingIndex=c),n.push(d)}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(Ze.basename(e)==="_tree.json")return;let r=Ze.relative(n,e);if(r.startsWith(".."))return;let a=r.split(Ze.sep).filter(d=>d.length>0);if(a.length<2)return;let o=a[a.length-2],s=Ze.dirname(e),c=Ze.dirname(s),l=this.index.getOriginalInstance(c,o);return l?l.siblingIndex:$r(o).siblingIndex}isForwardOnlyPath(e){return this.index.isPathUnderClass(e,Ac)}logForwardOnlySkip(e,n){g.info("Skipped reverse change for forward-only path",{source:n,path:Ze.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=Ze.relative(n,e);if(i.startsWith("..")||i===""||Ze.isAbsolute(i))return null;let r=i.split(Ze.sep).filter(s=>s.length>0);if(r.length===0)return null;let a=["game"],o=n;for(let s of r){o=Ze.join(o,s);let c=Ze.dirname(o);a.push(this.index.getOriginalNameForDir(c,s))}return lt(a)}getSiblingIndexForDirectory(e){let n=Ze.basename(e),i=Ze.dirname(e),r=this.index.getOriginalInstance(i,n);return r?r.siblingIndex:$r(n).siblingIndex}async isContainerDirectory(e){try{let n=await Co.readdir(e,{withFileTypes:!0});for(let i of n)if(i.isFile()&&Nv.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 Co.readFile(e,"utf-8"),r=Ze.relative(this.placeRoot,e);return{type:"scriptSource",instancePath:n,fsPath:r,fileType:"script",content:i}}async readModifiedProps(e,n){let i=await Co.readFile(e,"utf-8"),r=JSON.parse(i),a=Ze.relative(this.placeRoot,e);return{type:"properties",instancePath:n,fsPath:a,fileType:"props",content:r}}async readModifiedValue(e,n){let i=await Co.readFile(e,"utf-8"),r=JSON.parse(i),a=Ze.relative(this.placeRoot,e);return{type:"valueChanged",instancePath:n,fsPath:a,fileType:"value",content:r}}async readNewScript(e,n){let i=await Co.readFile(e,"utf-8"),r=Ze.relative(this.placeRoot,e),a=Ze.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 Co.readFile(e,"utf-8"),r=JSON.parse(i),a=Ze.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 Co.readFile(e,"utf-8"),r=JSON.parse(i),a=Ze.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 yt(e)}isServiceInstance(e){let n=ai(e);return n.length!==2||n[0]!=="game"?!1:PP.has(n[1])}};ae();import Due from"path";ae();import fa from"path";import{createHash as X6}from"crypto";import{promises as eE}from"fs";var $ue=500,QP=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`${fa.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=$ue){this.explorerRoot=fa.resolve(e),this.syncIndex=n,this.scanIntervalMs=i}setDirectionChecker(e){this.directionChecker=e}setOnForwardViolation(e){this.onForwardViolation=e}suppressPath(e,n){let i=fa.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=G6(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=fa.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=fa.join(this.explorerRoot,r.relativePath),o=new Date().toISOString();if(await this.isSuppressedPath(a)||r.kind==="file"&&KP(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 eE.readFile(a,"utf-8"),l=X6("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,Ac)?!0:this.directionChecker?this.directionChecker(e)==="forward":!1}async isSuppressedPath(e){let n=fa.resolve(e);for(let[i,r]of this.suppressedPaths){if(n===i||n.startsWith(`${i}${fa.sep}`))return r.mode==="generic"?!0:r.mode==="missing"?!await Oue(n):await Aue(n)===r.hash;if(i.startsWith(`${n}${fa.sep}`))return r.mode==="generic"}return!1}};async function Oue(t){try{return await eE.access(t),!0}catch{return!1}}async function Aue(t){try{let e=await eE.readFile(t,"utf-8");return X6("md5").update(e,"utf-8").digest("hex")}catch{return null}}var Q6=QP;var at="snapshot-scanner",Qv=class extends Q6{placeId;detectorExplorerRoot;lifecycleGeneration=0;constructor(e,n,i={}){super(e,n,i.scanIntervalMs),this.placeId=i.placeId??null,this.detectorExplorerRoot=Due.resolve(e)}async start(){let e=++this.lifecycleGeneration;g.info("Reverse detector bootstrap scheduled",{placeId:this.placeId,mode:at,explorerRoot:this.detectorExplorerRoot}),await super.start(),e===this.lifecycleGeneration&&g.info("Reverse detector bootstrap completed",{placeId:this.placeId,mode:at})}async stop(e){this.lifecycleGeneration+=1,g.info("Reverse detector stopping",{placeId:this.placeId,mode:at,reason:e??"unspecified"}),await super.stop(e)}beginFullSyncReplacement(){g.info("Reverse detector paused for full sync replacement",{placeId:this.placeId,mode:at}),super.beginFullSyncReplacement()}async endFullSyncReplacement(){g.info("Reverse detector baseline refresh scheduled",{placeId:this.placeId,mode:at}),await super.endFullSyncReplacement()}async rescan(){g.debug("Reverse detector manual rescan requested",{placeId:this.placeId,mode:at});let e=await super.rescan();return g.info("Reverse detector manual rescan completed",{placeId:this.placeId,mode:at,added:e,pendingCount:this.getPendingCount()}),e}};import Wc from"path";import{promises as eF}from"fs";function Io(t,e,n,i,r=!1){if(i&&i>=1){let s=yt(e);if(s){let c=t.getEffectiveDir(s,i);if(c){let l=it(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 tE(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=Wc.relative(s,a).split(Wc.sep).filter(l=>l.length>0);return lt(["game",...c])}let o=e;if(n){let s=t.getResolvedName(e,n);if(s)o=mp(o,s);else if(r)throw new Error(`Missing collision mapping for ${e}#${n}`)}return o}function Nue(t,e){let n=ai(t),i=ai(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 tF(t,e){e=jue(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=Io(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&&Nue(a,s)){i.add(r);break}return i.size===0?e:e.filter((r,a)=>!i.has(a))}function jue(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=Po(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 d=e[u];if(d.type!=="instanceRemoved")continue;let p=d.siblingIndex;if(!l.has(p)&&c.has(p))l.add(p);else{let f;for(let m of c)if(!l.has(m)){f=m;break}f!==void 0&&(l.add(f),r[u]={...d,siblingIndex:f})}}}return r}function Po(t,e,n,i=!1){if(n&&n>=1){let r=yt(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 ma(t,e,n,i,r){let a=Wc.join(e,n),o=Wc.join(e,i);try{(await eF.stat(a)).isDirectory()&&(r&&(r.suppressPath(a),r.suppressPath(o)),await eF.rename(a,o),t.renameHashKeysUnder(a,o),t.renameReverseNameMappingsUnder(a,o),t.renameInstanceDirMappingsUnder(a,o))}catch{return}}function tE(t,e,n){let i=Wc.relative(t.getExplorerRoot(),e).split(Wc.sep).filter(r=>r.length>0);return lt(["game",...i,n])}async function eb(t,e){let n=t.index.planCollisionReindexAfterBatchRemovals(e.instancePath,[...e.removedSiblingIndices],e.parentDir);for(let i of n.renames)await ma(t.index,e.parentDir,i.from,i.to,t.fileWatcher),await Gc(t.writer,t.index,e.parentDir,i.from,i.to);t.index.applyCollisionReindexAfterBatchRemovals(e.instancePath,e.parentDir,n)}async function Gc(t,e,n,i,r){let a=tE(e,n,i),o=tE(e,n,r);await t.renameInTree(a,o,r)}import tb from"path";var nF=5*60*1e3,rF=100;function nE(t){return t!==null&&typeof t=="object"&&!Array.isArray(t)}function hp(t){return typeof t=="string"&&t.length>0}function nb(t,e){let n=tb.resolve(t),i=tb.resolve(e),r=tb.relative(n,i);return r===""||!r.startsWith("..")&&!tb.isAbsolute(r)}function iF(t){if(!nE(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(!hp(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"}:hp(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 hp(t.serviceName)?hp(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 aF(t){if(!nE(t))return{success:!1,error:"Body must be an object"};if(!hp(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(!nE(i)||!e.has(i.type))return{success:!1,error:`changes[${n}]: invalid type '${String(i?.type)}'`}}return{success:!0,data:t}}import lr from"path";import{promises as Eo}from"fs";ae();var rb=class{constructor(e){this.ctx=e}async processChangeForPlace(e,n,i){let r=D2(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=Io(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=Oc.has(n.className),s=n.path,c=n.name;{let d=Po(e.index,n.path,n.parentSiblingIndex,!0),{resolved:p,retroactiveRename:f}=e.index.registerCollision(n.path,n.siblingIndex,d);f&&(await ma(e.index,d,f.from,f.to,e.fileWatcher),await Gc(e.writer,e.index,d,f.from,f.to));let m=e.index.sanitizeName(n.name);if(p!==m||d!==e.index.resolveParentDir(n.path)){let h=e.index.getExplorerRoot(),y=lr.relative(h,d).split(lr.sep).filter(v=>v.length>0);s=lt(["game",...y,p]),c=p}}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=yt(s)||n.parentPath;return await e.writer.addToTree(u,l),e.instanceCount++,o&&n.scriptSource!==void 0&&e.scriptCount++,null}case"instanceRemoved":{let o=Io(e.index,n.path,n.siblingIndex,n.parentSiblingIndex,!0),s=Po(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=it(o),l=yt(o);if(l&&c&&await e.writer.removeFromTree(l,c),n.siblingIndex!==void 0){let d=`${s}\0${n.path}`,p=i.get(d);p||(p={instancePath:n.path,parentDir:s,removedSiblingIndices:new Set},i.set(d,p)),p.removedSiblingIndices.add(n.siblingIndex)}let u=yt(o);if(u)try{let d=e.index.resolveChildrenDir(u);lr.dirname(d)!==e.index.getExplorerRoot()&&(await Eo.readdir(d)).length===0&&await Eo.rmdir(d)}catch{return null}return null}case"instanceRenamed":{let o=Io(e.index,n.oldPath,n.siblingIndex,n.parentSiblingIndex,!0),s=yt(n.oldPath)===yt(n.newPath),c=s?Po(e.index,n.oldPath,n.parentSiblingIndex,!0):null,l=Po(e.index,n.newPath,n.parentSiblingIndex,!0),{resolved:u,retroactiveRename:d}=e.index.registerCollision(n.newPath,n.siblingIndex,l);d&&(await ma(e.index,l,d.from,d.to,e.fileWatcher),await Gc(e.writer,e.index,l,d.from,d.to));let p=u!==e.index.sanitizeName(n.newName)?mp(n.newPath,u):n.newPath;if(await e.writer.renameInstance(o,p,n.newName),this.renameOriginalClassMapping(e,n.oldPath,n.newPath),await e.writer.renameInTree(o,p,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 ma(e.index,c,m.from,m.to,e.fileWatcher),await Gc(e.writer,e.index,c,m.from,m.to);e.index.applyCollisionReindexAfterRemoval(n.oldPath,c,f)}return null}case"instanceMoved":{let o=Io(e.index,n.oldPath,n.siblingIndex,n.parentSiblingIndex,!0),s=Po(e.index,n.newPath,n.parentSiblingIndex,!0),{resolved:c,retroactiveRename:l}=e.index.registerCollision(n.newPath,n.siblingIndex,s);l&&(await ma(e.index,s,l.from,l.to,e.fileWatcher),await Gc(e.writer,e.index,s,l.from,l.to));let u=it(n.newPath)||"",d=c!==e.index.sanitizeName(u)?mp(n.newPath,c):n.newPath;return await e.writer.moveInstance(o,d),this.renameOriginalClassMapping(e,n.oldPath,n.newPath),await e.writer.moveInTree(o,d),null}case"scriptSourceChanged":{let o=Io(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=lr.join(this.ctx.config.getPlaceRoot(e.placeId),"explorer"),o=lr.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=lr.join(this.ctx.config.getPlaceRoot(e.placeId),"explorer"),a=lr.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 Eo.readFile(i,"utf-8"),a=JSON.parse(r);if(a.className&&Oc.has(a.className))return a.className}catch{return"ModuleScript"}return"ModuleScript"}async cleanupStaleScriptFilesForPlace(e,n,i){let r=Av[i],a=lr.join(this.ctx.config.getPlaceRoot(e.placeId),"explorer");for(let[o,s]of Object.entries(Av)){if(s===r)continue;let c=e.index.resolveScriptPath(n,o,!1);try{await Eo.unlink(c),e.index.removeHash(c),g.info("Cleaned up stale script file",{placeId:e.placeId,removed:lr.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 Eo.readFile(r,"utf-8")}catch{return null}return e.index.computeHash(o)===a?null:{fsPath:lr.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 Eo.readFile(a,"utf-8")}catch{return null}return e.index.computeHash(s)===o?null:{fsPath:lr.relative(this.ctx.config.getPlaceRoot(e.placeId),a),instancePath:n,fileType:"script",studioContent:i,fileContent:s}}catch{return null}}async applyDeferredCollisionBatchReindexForPlace(e,n){await eb(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 Eo.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??it(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 oF from"path";import{promises as ib}from"fs";ae();var ab=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=oF.join(r,".sync-index.json"),o=!1;try{await ib.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=oF.join(r,"explorer"),c=new pa(r,s);await c.loadFromDisk();let l=await c.getModifiedFiles(),u=0,d=0,p=0;for(let m of l)switch(m.changeType){case"modified":u++;break;case"deleted":d++;break;case"added":p++;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:d,added:p,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=dn(e.query.limit,50,1,200),a=dn(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),d=(await Hv(l,1e4)).reverse().filter(h=>!(o&&h.direction!==o||s&&h.type!==s)),p=d.length,m={entries:d.slice(a,a+r),total:p,hasMore:a+r<p};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:{...vo},reverseDetectorActive:!1,reverseDetectorMode:at,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:{...vo},reverseDetectorActive:!1,reverseDetectorMode:at,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:at,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 Hv(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{...yo};let i=this.ctx.places.get(n);return i?{...i.directions}:{...yo}}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} is not loaded in the sync cache. Start sync for this place before reading synced files.`);let a=r.index.resolvePropsPath(n);try{return{content:await ib.readFile(a,"utf-8"),path:a}}catch(s){if(s.code!=="ENOENT")throw s}for(let s of Oc){let c=r.index.resolveScriptPath(n,s,!1);try{return{content:await ib.readFile(c,"utf-8"),path:c}}catch{continue}}let o=r.index.resolveValuePath(n);try{return{content:await ib.readFile(o,"utf-8"),path:o}}catch(s){if(s.code!=="ENOENT")throw s}throw new Error(`No synced file found for instance: ${n}. Ensure the instance has been synced to disk and that the canonical instance path is correct.`)}async writeSyncedFile(e,n,i){let r=parseInt(e,10),a=this.ctx.places.get(r);if(!a)throw new Error(`Place ${e} is not loaded in the sync cache. Start sync for this place before writing synced files.`);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. Start sync for the current place before using reverse-sync disk execution.");let r=this.ctx.places.get(i);if(!r)throw new Error(`Place ${i} is not loaded in the sync cache. Restart sync before retrying disk execution.`);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:it(n.path),properties:{[n.property]:n.value}}),{success:!0,path:n.path};default:throw new Error(`Disk execution is not supported for command: ${e}. This command must run through the plugin route.`)}}};import De from"path";import{createHash as Mue,randomUUID as zue}from"crypto";import{promises as Sn}from"fs";ae();function Lue(t){return Mue("md5").update(t,"utf-8").digest("hex")}function sF(t){return t.pluginProcessToken?`process:${t.pluginProcessToken}`:t.pluginSessionId?`session:${t.pluginSessionId}`:t.pluginClientId?`client:${t.pluginClientId}`:null}var ob=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.activeSyncIdentityScope=sF(n),r.placeName=n.placeName,n.directions&&typeof n.directions=="object"){let d=n.directions,p=f=>f==="forward"||f==="reverse"||f==="bidirectional";p(d.scripts)&&(r.directions.scripts=d.scripts),p(d.values)&&(r.directions.values=d.values),p(d.containers)&&(r.directions.containers=d.containers),p(d.data)&&(r.directions.data=d.data),p(d.services)&&(r.directions.services=d.services),g.info("Sync directions received",{placeId:e,directions:r.directions})}else r.directions={...yo};if(n.applyModes&&typeof n.applyModes=="object"){let d=n.applyModes,p=f=>f==="auto"||f==="manual";p(d.scripts)&&(r.applyModes.scripts=d.scripts),p(d.values)&&(r.applyModes.values=d.values),p(d.containers)&&(r.applyModes.containers=d.containers),p(d.data)&&(r.applyModes.data=d.data),p(d.services)&&(r.applyModes.services=d.services)}else r.applyModes={...vo};r.forwardRestoreQueue=[];let a=zue();r.activeFullSyncSessionId=a,this.pendingServiceTrees.set(a,new Map),r.instanceCount=0,r.scriptCount=0;let o=this.ctx.config.getPlaceRoot(e),s=De.join(o,`explorer_tmp_${a}`);await Sn.mkdir(s,{recursive:!0}),r.tmpIndex=new pa(o,s),r.tmpWriter=new Vc(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=De.join(o,".sync-meta.json");await Sn.mkdir(De.dirname(u),{recursive:!0}),await this.ctx.atomicWriteFile(u,JSON.stringify(l,null,2)+`
|
|
502
502
|
`),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,identityScope:r.activeSyncIdentityScope,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 d=r.tmpIndex.resolveParentDir(u.path),p=c.get(d)??d,f=r.activeSyncIdentityScope??sF(n),m=this.getPreferredResolvedName(r,f,u),{resolved:h,retroactiveRename:y}=r.tmpIndex.registerCollisionWithPreferredName(u.path,u.siblingIndex??void 0,p,m);y&&(await ma(r.tmpIndex,p,y.from,y.to),this.rewritePendingEffectivePaths(o,r.tmpIndex.getExplorerRoot(),De.join(p,y.from),De.join(p,y.to)),r.tmpIndex.updateSessionDebugMappingResolvedName(f,y.instancePath,y.siblingIndex,y.to));let v=r.tmpIndex.resolveChildrenDir(u.path),S=De.join(p,h);S!==v&&c.set(v,S);let x=r.tmpIndex.sanitizeName(u.name),w=u;if(p!==d||h!==x){let D=r.tmpIndex.getExplorerRoot(),A=De.relative(D,p).split(De.sep).filter(he=>he.length>0),ue=lt(["game",...A,h]);w={...u,path:ue}}let I=await r.tmpWriter.writeInstance(w);r.tmpIndex.setClassName(u.path,u.className,u.siblingIndex),this.recordSessionDebugMapping(r,f,u,h),s++,(I.propsWritten||I.valueWritten)&&r.instanceCount++,I.scriptWritten&&r.scriptCount++,o.instances.push({effectivePath:w.path,originalPath:u.path,className:u.className})}let l=this.isLastChunk(o,n.chunkIndex,n.totalChunks);if(l){let u=this.buildServiceTree(r,o);await r.tmpWriter.writeTree(n.serviceName,u);let d=this.pendingServiceTrees.get(a);d?.delete(n.serviceName),d&&d.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=De.join(o,"explorer"),c=De.join(o,`explorer_tmp_${a}`),l=r.fileWatcher,u=this.getAndClearPreserveLocalFiles(a),d=new Map,p=[];if(u.length>0){for(let f of u){let m=De.resolve(o,f);try{let h=await Sn.readFile(m,"utf-8");d.set(f,h)}catch{p.push(f)}}g.info("Backed up local files for preservation",{placeId:e,requested:u.length,backed:d.size,deleted:p.length})}l&&l.beginFullSyncReplacement();try{try{await Sn.rm(s,{recursive:!0,force:!0})}catch{}await Sn.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=De.join(o,".sync-meta.json");if(await this.ctx.atomicWriteFile(m,JSON.stringify(f,null,2)+`
|
|
503
503
|
`),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 x=De.relative(h,v),w=De.resolve(y,x);r.index.updateHashByValue(w,S)}for(let[v,S]of r.tmpIndex.getAllFileHashes()){let x=De.relative(h,v),w=De.resolve(y,x);r.index.updateFileHashByValue(w,S)}r.index.resetNameCounters(),r.index.mergeNameMappingsFrom(r.tmpIndex),r.index.mergeSessionDebugMappingsFrom(r.tmpIndex)}if(r.tmpIndex=null,r.tmpWriter&&(r.tmpWriter.stopChangeLogFlusher(),r.tmpWriter=null),this.pendingServiceTrees.delete(a),r.collisionDirMap=null,r.activeSyncIdentityScope=null,await r.index.saveToDisk(),r.state="syncing",r.activeFullSyncSessionId=null,r.syncProgress=null,this.ctx.touchRuntimePlace(e),this.ctx.activeFullSyncPlaceId=null,d.size>0){for(let[h,y]of d){let v=De.resolve(o,h);try{await Sn.mkdir(De.dirname(v),{recursive:!0}),await Sn.writeFile(v,y,"utf-8"),l?.suppressPath(v,Lue(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:d.size})}if(p.length>0){let h=0;for(let y of p){let v=De.resolve(o,y);try{await Sn.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 Mv(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=De.join(h,".project-meta.json");await this.ctx.atomicWriteFile(v,JSON.stringify(y,null,2)+`
|
|
504
|
-
`),await Dc(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=De.join(r,`explorer_tmp_${n}`);try{await Sn.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.activeSyncIdentityScope=null,e.tmpWriter&&(e.tmpWriter.stopChangeLogFlusher(),e.tmpWriter=null),this.ctx.activeFullSyncPlaceId===e.placeId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(e.placeId))},nF);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}getPreferredResolvedName(e,n,i){let r=typeof i.sessionDebugId=="string"?i.sessionDebugId:"";if(!n||r==="")return;let a=e.index.getSessionDebugMapping(n,r);if(!a)return;let o=yt(i.path),s=it(i.path)||i.name;if(!(a.parentPath!==o||a.originalName!==s))return a.resolvedName}recordSessionDebugMapping(e,n,i,r){let a=typeof i.sessionDebugId=="string"?i.sessionDebugId:"";!n||a===""||!e.tmpIndex||e.tmpIndex.setSessionDebugMapping({scope:n,sessionDebugId:a,instancePath:i.path,parentPath:yt(i.path),originalName:it(i.path)||i.name,siblingIndex:Math.max(1,i.siblingIndex??1),resolvedName:r,className:i.className,updatedAt:new Date().toISOString()})}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=Mt(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?Mt(n).map(i=>e.tmpIndex.sanitizeName(i)):Mt(n)}rewritePendingEffectivePaths(e,n,i,r){let a=De.relative(n,i).split(De.sep).filter(l=>l.length>0),o=De.relative(n,r).split(De.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=lt(["game",...a]),c=lt(["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(d=>d.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 Sn.readdir(e,{withFileTypes:!0});for(let i of n)if(i.isDirectory()){if(i.name.startsWith("explorer_tmp_")){let r=De.join(e,i.name);g.warn("Removing stale temp directory from crashed sync",{dir:i.name});try{await Sn.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=De.join(e,i.name);try{let a=await Sn.readdir(r,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=De.join(r,o.name);g.warn("Removing stale temp directory from crashed sync",{dir:`${i.name}/${o.name}`});try{await Sn.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 ur from"path";import{promises as sb}from"fs";ae();var cb=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 d=ur.resolve(s,u);if(!nb(s,d)){l.push({path:u,error:"Path is outside the place root"});continue}try{let p=await sb.readFile(d,"utf-8"),f=o.index.computeHash(p);o.index.updateHashByValue(d,f),o.index.updateFileHashByValue(d,f),c++}catch(p){let f=p.code;if(f==="ENOENT"){o.index.removeHash(d),o.index.removeHashesUnder(d);let m=this.resolveInstancePathForAppliedPath(o.index,d);if(m){let h=it(m),y=yt(m);y&&h&&await o.writer.removeFromTree(y,h)}c++}else f==="EISDIR"?c++:l.push({path:u,error:p instanceof Error?p.message:String(p)})}}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(!nb(o,ur.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 p=r.match(/^place_(\d+)(?:_[^/]+)?\//);if(p){let f=parseInt(p[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=ur.resolve(c,r);if(!nb(c,l)){n.status(403).json({error:"Forbidden",message:"Path is outside the place root"});return}let u=s.index,d=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 sb.mkdir(ur.dirname(l),{recursive:!0});let p=i.studioContent,f=u.computeHash(p);s.fileWatcher?.suppressPath(l,f),await sb.writeFile(l,p,"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 p=await sb.readFile(l,"utf-8"),f=u.computeHash(p);u.resolveFile(l,"apply-file",f),await u.saveToDisk();let m=d.getFileType(l),h=d.resolveInstancePathFromFile(l);n.status(200).json({status:"resolved",resolution:"apply-file",fsPath:r,instancePath:h,fileType:m,content:p});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=ur.relative(r,n);if(a.startsWith("..")||a===""||ur.isAbsolute(a))return null;let o=a.split(ur.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=ur.basename(n),c=ur.dirname(n),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(Nv.some(f=>u.endsWith(f))||u==="_tree.json")return null;let d=["game"],p=r;for(let f of o){p=ur.join(p,f);let m=ur.dirname(p);d.push(e.getOriginalNameForDir(m,f))}return lt(d)}};ae();function cF(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 Fue(t){return t.type==="instanceAdded"||t.type==="instanceRemoved"||t.type==="instanceRenamed"||t.type==="instanceMoved"||t.type==="scriptSourceChanged"}var Ar=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;reverseDetectorFactory;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;currentConnectedPlaceId=null;currentConnectedPlaceName=null;constructor(e,n={}){this.config=new Ov(e,n),this.reverseDetectorFactory=n.reverseDetectorFactory??(i=>new Qv(i.explorerRoot,i.syncIndex,{placeId:i.placeId})),this.apiHandler=new ab(this),this.changeProcessor=new rb(this),this.initHandler=new ob(this),this.reverseHandler=new cb(this),this.places=new Yv({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:at}),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,i.activeSyncIdentityScope=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=rE.join(r,"explorer");await Jc.mkdir(r,{recursive:!0}),await Jc.mkdir(a,{recursive:!0});let o=new pa(r,a);await o.loadFromDisk();let s=new Vc(this.config,o,e),c=new Xv(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:{...yo},applyModes:{...vo},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null,activeSyncIdentityScope:null},this.places.set(e,i),g.info("Created new place context",{placeId:e,placeRoot:r})}return i}async handleSyncInit(e,n){try{cF(e.body);let i=iF(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{cF(e.body);let i=aF(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=tF(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,d=!1,p=new Map;for(let m of s)try{let h=await this.changeProcessor.processChangeForPlace(o,m,p);h?l.push(h):(u++,Fue(m)&&(d=!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 p.values())try{await eb(o,m)}catch(h){c.push({path:m.instancePath,error:h instanceof Error?h.message:String(h)})}if(d)try{let m=this.config.getSyncRoot(),h=this.config.getPlaceRoot(a);await Mv(m,h),await Dc(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>=rF&&(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:at,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:at,fileWatcherActive:c,forwardOnlyClasses:[...Ac]};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:at}),a.fileWatcher.beginFullSyncReplacement()),a.incompleteSyncTimer&&(clearTimeout(a.incompleteSyncTimer),a.incompleteSyncTimer=null),o){let l=this.config.getPlaceRoot(r),u=rE.join(l,`explorer_tmp_${o}`);await Jc.rm(u,{recursive:!0,force:!0}).catch(()=>{})}a.tmpWriter&&(a.tmpWriter.stopChangeLogFlusher(),a.tmpWriter=null),a.tmpIndex=null,a.collisionDirMap=null,a.activeSyncIdentityScope=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."+Uue().slice(0,8);try{await Jc.writeFile(i,n,"utf-8"),await Jc.rename(i,e)}catch(r){throw await Jc.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:at});return}g.info("startFileWatcherForPlace: resuming paused reverse detector",{placeId:e.placeId,mode:at}),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:at})}).catch(i=>{g.warn("Failed to start reverse detector",{placeId:e.placeId,mode:at,error:i instanceof Error?i.message:String(i)}),e.fileWatcher===n&&(e.fileWatcher=null)})}createReverseChangeDetector(e){return this.reverseDetectorFactory({explorerRoot:rE.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=N2(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 que}from"crypto";function lF(t,e){let n=que(),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,shuttingDown:!1,onUpstreamPermanentlyDown:null,clientModeIdleTimeoutMs:t.clientModeIdleTimeoutMs??36e5,clientModeIdleWatchdogTimer:null,shutdownFn:null,historyManager:null,analyticsManager:null,executionContextManager:null,licenseState:null,syncController:null,internalCommandExecutor:null,dashboardFolderPicker:null,assetLibraryOpenCloudServiceFactory:null,activeSyncOwnerInstanceId:null,activeProjectRoot:null,pendingDashboardSyncRootPin:null,playtestControlCommand:null,dashboardSyncRootSwitchInFlight:!1,uiStudioRecorders:new Map,aiClientName:"",pluginVersion:"",syncedSessionToken:null,serverLastCommandAt:null}}var uF=xt(oa(),1);ae();function dF(t){let e=uF.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()})}ic();import{randomUUID as PE}from"crypto";ae();function Bue(){let t=process.env.WEPPY_ROBLOX_MCP_VERSION?.trim();return t||null}var ot=Bue()??"2.7.13";bp();function pb(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 yF(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 vF(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 bF(t,e,n){let i=parseInt(e.query.maxAge)||3e4,r=xp(t,i);r?n.json({cached:!0,...r}):n.json({cached:!1,message:"No cached selection available"})}function xp(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 uE(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)&&zP(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:ot,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:pb(t)}}function dE(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&&(ct(t,"connection",{clientId:e,status:"disconnected"}),Lc(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 fb(t,e,n){try{let i=e.body;if(!i.clientId){n.status(400).json({error:"Missing clientId"});return}let r=uE(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 mb(t,e,n){let i=e.body?.clientId;if(!i){n.status(400).json({error:"Missing clientId"});return}let r=dE(t,i);n.json({status:"ok",existed:r})}function xF(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),ct(t,"mcp_status",{aiClientName:a.aiClientName??"Unknown",instanceId:i.instanceId,status:"registered"}),Lc(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:ot,mcpInstanceCount:t.mcpInstances.size+1})}catch(i){g.error("Error registering MCP instance",i),n.status(500).json({error:"Internal server error"})}}function _F(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&&(ct(t,"mcp_status",{aiClientName:r?.aiClientName??"Unknown",instanceId:i,status:"unregistered"}),Lc(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 SF(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 wF(t){let e=3e4,n=Date.now(),i=or({appDataDir:t.config.appDataDir});for(let[r,a]of t.pluginClients)n-a.lastSeen>e&&(t.pluginClients.delete(r),t.pendingCommands.delete(r));return yF(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:ot,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 hb(t,e){e.json(wF(t))}function gb(t,e,n){let i=e.query.instanceId;i&&t.mcpInstances.has(i)&&(t.mcpInstances.get(i).lastSeen=Date.now()),yF(t);let r=_o(t),s={...{status:"online",connectedClients:r.length,queuedCommands:g6(t),uptime:Date.now()-t.startTime,version:ot,enableContextCapture:t.executionContextManager?.isEnabled()??t.config.enableContextCapture??!0,isClientMode:t.isClientMode,shuttingDown:t.shuttingDown,pid:process.pid,sessionId:t.sessionId},instanceId:t.instanceId,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:pb(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 kF(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 yb(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 wF(t)}ae();gp();var Xc={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"},mutate_instances_smart_duplicate:{spacing:"offset"}}},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_ui:{discriminator:"action",validActions:new Set(["design_brief","create_tree","update","delete","preview","check"])},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","screenshot"]),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_open_cloud_assets:{discriminator:"action",validActions:new Set(["credential_status","capabilities","upload","update","info","operation_status"])},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"])},manage_studio:{discriminator:"action",validActions:new Set(["toggle_ui_preview"])}};var Vue={manage_selection_cached:"internal",manage_open_cloud_assets_credential_status:"internal",manage_open_cloud_assets_capabilities:"internal",manage_open_cloud_assets_upload:"internal",manage_open_cloud_assets_update:"internal",manage_open_cloud_assets_info:"internal",manage_open_cloud_assets_operation_status:"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 vb(t){return Vue[t]||"plugin"}var CF=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_camera_screenshot","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_open_cloud_assets_capabilities","manage_open_cloud_assets_credential_status","manage_open_cloud_assets_info","manage_open_cloud_assets_operation_status","manage_open_cloud_assets_update","manage_open_cloud_assets_upload","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_studio_toggle_ui_preview","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","manage_ui_check","manage_ui_create_tree","manage_ui_delete","manage_ui_design_brief","manage_ui_preview","manage_ui_update","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"]),pE=new Set(["system_info_connection","system_info_ping","system_info_usage"]);function qn(t){return CF.has(t)?"pro":"basic"}function Wue(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 Gue=/^Pro action '[^']+' is blocked in Basic mode\. Basic fallback '([^']+)' failed: (.*)$/s,Jue=/^Pro action '[^']+' is blocked in Basic mode\.(?! Basic fallback ')/,Kue=/instance not found|not found in|parent not found/,Yue=/is required|missing required/,Xue=/unknown action|unknown command/,Que=/must be|invalid|cannot/;function ede(t){let e=Gue.exec(t);return e?{fallbackCommand:e[1],reason:e[2]}:null}function tde(t){return Jue.test(t)}function nde(t){let e=t.toLowerCase();return Kue.test(e)?"not_found":Yue.test(e)?"missing_param":Xue.test(e)?"unknown_action":Que.test(e)?"validation":"other"}function fE(t){return t.errorType==="bridge_error"||t.errorType==="command_timeout"||t.errorType==="plugin_runtime"||t.errorType==="exception"||t.errorType==="device_blocked"||t.failureOrigin==="bridge"||t.failureOrigin==="plugin_runtime"||t.failureOrigin==="server_exception"||t.failureOrigin==="product_bug"||t.failureOrigin==="unknown"||t.recoverability==="retry"||t.recoverability==="wait_connection"||t.recoverability==="unknown"?"error":"warning"}function Qc(t){if(t.resultSuccess){let e=Wue(t.resultData);return e?{kind:"success_fallback",fallbackCommand:e.executedCommand,...e.alternatives?{alternatives:e.alternatives}:{}}:{kind:"success_ok"}}if(!t.resultError)return{kind:"error"};if(t.tier==="pro"){let e=ede(t.resultError);if(e)return{kind:"blocked_fallback_failed",fallbackCommand:e.fallbackCommand,blockedDetail:nde(e.reason)};if(tde(t.resultError))return{kind:"blocked_unsupported"}}return t.failureClassification?{kind:fE(t.failureClassification)}:{kind:"error"}}function be(t,e,n,i,r={}){return{errorType:t,errorDetail:e,failureOrigin:n,recoverability:i,...r}}function rde(t){let e=t.toLowerCase();return/^\s*compilation error:/.test(e)?be("execution_failed","compile_error","agent_request","code_fix",{validationStage:"plugin_handler"}):/^\s*result too large:/.test(e)?be("execution_failed","result_too_large","agent_request","code_fix",{validationStage:"plugin_handler"}):/^\s*execution timed out after/.test(e)?be("execution_failed","execution_timeout","agent_request","code_fix",{validationStage:"plugin_handler"}):/potentially dangerous infinite loop|blocked in sandbox|access to .* is blocked|forbidden|not allowed|cannot access/.test(e)?be("execution_failed","blocked_source","roblox_policy","code_fix",{validationStage:"plugin_handler"}):null}function IF(t){let e=t.toLowerCase();return/upstream_server_down/.test(e)?be("bridge_error","upstream_down","bridge","wait_connection"):/^http [45]\d\d/.test(e)?be("bridge_error","http_error","bridge","retry"):/duplicate request\s*id/.test(e)?be("bridge_error","duplicate_request_id","bridge","code_fix"):/timeout/.test(e)?be("command_timeout","timeout","bridge","retry"):/econnrefused|socket hang up|econnreset|epipe|network/.test(e)?be("bridge_error","connection","bridge","wait_connection"):/abort/.test(e)?be("command_timeout","aborted","bridge","retry"):/runtime error|traceback|attempt to/.test(e)?be("plugin_runtime","runtime_error","plugin_runtime","code_fix"):/created instance, but failed to set \d+ propert/.test(e)?be("execution_failed","property_apply_failed","agent_request","fix_params",{validationStage:"plugin_handler",partialSuccess:"true"}):/failed to set property/.test(e)?be("execution_failed","property_apply_failed","agent_request","fix_params",{validationStage:"plugin_handler"}):/parent(?: instance)? not found/.test(e)?be("execution_failed","parent_not_found","agent_request","fix_params",{validationStage:"plugin_handler"}):/(?:instance|path|root|source instance|ancestor instance) not found|target\s+(?:path\s+)?(?:['"`][^'"`]+['"`]\s+)?not found|not found in|no synced file found for instance/.test(e)?be("execution_failed","not_found","user_project_state","fix_params",{validationStage:"plugin_handler"}):/is required|missing required/.test(e)?be("execution_failed","missing_param","agent_request","fix_params",{validationStage:"schema"}):/invalid classname|invalid class/.test(e)?be("execution_failed","invalid_class","agent_request","fix_params",{validationStage:"plugin_handler"}):/unknown action|unknown command/.test(e)?be("execution_failed","unknown_action","agent_request","fix_params",{validationStage:"dispatcher"}):/failed to insert asset/.test(e)?be("execution_failed","asset_error","roblox_policy","fix_params",{validationStage:"roblox_api"}):/failed to read property|property access denied/.test(e)?be("execution_failed","property_error","agent_request","fix_params",{validationStage:"plugin_handler"}):/forbidden (?:path|parent path)|exceeds (?:total lines|max(?:imum)?(?: length)?)|is not (?:a|an) /.test(e)?be("execution_failed","validation","agent_request","fix_params",{validationStage:"plugin_handler"}):/sync controller not available|project sync is not initialized|no active sync place|not found in sync cache/.test(e)?be("execution_failed","sync_not_ready","user_project_state","wait_connection",{validationStage:"preflight"}):/cannot capture edit-mode screenshot while a playtest is active|play-mode screenshot capture is not supported/.test(e)?be("execution_failed","unsupported_context","roblox_policy","no_retry",{validationStage:"preflight"}):/editableimage api is not enabled|allow mesh \/ image apis|allow http requests/.test(e)?be("execution_failed","feature_disabled","roblox_policy","no_retry",{validationStage:"preflight"}):/must be|invalid|cannot/.test(e)?be("execution_failed","validation","agent_request","fix_params",{validationStage:"plugin_handler"}):be("execution_failed","other","unknown","unknown")}function el(t,e){if(t==="execute_luau"){let i=rde(e);if(i)return i}let n=IF(e);return t==="execute_luau"&&n.errorDetail==="runtime_error"&&/^\s*runtime error:/i.test(e)?be("execution_failed","runtime_error","agent_request","code_fix"):n}function PF(t){let n=(t instanceof Error?t.message:String(t)).toLowerCase();return/timeout/.test(n)?be("command_timeout","timeout","bridge","retry"):/econnrefused|socket hang up|network|econnreset|epipe/.test(n)?be("bridge_error","connection","bridge","wait_connection"):/eaddrinuse/.test(n)?be("bridge_error","port_in_use","bridge","no_retry"):be("exception","unexpected","server_exception","code_fix")}_b();import{randomBytes as lde}from"node:crypto";var ude=["manage_ui_create_tree","manage_ui_update","manage_ui_delete"],NF=["manage_ui_preview","manage_ui_check"],dde=["game_genre","ui_role","style_family","layout_family","device_policy","safe_area_policy","existing_ui_strategy"];function bE(t){return t&&typeof t=="object"?t:void 0}function yE(t){return typeof t=="number"&&Number.isFinite(t)?t:0}function Sb(t){let e=bE(t);if(!e)return;let n={};for(let i of dde){let r=e[i];typeof r=="string"&&r.trim().length>0&&(n[i]=r)}return Object.keys(n).length>0?n:void 0}function vE(t){let e=Sb(t.qualityPlanSummary);if(e)return e;let n=bE(t.contextSummary),i=Sb(n?.qualityPlanSummary);if(i)return i;let r=Sb(n?.qualityPlan);if(r)return r;let a=Sb(n?.quality_plan);if(a)return a}function jF(t,e){return e?{...t??{},...e}:t}function pde(t){return typeof t=="string"?{snapshotId:t}:t&&typeof t=="object"?{snapshotId:typeof t.snapshotId=="string"?t.snapshotId:null,...t.designCheckSummary!==void 0&&{designCheckSummary:t.designCheckSummary},...t.capturedAt!==void 0&&{capturedAt:t.capturedAt}}:{snapshotId:null}}function MF(t,e,n,i,r){if(!e)return;let a=yE(e.priority_high),o=yE(e.priority_medium),s=yE(e.priority_low),c=typeof e.total=="number"&&Number.isFinite(e.total)?e.total:a+o+s,l=n??(typeof e.target=="string"?e.target:void 0);return{source:t,...l!==void 0&&{targetPath:l},...typeof i=="string"&&{snapshotId:i},...r!==void 0&&{capturedAt:r},priority_high:a,priority_medium:o,priority_low:s,total:c}}function zF(t,e){if(!e)return t;if(!t)return e;let n=t.targetPath===e.targetPath?t.targetPath:void 0,i=t.snapshotId===e.snapshotId?t.snapshotId:void 0;return{source:e.source,...n!==void 0&&{targetPath:n},...i!==void 0&&{snapshotId:i},...e.capturedAt!==void 0&&{capturedAt:e.capturedAt},priority_high:t.priority_high+e.priority_high,priority_medium:t.priority_medium+e.priority_medium,priority_low:t.priority_low+e.priority_low,total:t.total+e.total}}function fde(t){let e=bE(t);if(e)for(let n of["userMessage","intent","expectedBehavior"]){let i=e[n];if(typeof i=="string"&&i.trim().length>0)return i.trim().slice(0,80)}}function mde(){return{source:"unavailable",priority_high:0,priority_medium:0,priority_low:0,total:0}}function LF(t){let e=t.now??Date.now,n=null,i=null,r=l=>`${l}_${e()}_${lde(3).toString("hex")}`,a=()=>{i&&clearTimeout(i),i=setTimeout(()=>{s("idle_timeout")},t.idleMs)},o=async l=>{try{return pde(await t.capturePreview(l))}catch{return{snapshotId:null}}},s=async l=>{if(!n)return;i&&(clearTimeout(i),i=null);let u=n;n=null;let d={},p;for(let h of u.affectedPaths)if(u.afterSnapshotsPathPolicy[h]==="skip")d[h]=null;else{let y=await o(h);d[h]=y.snapshotId,p=zF(p,MF("after_snapshot",y.designCheckSummary,h,y.snapshotId,y.capturedAt))}let f=p??u.latestDesignCheckSummary??mde(),m={requestId:u.requestId,...u.contextId!==void 0&&{contextId:u.contextId},...u.label!==void 0&&{label:u.label},startedAt:u.startedAtIso,closedAt:new Date(e()).toISOString(),closeReason:l,ok:u.ok,affectedPaths:Array.from(u.affectedPaths).sort(),mutationCount:u.mutationCount,beforeSnapshots:u.beforeSnapshots,afterSnapshots:d,childMutationIds:u.childMutationIds,...u.qualityPlanSummary!==void 0&&{qualityPlanSummary:u.qualityPlanSummary},postChangeDesignCheckSummary:f};await OF(t.placeDir,m)},c=l=>({requestId:r("req"),contextId:l.contextId,label:fde(l.contextSummary),startedAtIso:new Date(e()).toISOString(),startedAtMs:e(),affectedPaths:new Set,beforeSnapshots:{},afterSnapshotsPathPolicy:{},childMutationIds:[],mutationCount:0,ok:!0,qualityPlanSummary:vE(l),latestDesignCheckSummary:void 0,pendingMutation:void 0});return{async beforeMutation(l){if(NF.includes(l.command)){if(n){let d=r("mut");n.qualityPlanSummary=jF(n.qualityPlanSummary,vE(l)),n.pendingMutation={mutationId:d,startedMs:e(),args:l}}return}if(!ude.includes(l.command))return;n&&n.contextId!==l.contextId&&await s("context_change"),n||(n=c(l)),n.qualityPlanSummary=jF(n.qualityPlanSummary,vE(l)),l.targetPath&&!(l.targetPath in n.beforeSnapshots)&&(n.affectedPaths.add(l.targetPath),l.command==="manage_ui_create_tree"?n.beforeSnapshots[l.targetPath]=null:n.beforeSnapshots[l.targetPath]=(await o(l.targetPath)).snapshotId,l.command==="manage_ui_delete"?n.afterSnapshotsPathPolicy[l.targetPath]="skip":l.targetPath in n.afterSnapshotsPathPolicy||(n.afterSnapshotsPathPolicy[l.targetPath]="capture"));let u=r("mut");n.pendingMutation={mutationId:u,startedMs:e(),args:l}},async afterMutation(l){if(!n?.pendingMutation)return;let{mutationId:u,args:d}=n.pendingMutation,p={mutationId:u,requestId:n.requestId,ts:new Date(e()).toISOString(),command:d.command,origin:d.origin,...d.targetPath!==void 0&&{targetPath:d.targetPath},...d.briefId!==void 0&&{briefId:d.briefId},...l.diffSummary!==void 0&&{diffSummary:l.diffSummary},...l.changeDetails!==void 0&&{changeDetails:l.changeDetails},ok:l.ok,...l.error!==void 0&&{error:l.error},durationMs:l.durationMs};if(await AF(t.placeDir,p),n.pendingMutation=void 0,NF.includes(d.command)){n.latestDesignCheckSummary=zF(n.latestDesignCheckSummary,MF("check_results",l.designCheckSummary,d.targetPath,l.snapshotId,l.capturedAt));return}if(n.mutationCount++,n.childMutationIds.push(u),l.ok||(n.ok=!1),!n.contextId){await s("legacy_single");return}a()},async flushNow(){n&&await s("explicit")}}}var CE=100,Yde=12e4,Xde=15e3,i8=new Set(["manage_ui_create_tree","manage_ui_update","manage_ui_delete"]),Qde={Text:{category:"text",label:"\uD14D\uC2A4\uD2B8"},PlaceholderText:{category:"text",label:"\uD50C\uB808\uC774\uC2A4\uD640\uB354"},TextColor3:{category:"color",label:"\uAE00\uC790\uC0C9"},BackgroundColor3:{category:"color",label:"\uBC30\uACBD\uC0C9"},BorderColor3:{category:"color",label:"\uD14C\uB450\uB9AC\uC0C9"},ImageColor3:{category:"color",label:"\uC774\uBBF8\uC9C0\uC0C9"},Size:{category:"size",label:"\uC0AC\uC774\uC988"},TextSize:{category:"size",label:"\uAE00\uC790 \uD06C\uAE30"},Position:{category:"layout",label:"\uC704\uCE58"},AnchorPoint:{category:"layout",label:"\uC575\uCEE4"},Rotation:{category:"layout",label:"\uD68C\uC804"},LayoutOrder:{category:"layout",label:"\uB808\uC774\uC544\uC6C3 \uC21C\uC11C"},Visible:{category:"state",label:"\uD45C\uC2DC \uC0C1\uD0DC"},Active:{category:"state",label:"\uD65C\uC131 \uC0C1\uD0DC"},Interactable:{category:"state",label:"\uC0C1\uD638\uC791\uC6A9 \uC0C1\uD0DC"},Selectable:{category:"state",label:"\uC120\uD0DD \uAC00\uB2A5"},Enabled:{category:"state",label:"\uD65C\uC131\uD654"},Image:{category:"asset",label:"\uC774\uBBF8\uC9C0"},Texture:{category:"asset",label:"\uD14D\uC2A4\uCC98"},SoundId:{category:"asset",label:"\uC0AC\uC6B4\uB4DC"}},epe={text:"\uD14D\uC2A4\uD2B8",color:"\uCEEC\uB7EC",size:"\uC0AC\uC774\uC988",layout:"\uB808\uC774\uC544\uC6C3",state:"\uC0C1\uD0DC",asset:"\uC5D0\uC14B",property:"\uC18D\uC131"},tpe=Object.entries(Xc).reduce((t,[e,n])=>{for(let i of n.validActions)t[`${e}_${i}`]=e;return t},{});function XF(t){return{toolName:tpe[t]||t,commandName:t}}function QF(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 kb(t,e){if(t!=="manage_sync_status_current_place")return e;let{placeId:n,...i}=e;return i}async function npe(t,e){let n=t.syncController;if(!n)return null;try{let i=e.placeId,r=n.getDefaultRuntimePlaceId(),a=null;if(typeof i=="number"&&Number.isFinite(i))a=i;else if(typeof i=="string"&&i.trim().length>0){let s=Number.parseInt(i,10);Number.isFinite(s)&&(a=s)}if(a===null&&(a=r),a===null||!Number.isFinite(a))return null;let o=a===r?n.getDefaultRuntimePlaceName():null;return typeof o=="string"&&o.length>0?await n.config.resolvePlaceRoot(a,o):n.config.getPlaceRoot(a)}catch{return null}}function rpe(t){let e=t.tree;if(!e||typeof e!="object")return;let n=e,i=typeof n.name=="string"&&n.name.length>0?n.name:void 0;return i?`${typeof n.parent=="string"&&n.parent.length>0?n.parent:"StarterGui"}.${i}`:void 0}function ipe(t,e){let n=e.targetPath;if(typeof n=="string"&&n.length>0)return n;if(t==="manage_ui_create_tree")return rpe(e)}function ape(t){let e=Qde[t];if(e)return e;let n=t.toLowerCase();return n.includes("color")?{category:"color",label:"\uC0C9\uC0C1"}:t==="Size"||n.endsWith("size")||n.includes("scale")?{category:"size",label:"\uC0AC\uC774\uC988"}:n.includes("position")||n.includes("anchor")||n.includes("rotation")||n.includes("layout")?{category:"layout",label:"\uB808\uC774\uC544\uC6C3"}:n.includes("visible")||n.includes("active")||n.includes("enabled")||n.includes("selectable")||n.includes("interactable")?{category:"state",label:"\uC0C1\uD0DC"}:n.includes("image")||n.includes("texture")||n.includes("sound")||n.includes("asset")?{category:"asset",label:"\uC5D0\uC14B"}:n.includes("text")||n.includes("placeholder")?{category:"text",label:"\uD14D\uC2A4\uD2B8"}:{category:"property",label:t}}function e8(t,e){return Object.prototype.hasOwnProperty.call(t,e)}function ope(t){if(!t||typeof t!="object")return new Map;let e=t,n=e.changed_properties??e.changedProperties;if(!Array.isArray(n))return new Map;let i=new Map;for(let r of n){if(!r||typeof r!="object")continue;let a=r,o=a.property;if(typeof o!="string"||o.length===0)continue;let s=e8(a,"before"),c=e8(a,"after");i.set(o,{...s&&{before:a.before},...c&&{after:a.after},hasBefore:s,hasAfter:c})}return i}function a8(t,e){let i=t.changes?.properties;if(!i||typeof i!="object")return;let r=ope(e),a=Object.entries(i).map(([o,s])=>{let c=ape(o),l=r.get(o),u={property:o,category:c.category,label:c.label,after:l?.hasAfter?l.after:s};return l?.hasBefore&&(u.before=l.before),u});return a.length>0?a:void 0}function o8(t){if(!t||t.length===0)return;let e=new Map;for(let r of t)e.set(r.category,(e.get(r.category)??0)+1);let i=["text","color","size","layout","state","asset","property"].map(r=>{let a=e.get(r)??0;return a>0?`${epe[r]} ${a}\uAC1C`:null}).filter(r=>r!==null);return i.length>0?`${i.join(", ")} \uBCC0\uACBD`:void 0}function t8(t,e){if(t==="manage_ui_update"){let n=e.changes,i=o8(a8(e));if(i)return i;let r=n?.properties?Object.keys(n.properties).length:0,a=Array.isArray(n?.addChildren)?n.addChildren.length:0,o=Array.isArray(n?.removeChildren)?n.removeChildren.length:0;return`${r} props, +${a}, -${o}`}if(t==="manage_ui_create_tree")return"tree \uC0DD\uC131";if(t==="manage_ui_delete")return"\uC81C\uAC70\uB428"}function Tp(t){return t&&typeof t=="object"?t:void 0}function Cb(t,e){let n=t[e];return typeof n=="number"&&Number.isFinite(n)?n:0}function s8(t){let e=Tp(t),n=Tp(e?.design_check_summary);if(n)return{priority_high:Cb(n,"priority_high"),priority_medium:Cb(n,"priority_medium"),priority_low:Cb(n,"priority_low"),total:Cb(n,"total"),...typeof n.scope=="string"&&{scope:n.scope},...typeof n.target=="string"&&{target:n.target}}}function c8(t){let n=Tp(t)?.snapshot_id;return typeof n=="string"?n:null}function n8(t){if(typeof t=="string"&&t.trim().length>0)return t;if(typeof t=="number"&&Number.isFinite(t)){let e=t<1e10?t*1e3:t;return new Date(e).toISOString()}}function l8(t){let e=Tp(t),n=Tp(e?.meta);return n8(n?.captured_at)??n8(n?.evaluated_at)}function spe(t,e){let n=t.uiStudioRecorders.get(e);if(n)return n;let i=LF({placeDir:e,idleMs:Xde,capturePreview:async r=>{try{let a=await To(t,"manage_ui_preview",{targetPath:r},PE());if(!a.success||!a.data||typeof a.data!="object")return null;let o=s8(a.data),s=l8(a.data);return{snapshotId:c8(a.data),...o!==void 0&&{designCheckSummary:o},...s!==void 0&&{capturedAt:s}}}catch{return null}}});return t.uiStudioRecorders.set(e,i),i}async function cpe(t,e,n){if(!i8.has(e))return null;let i=await npe(t,n);if(!i)return null;let r=spe(t,i),a=e,o=typeof n.contextId=="string"?n.contextId:void 0,s=n.contextSummary,c=s!==void 0&&typeof s=="object"?s:void 0,l=ipe(a,n),u=typeof n.briefId=="string"?n.briefId:void 0,d={command:a,...o!==void 0&&{contextId:o},...c!==void 0&&{contextSummary:c},...l!==void 0&&{targetPath:l},...u!==void 0&&{briefId:u},origin:n.__origin==="dashboard"?"dashboard":"ai"};try{return await r.beforeMutation(d),{recorder:r,command:a,params:n,startedAt:Date.now()}}catch(p){return g.debug("UI Studio recorder beforeMutation \uC2E4\uD328 (\uBB34\uC2DC)",{error:p}),null}}async function r8(t,e){if(t)try{let n=t.command==="manage_ui_update"?a8(t.params,e.data):void 0,i=t.command==="manage_ui_update"?o8(n)??t8(t.command,t.params):t8(t.command,t.params),r=s8(e.data),a=c8(e.data),o=l8(e.data);await t.recorder.afterMutation({ok:e.success,durationMs:Date.now()-t.startedAt,...i!==void 0&&{diffSummary:i},...n!==void 0&&{changeDetails:n},...e.error!==void 0&&{error:e.error},...r!==void 0&&{designCheckSummary:r},...a!==null&&{snapshotId:a},...o!==void 0&&{capturedAt:o}})}catch(n){g.debug("UI Studio recorder afterMutation \uC2E4\uD328 (\uBB34\uC2DC)",{error:n})}}async function IE(t,e,n,i,r,a){if(!t.historyManager)return;let o=!r.success&&r.error?el(n,r.error):void 0,s=Qc({resultSuccess:r.success,resultData:r.data,resultError:r.error,tier:qn(n),...o?{failureClassification:o}:{}});if(r.success){let l=s.kind==="success_fallback"&&r.data&&typeof r.data=="object"?{...r.data,outcomeStatus:"fallback",requestedCommand:n,executedCommand:s.fallbackCommand,...s.alternatives?{alternatives:s.alternatives}:{}}:r.data;await t.historyManager.recordSuccess(e,kb(n,i),l,a,n);return}if(s.kind==="blocked_unsupported"){await t.historyManager.recordBlockedOutcome(e,kb(n,i),r.error||"Pro action blocked in Basic mode","blocked_unsupported",{executionTimeMs:a,command:n});return}if(s.kind==="blocked_fallback_failed"){await t.historyManager.recordBlockedOutcome(e,kb(n,i),r.error||"Pro action blocked in Basic mode","blocked_fallback_failed",{executionTimeMs:a,command:n,...s.fallbackCommand?{fallbackCommand:s.fallbackCommand}:{},...s.blockedDetail?{blockedDetail:s.blockedDetail}:{}});return}await(s.kind==="warning"?t.historyManager.recordWarning.bind(t.historyManager):t.historyManager.recordFailure.bind(t.historyManager))(e,kb(n,i),r.error||"Unknown error",a,o?.errorType.toUpperCase(),n)}function lpe(t,e,n){if(!e)return;let i=t.pluginClients.get(e);i?.inFlightRequestId===n&&(i.inFlightRequestId=void 0)}function upe(t,e,n){if(g.debug("Broadcasting command",{command:e.data.command,requestId:e.data.requestId,targetClientId:n||"all",activeWebsocketClients:_o(t).length}),n){let i=t.pendingCommands.get(n)||[];i.length>=CE&&i.shift(),i.push(e),t.pendingCommands.set(n,i)}else{let i=_o(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>=CE&&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>=CE&&t.globalPendingCommands.shift(),t.globalPendingCommands.push(e)}Bv(t,n)}async function u8(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=XF(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==="manage_camera_play_screenshot"){let m="manage_camera.play_screenshot is not supported. Screenshot capture is available only in Edit mode via manage_camera.screenshot.",h=Date.now()-i;t.historyManager&&await t.historyManager.recordFailure("manage_camera",a,m,h,"UNSUPPORTED_COMMAND",r),n.json({requestId:s,success:!1,error:m});return}if(r==="get_cached_selection"){let m=a.maxAge,h=xp(t,m!==void 0?m:3e4),y=Date.now()-i;if(!h){let x={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,x.data,y,c.commandName),n.json(x);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 yb(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(vb(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=QF(m,h.data);t.historyManager&&await IE(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 d=await To(t,r,a,s,l),p=Date.now()-i,f={...QF(a,d.data),__sessionId:u};t.historyManager&&await IE(t,c.toolName,c.commandName,f,d,p),n.json(d)}catch(o){let s=Date.now()-i,c=wn(o);if(t.historyManager&&r){let l=XF(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 d8(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=PE(),{action:l,...u}=r;try{t.serverLastCommandAt=Date.now();let d=await To(t,s,u,c),p=Date.now()-i;t.historyManager&&await IE(t,"manage_properties",s,u,d,p),n.json(d)}catch(d){let p=Date.now()-i,f=wn(d);t.historyManager&&await t.historyManager.recordFailure("manage_properties",u,f,p,"EXCEPTION",s),g.error("/api/properties \uC694\uCCAD \uCC98\uB9AC \uC2E4\uD328",d),n.status(500).json({success:!1,error:f})}}async function To(t,e,n,i,r){if(g.debug("Executing command locally",{command:e,requestId:i}),s6(t,i)){let l=`Duplicate requestId '${i}' for command '${e}'. Generate a unique requestId for each command.`;return g.warn("Duplicate execute request rejected",{command:e,requestId:i}),{requestId:i,success:!1,error:l}}let a=i8.has(e)?await cpe(t,e,n):null,o=t.config.requestTimeout,s=r?.timeout??Math.max(t.config.requestTimeout,Yde),c=new Promise((l,u)=>{let d={requestId:i,command:e,params:n,timestamp:Date.now(),queueTimeoutMs:o,ackedTimeoutMs:s,state:xn.QUEUED,resolve:l,reject:u,timeoutId:null};t.commandQueue.set(i,d),d.timeoutId=setTimeout(()=>{t.commandQueue.delete(d.requestId),lpe(t,d.targetClientId,d.requestId),d.reject(new Error(`Command timeout after ${o}ms while ${d.state}: ${d.command}`))},o)});return upe(t,{event:"command",id:PE(),data:{command:e,requestId:i,params:n}}),c.then(async l=>{let u=l;if(e==="manage_ui_preview"&&l.success&&l.data)try{let{processPreviewResult:d,resolvePreviewPlaceDirFromCtx:p}=await Promise.resolve().then(()=>(kE(),wE)),f=await p(t,n),m=await d("manage_ui_preview",l.data,f);u={...l,data:m.data,...m.mcpContent?{mcpContent:m.mcpContent}:{}}}catch(d){g.warn("preview post-processing failed",{error:d instanceof Error?d.message:String(d)})}if(e==="manage_ui_check"&&l.success&&l.data&&n.includeVisualAnalysis===!0)try{let{resolvePreviewPlaceDirFromCtx:d}=await Promise.resolve().then(()=>(kE(),wE)),{processCheckResult:p}=await Promise.resolve().then(()=>(YF(),KF)),f=await d(t,n),m=await p("manage_ui_check",l.data,f,n);u={...u,data:m.data}}catch(d){g.warn("check visual post-processing failed",{error:d instanceof Error?d.message:String(d)})}return u}).then(async l=>(await r8(a,{success:l.success,...l.data!==void 0&&{data:l.data},...l.error!==void 0&&{error:l.error}}),l),async l=>{throw await r8(a,{success:!1,error:wn(l)}),l})}ae();function Ib(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),ct(t,"license",n)}function jr(t,e,n,i){t.status(e).json({success:!1,error:{code:n,message:i}})}function dpe(t){if(typeof t!="string")return;let e=t.trim().toLowerCase();return e.length>0?e:void 0}function Pb(t,e){if(e==="plugin")return t;let n={...t};return delete n.sessionToken,n}function $p(t,e,n,i){let r=dpe(i??e.query.provider)??"auto";return t.licenseState?.supportsProvider(r)===!1?(jr(n,400,"LICENSE_PROVIDER_UNSUPPORTED",`provider "${r}" is not supported`),null):r}function Op(t,e){return t.licenseState?!0:(jr(e,503,"LICENSE_NOT_INITIALIZED","License system is not initialized"),!1)}function ppe(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(!Op(t,n))return;let r=e.body,a=typeof r?.licenseKey=="string"?r.licenseKey:"";if(!a.trim()){jr(n,400,"LICENSE_KEY_REQUIRED","licenseKey is required");return}let o=$p(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"),Ib(t,{provider:u.provider,tier:u.canUsePro?"pro":"basic",status:u.status,canUsePro:u.canUsePro}),n.json(Pb(u,i))}).catch(u=>{g.warn("License activate failed",{error:u instanceof Error?u.message:"unknown_error"}),jr(n,502,"LICENSE_ACTIVATE_FAILED",u instanceof Error?u.message:"License activation failed")})}function Dp(t,e,n,i="plugin"){if(!Op(t,n))return;let r=$p(t,e,n);if(r)try{let a=t.licenseState.getStatus(r);t.analyticsManager?.setTier(a.canUsePro?"pro":"basic"),n.json(Pb(a,i))}catch(a){g.warn("License status check failed",{error:a instanceof Error?a.message:"unknown_error"}),jr(n,502,"LICENSE_STATUS_FAILED",a instanceof Error?a.message:"License status check failed")}}function Np(t,e,n,i="plugin"){if(!Op(t,n))return;let r=e.body,a=$p(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"),Ib(t,{provider:s.provider,tier:s.canUsePro?"pro":"basic",status:s.status,canUsePro:s.canUsePro}),n.json(Pb(s,i))}).catch(s=>{g.warn("License refresh failed",{error:s instanceof Error?s.message:"unknown_error"}),jr(n,502,"LICENSE_REFRESH_FAILED",s instanceof Error?s.message:"License refresh failed")})}function jp(t,e,n,i="plugin"){if(!Op(t,n))return;let r=e.body,a=$p(t,e,n,r?.provider);a&&t.licenseState.resetGateway({provider:a,clientType:i}).then(o=>{t.analyticsManager?.setTier("basic"),Ib(t,{provider:o.provider,tier:"basic",status:o.status,canUsePro:o.canUsePro,cleared:!0}),n.json(Pb(o,i))}).catch(o=>{g.warn("License reset failed",{error:o instanceof Error?o.message:"unknown_error"}),jr(n,502,"LICENSE_RESET_FAILED",o instanceof Error?o.message:"License reset failed")})}function EE(t,e,n){if(!Op(t,n))return;let i=e.body,r=$p(t,e,n,i?.provider);if(!r)return;if(i?.clientType!=="plugin"){jr(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=ppe(i?.snapshot)?i.snapshot:void 0;if(!o&&!a){jr(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"),Ib(t,{provider:c.provider,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"}),jr(n,502,"LICENSE_BOOTSTRAP_FAILED",c instanceof Error?c.message:"License bootstrap failed")})}function p8(t){t.app.post("/license/bootstrap",(e,n)=>EE(t,e,n)),t.app.post("/license/activate",(e,n)=>Ap(t,e,n,"plugin")),t.app.post("/license/refresh",(e,n)=>Np(t,e,n,"plugin")),t.app.post("/license/reset",(e,n)=>jp(t,e,n,"plugin")),t.app.get("/license/status",(e,n)=>Dp(t,e,n,"plugin"))}mt();ae();import{promises as Eb}from"fs";import RE from"path";function fpe(t){let e=t.config.appDataDir??ft();return RE.join(e,"observability","logs")}async function al(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=fpe(t);await Eb.mkdir(a,{recursive:!0});let c=`plugin-${new Date().toISOString().split("T")[0]}.log`,l=RE.join(a,c),u=RE.join(a,"current.log"),d=r.logs.map(p=>`[${new Date(p.timestamp*1e3).toISOString().replace("T"," ").substring(0,19)}] [${p.level}] ${p.message}`).join(`
|
|
504
|
+
`),await Dc(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=De.join(r,`explorer_tmp_${n}`);try{await Sn.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.activeSyncIdentityScope=null,e.tmpWriter&&(e.tmpWriter.stopChangeLogFlusher(),e.tmpWriter=null),this.ctx.activeFullSyncPlaceId===e.placeId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(e.placeId))},nF);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}getPreferredResolvedName(e,n,i){let r=typeof i.sessionDebugId=="string"?i.sessionDebugId:"";if(!n||r==="")return;let a=e.index.getSessionDebugMapping(n,r);if(!a)return;let o=yt(i.path),s=it(i.path)||i.name;if(!(a.parentPath!==o||a.originalName!==s))return a.resolvedName}recordSessionDebugMapping(e,n,i,r){let a=typeof i.sessionDebugId=="string"?i.sessionDebugId:"";!n||a===""||!e.tmpIndex||e.tmpIndex.setSessionDebugMapping({scope:n,sessionDebugId:a,instancePath:i.path,parentPath:yt(i.path),originalName:it(i.path)||i.name,siblingIndex:Math.max(1,i.siblingIndex??1),resolvedName:r,className:i.className,updatedAt:new Date().toISOString()})}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=Mt(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?Mt(n).map(i=>e.tmpIndex.sanitizeName(i)):Mt(n)}rewritePendingEffectivePaths(e,n,i,r){let a=De.relative(n,i).split(De.sep).filter(l=>l.length>0),o=De.relative(n,r).split(De.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=lt(["game",...a]),c=lt(["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(d=>d.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 Sn.readdir(e,{withFileTypes:!0});for(let i of n)if(i.isDirectory()){if(i.name.startsWith("explorer_tmp_")){let r=De.join(e,i.name);g.warn("Removing stale temp directory from crashed sync",{dir:i.name});try{await Sn.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=De.join(e,i.name);try{let a=await Sn.readdir(r,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=De.join(r,o.name);g.warn("Removing stale temp directory from crashed sync",{dir:`${i.name}/${o.name}`});try{await Sn.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 ur from"path";import{promises as sb}from"fs";ae();var cb=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 d=ur.resolve(s,u);if(!nb(s,d)){l.push({path:u,error:"Path is outside the place root"});continue}try{let p=await sb.readFile(d,"utf-8"),f=o.index.computeHash(p);o.index.updateHashByValue(d,f),o.index.updateFileHashByValue(d,f),c++}catch(p){let f=p.code;if(f==="ENOENT"){o.index.removeHash(d),o.index.removeHashesUnder(d);let m=this.resolveInstancePathForAppliedPath(o.index,d);if(m){let h=it(m),y=yt(m);y&&h&&await o.writer.removeFromTree(y,h)}c++}else f==="EISDIR"?c++:l.push({path:u,error:p instanceof Error?p.message:String(p)})}}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(!nb(o,ur.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 p=r.match(/^place_(\d+)(?:_[^/]+)?\//);if(p){let f=parseInt(p[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=ur.resolve(c,r);if(!nb(c,l)){n.status(403).json({error:"Forbidden",message:"Path is outside the place root"});return}let u=s.index,d=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 sb.mkdir(ur.dirname(l),{recursive:!0});let p=i.studioContent,f=u.computeHash(p);s.fileWatcher?.suppressPath(l,f),await sb.writeFile(l,p,"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 p=await sb.readFile(l,"utf-8"),f=u.computeHash(p);u.resolveFile(l,"apply-file",f),await u.saveToDisk();let m=d.getFileType(l),h=d.resolveInstancePathFromFile(l);n.status(200).json({status:"resolved",resolution:"apply-file",fsPath:r,instancePath:h,fileType:m,content:p});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=ur.relative(r,n);if(a.startsWith("..")||a===""||ur.isAbsolute(a))return null;let o=a.split(ur.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=ur.basename(n),c=ur.dirname(n),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(Nv.some(f=>u.endsWith(f))||u==="_tree.json")return null;let d=["game"],p=r;for(let f of o){p=ur.join(p,f);let m=ur.dirname(p);d.push(e.getOriginalNameForDir(m,f))}return lt(d)}};ae();function cF(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 Fue(t){return t.type==="instanceAdded"||t.type==="instanceRemoved"||t.type==="instanceRenamed"||t.type==="instanceMoved"||t.type==="scriptSourceChanged"}var Ar=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;reverseDetectorFactory;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;currentConnectedPlaceId=null;currentConnectedPlaceName=null;constructor(e,n={}){this.config=new Ov(e,n),this.reverseDetectorFactory=n.reverseDetectorFactory??(i=>new Qv(i.explorerRoot,i.syncIndex,{placeId:i.placeId})),this.apiHandler=new ab(this),this.changeProcessor=new rb(this),this.initHandler=new ob(this),this.reverseHandler=new cb(this),this.places=new Yv({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:at}),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,i.activeSyncIdentityScope=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=rE.join(r,"explorer");await Jc.mkdir(r,{recursive:!0}),await Jc.mkdir(a,{recursive:!0});let o=new pa(r,a);await o.loadFromDisk();let s=new Vc(this.config,o,e),c=new Xv(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:{...yo},applyModes:{...vo},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null,activeSyncIdentityScope:null},this.places.set(e,i),g.info("Created new place context",{placeId:e,placeRoot:r})}return i}async handleSyncInit(e,n){try{cF(e.body);let i=iF(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{cF(e.body);let i=aF(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=tF(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,d=!1,p=new Map;for(let m of s)try{let h=await this.changeProcessor.processChangeForPlace(o,m,p);h?l.push(h):(u++,Fue(m)&&(d=!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 p.values())try{await eb(o,m)}catch(h){c.push({path:m.instancePath,error:h instanceof Error?h.message:String(h)})}if(d)try{let m=this.config.getSyncRoot(),h=this.config.getPlaceRoot(a);await Mv(m,h),await Dc(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>=rF&&(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:at,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:at,fileWatcherActive:c,forwardOnlyClasses:[...Ac]};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:at}),a.fileWatcher.beginFullSyncReplacement()),a.incompleteSyncTimer&&(clearTimeout(a.incompleteSyncTimer),a.incompleteSyncTimer=null),o){let l=this.config.getPlaceRoot(r),u=rE.join(l,`explorer_tmp_${o}`);await Jc.rm(u,{recursive:!0,force:!0}).catch(()=>{})}a.tmpWriter&&(a.tmpWriter.stopChangeLogFlusher(),a.tmpWriter=null),a.tmpIndex=null,a.collisionDirMap=null,a.activeSyncIdentityScope=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."+Uue().slice(0,8);try{await Jc.writeFile(i,n,"utf-8"),await Jc.rename(i,e)}catch(r){throw await Jc.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:at});return}g.info("startFileWatcherForPlace: resuming paused reverse detector",{placeId:e.placeId,mode:at}),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:at})}).catch(i=>{g.warn("Failed to start reverse detector",{placeId:e.placeId,mode:at,error:i instanceof Error?i.message:String(i)}),e.fileWatcher===n&&(e.fileWatcher=null)})}createReverseChangeDetector(e){return this.reverseDetectorFactory({explorerRoot:rE.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=N2(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 que}from"crypto";function lF(t,e){let n=que(),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,shuttingDown:!1,onUpstreamPermanentlyDown:null,clientModeIdleTimeoutMs:t.clientModeIdleTimeoutMs??36e5,clientModeIdleWatchdogTimer:null,shutdownFn:null,historyManager:null,analyticsManager:null,executionContextManager:null,licenseState:null,syncController:null,internalCommandExecutor:null,dashboardFolderPicker:null,assetLibraryOpenCloudServiceFactory:null,activeSyncOwnerInstanceId:null,activeProjectRoot:null,pendingDashboardSyncRootPin:null,playtestControlCommand:null,dashboardSyncRootSwitchInFlight:!1,uiStudioRecorders:new Map,aiClientName:"",pluginVersion:"",syncedSessionToken:null,serverLastCommandAt:null}}var uF=xt(oa(),1);ae();function dF(t){let e=uF.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()})}ic();import{randomUUID as PE}from"crypto";ae();function Bue(){let t=process.env.WEPPY_ROBLOX_MCP_VERSION?.trim();return t||null}var ot=Bue()??"2.7.14";bp();function pb(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 yF(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 vF(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 bF(t,e,n){let i=parseInt(e.query.maxAge)||3e4,r=xp(t,i);r?n.json({cached:!0,...r}):n.json({cached:!1,message:"No cached selection available"})}function xp(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 uE(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)&&zP(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:ot,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:pb(t)}}function dE(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&&(ct(t,"connection",{clientId:e,status:"disconnected"}),Lc(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 fb(t,e,n){try{let i=e.body;if(!i.clientId){n.status(400).json({error:"Missing clientId"});return}let r=uE(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 mb(t,e,n){let i=e.body?.clientId;if(!i){n.status(400).json({error:"Missing clientId"});return}let r=dE(t,i);n.json({status:"ok",existed:r})}function xF(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),ct(t,"mcp_status",{aiClientName:a.aiClientName??"Unknown",instanceId:i.instanceId,status:"registered"}),Lc(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:ot,mcpInstanceCount:t.mcpInstances.size+1})}catch(i){g.error("Error registering MCP instance",i),n.status(500).json({error:"Internal server error"})}}function _F(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&&(ct(t,"mcp_status",{aiClientName:r?.aiClientName??"Unknown",instanceId:i,status:"unregistered"}),Lc(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 SF(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 wF(t){let e=3e4,n=Date.now(),i=or({appDataDir:t.config.appDataDir});for(let[r,a]of t.pluginClients)n-a.lastSeen>e&&(t.pluginClients.delete(r),t.pendingCommands.delete(r));return yF(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:ot,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 hb(t,e){e.json(wF(t))}function gb(t,e,n){let i=e.query.instanceId;i&&t.mcpInstances.has(i)&&(t.mcpInstances.get(i).lastSeen=Date.now()),yF(t);let r=_o(t),s={...{status:"online",connectedClients:r.length,queuedCommands:g6(t),uptime:Date.now()-t.startTime,version:ot,enableContextCapture:t.executionContextManager?.isEnabled()??t.config.enableContextCapture??!0,isClientMode:t.isClientMode,shuttingDown:t.shuttingDown,pid:process.pid,sessionId:t.sessionId},instanceId:t.instanceId,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:pb(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 kF(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 yb(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 wF(t)}ae();gp();var Xc={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"},mutate_instances_smart_duplicate:{spacing:"offset"}}},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_ui:{discriminator:"action",validActions:new Set(["design_brief","create_tree","update","delete","preview","check"])},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","screenshot"]),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_open_cloud_assets:{discriminator:"action",validActions:new Set(["credential_status","capabilities","upload","update","info","operation_status"])},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"])},manage_studio:{discriminator:"action",validActions:new Set(["toggle_ui_preview"])}};var Vue={manage_selection_cached:"internal",manage_open_cloud_assets_credential_status:"internal",manage_open_cloud_assets_capabilities:"internal",manage_open_cloud_assets_upload:"internal",manage_open_cloud_assets_update:"internal",manage_open_cloud_assets_info:"internal",manage_open_cloud_assets_operation_status:"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 vb(t){return Vue[t]||"plugin"}var CF=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_camera_screenshot","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_open_cloud_assets_capabilities","manage_open_cloud_assets_credential_status","manage_open_cloud_assets_info","manage_open_cloud_assets_operation_status","manage_open_cloud_assets_update","manage_open_cloud_assets_upload","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_studio_toggle_ui_preview","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","manage_ui_check","manage_ui_create_tree","manage_ui_delete","manage_ui_design_brief","manage_ui_preview","manage_ui_update","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"]),pE=new Set(["system_info_connection","system_info_ping","system_info_usage"]);function qn(t){return CF.has(t)?"pro":"basic"}function Wue(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 Gue=/^Pro action '[^']+' is blocked in Basic mode\. Basic fallback '([^']+)' failed: (.*)$/s,Jue=/^Pro action '[^']+' is blocked in Basic mode\.(?! Basic fallback ')/,Kue=/instance not found|not found in|parent not found/,Yue=/is required|missing required/,Xue=/unknown action|unknown command/,Que=/must be|invalid|cannot/;function ede(t){let e=Gue.exec(t);return e?{fallbackCommand:e[1],reason:e[2]}:null}function tde(t){return Jue.test(t)}function nde(t){let e=t.toLowerCase();return Kue.test(e)?"not_found":Yue.test(e)?"missing_param":Xue.test(e)?"unknown_action":Que.test(e)?"validation":"other"}function fE(t){return t.errorType==="bridge_error"||t.errorType==="command_timeout"||t.errorType==="plugin_runtime"||t.errorType==="exception"||t.errorType==="device_blocked"||t.failureOrigin==="bridge"||t.failureOrigin==="plugin_runtime"||t.failureOrigin==="server_exception"||t.failureOrigin==="product_bug"||t.failureOrigin==="unknown"||t.recoverability==="retry"||t.recoverability==="wait_connection"||t.recoverability==="unknown"?"error":"warning"}function Qc(t){if(t.resultSuccess){let e=Wue(t.resultData);return e?{kind:"success_fallback",fallbackCommand:e.executedCommand,...e.alternatives?{alternatives:e.alternatives}:{}}:{kind:"success_ok"}}if(!t.resultError)return{kind:"error"};if(t.tier==="pro"){let e=ede(t.resultError);if(e)return{kind:"blocked_fallback_failed",fallbackCommand:e.fallbackCommand,blockedDetail:nde(e.reason)};if(tde(t.resultError))return{kind:"blocked_unsupported"}}return t.failureClassification?{kind:fE(t.failureClassification)}:{kind:"error"}}function be(t,e,n,i,r={}){return{errorType:t,errorDetail:e,failureOrigin:n,recoverability:i,...r}}function rde(t){let e=t.toLowerCase();return/^\s*compilation error:/.test(e)?be("execution_failed","compile_error","agent_request","code_fix",{validationStage:"plugin_handler"}):/^\s*result too large:/.test(e)?be("execution_failed","result_too_large","agent_request","code_fix",{validationStage:"plugin_handler"}):/^\s*execution timed out after/.test(e)?be("execution_failed","execution_timeout","agent_request","code_fix",{validationStage:"plugin_handler"}):/potentially dangerous infinite loop|blocked in sandbox|access to .* is blocked|forbidden|not allowed|cannot access/.test(e)?be("execution_failed","blocked_source","roblox_policy","code_fix",{validationStage:"plugin_handler"}):null}function IF(t){let e=t.toLowerCase();return/upstream_server_down/.test(e)?be("bridge_error","upstream_down","bridge","wait_connection"):/^http [45]\d\d/.test(e)?be("bridge_error","http_error","bridge","retry"):/duplicate request\s*id/.test(e)?be("bridge_error","duplicate_request_id","bridge","code_fix"):/timeout/.test(e)?be("command_timeout","timeout","bridge","retry"):/econnrefused|socket hang up|econnreset|epipe|network/.test(e)?be("bridge_error","connection","bridge","wait_connection"):/abort/.test(e)?be("command_timeout","aborted","bridge","retry"):/runtime error|traceback|attempt to/.test(e)?be("plugin_runtime","runtime_error","plugin_runtime","code_fix"):/created instance, but failed to set \d+ propert/.test(e)?be("execution_failed","property_apply_failed","agent_request","fix_params",{validationStage:"plugin_handler",partialSuccess:"true"}):/failed to set property/.test(e)?be("execution_failed","property_apply_failed","agent_request","fix_params",{validationStage:"plugin_handler"}):/parent(?: instance)? not found/.test(e)?be("execution_failed","parent_not_found","agent_request","fix_params",{validationStage:"plugin_handler"}):/(?:instance|path|root|source instance|ancestor instance) not found|target\s+(?:path\s+)?(?:['"`][^'"`]+['"`]\s+)?not found|not found in|no synced file found for instance/.test(e)?be("execution_failed","not_found","user_project_state","fix_params",{validationStage:"plugin_handler"}):/is required|missing required/.test(e)?be("execution_failed","missing_param","agent_request","fix_params",{validationStage:"schema"}):/invalid classname|invalid class/.test(e)?be("execution_failed","invalid_class","agent_request","fix_params",{validationStage:"plugin_handler"}):/unknown action|unknown command/.test(e)?be("execution_failed","unknown_action","agent_request","fix_params",{validationStage:"dispatcher"}):/failed to insert asset/.test(e)?be("execution_failed","asset_error","roblox_policy","fix_params",{validationStage:"roblox_api"}):/failed to read property|property access denied/.test(e)?be("execution_failed","property_error","agent_request","fix_params",{validationStage:"plugin_handler"}):/forbidden (?:path|parent path)|exceeds (?:total lines|max(?:imum)?(?: length)?)|is not (?:a|an) /.test(e)?be("execution_failed","validation","agent_request","fix_params",{validationStage:"plugin_handler"}):/sync controller not available|project sync is not initialized|no active sync place|not found in sync cache/.test(e)?be("execution_failed","sync_not_ready","user_project_state","wait_connection",{validationStage:"preflight"}):/cannot capture edit-mode screenshot while a playtest is active|play-mode screenshot capture is not supported/.test(e)?be("execution_failed","unsupported_context","roblox_policy","no_retry",{validationStage:"preflight"}):/editableimage api is not enabled|allow mesh \/ image apis|allow http requests/.test(e)?be("execution_failed","feature_disabled","roblox_policy","no_retry",{validationStage:"preflight"}):/must be|invalid|cannot/.test(e)?be("execution_failed","validation","agent_request","fix_params",{validationStage:"plugin_handler"}):be("execution_failed","other","unknown","unknown")}function el(t,e){if(t==="execute_luau"){let i=rde(e);if(i)return i}let n=IF(e);return t==="execute_luau"&&n.errorDetail==="runtime_error"&&/^\s*runtime error:/i.test(e)?be("execution_failed","runtime_error","agent_request","code_fix"):n}function PF(t){let n=(t instanceof Error?t.message:String(t)).toLowerCase();return/timeout/.test(n)?be("command_timeout","timeout","bridge","retry"):/econnrefused|socket hang up|network|econnreset|epipe/.test(n)?be("bridge_error","connection","bridge","wait_connection"):/eaddrinuse/.test(n)?be("bridge_error","port_in_use","bridge","no_retry"):be("exception","unexpected","server_exception","code_fix")}_b();import{randomBytes as lde}from"node:crypto";var ude=["manage_ui_create_tree","manage_ui_update","manage_ui_delete"],NF=["manage_ui_preview","manage_ui_check"],dde=["game_genre","ui_role","style_family","layout_family","device_policy","safe_area_policy","existing_ui_strategy"];function bE(t){return t&&typeof t=="object"?t:void 0}function yE(t){return typeof t=="number"&&Number.isFinite(t)?t:0}function Sb(t){let e=bE(t);if(!e)return;let n={};for(let i of dde){let r=e[i];typeof r=="string"&&r.trim().length>0&&(n[i]=r)}return Object.keys(n).length>0?n:void 0}function vE(t){let e=Sb(t.qualityPlanSummary);if(e)return e;let n=bE(t.contextSummary),i=Sb(n?.qualityPlanSummary);if(i)return i;let r=Sb(n?.qualityPlan);if(r)return r;let a=Sb(n?.quality_plan);if(a)return a}function jF(t,e){return e?{...t??{},...e}:t}function pde(t){return typeof t=="string"?{snapshotId:t}:t&&typeof t=="object"?{snapshotId:typeof t.snapshotId=="string"?t.snapshotId:null,...t.designCheckSummary!==void 0&&{designCheckSummary:t.designCheckSummary},...t.capturedAt!==void 0&&{capturedAt:t.capturedAt}}:{snapshotId:null}}function MF(t,e,n,i,r){if(!e)return;let a=yE(e.priority_high),o=yE(e.priority_medium),s=yE(e.priority_low),c=typeof e.total=="number"&&Number.isFinite(e.total)?e.total:a+o+s,l=n??(typeof e.target=="string"?e.target:void 0);return{source:t,...l!==void 0&&{targetPath:l},...typeof i=="string"&&{snapshotId:i},...r!==void 0&&{capturedAt:r},priority_high:a,priority_medium:o,priority_low:s,total:c}}function zF(t,e){if(!e)return t;if(!t)return e;let n=t.targetPath===e.targetPath?t.targetPath:void 0,i=t.snapshotId===e.snapshotId?t.snapshotId:void 0;return{source:e.source,...n!==void 0&&{targetPath:n},...i!==void 0&&{snapshotId:i},...e.capturedAt!==void 0&&{capturedAt:e.capturedAt},priority_high:t.priority_high+e.priority_high,priority_medium:t.priority_medium+e.priority_medium,priority_low:t.priority_low+e.priority_low,total:t.total+e.total}}function fde(t){let e=bE(t);if(e)for(let n of["userMessage","intent","expectedBehavior"]){let i=e[n];if(typeof i=="string"&&i.trim().length>0)return i.trim().slice(0,80)}}function mde(){return{source:"unavailable",priority_high:0,priority_medium:0,priority_low:0,total:0}}function LF(t){let e=t.now??Date.now,n=null,i=null,r=l=>`${l}_${e()}_${lde(3).toString("hex")}`,a=()=>{i&&clearTimeout(i),i=setTimeout(()=>{s("idle_timeout")},t.idleMs)},o=async l=>{try{return pde(await t.capturePreview(l))}catch{return{snapshotId:null}}},s=async l=>{if(!n)return;i&&(clearTimeout(i),i=null);let u=n;n=null;let d={},p;for(let h of u.affectedPaths)if(u.afterSnapshotsPathPolicy[h]==="skip")d[h]=null;else{let y=await o(h);d[h]=y.snapshotId,p=zF(p,MF("after_snapshot",y.designCheckSummary,h,y.snapshotId,y.capturedAt))}let f=p??u.latestDesignCheckSummary??mde(),m={requestId:u.requestId,...u.contextId!==void 0&&{contextId:u.contextId},...u.label!==void 0&&{label:u.label},startedAt:u.startedAtIso,closedAt:new Date(e()).toISOString(),closeReason:l,ok:u.ok,affectedPaths:Array.from(u.affectedPaths).sort(),mutationCount:u.mutationCount,beforeSnapshots:u.beforeSnapshots,afterSnapshots:d,childMutationIds:u.childMutationIds,...u.qualityPlanSummary!==void 0&&{qualityPlanSummary:u.qualityPlanSummary},postChangeDesignCheckSummary:f};await OF(t.placeDir,m)},c=l=>({requestId:r("req"),contextId:l.contextId,label:fde(l.contextSummary),startedAtIso:new Date(e()).toISOString(),startedAtMs:e(),affectedPaths:new Set,beforeSnapshots:{},afterSnapshotsPathPolicy:{},childMutationIds:[],mutationCount:0,ok:!0,qualityPlanSummary:vE(l),latestDesignCheckSummary:void 0,pendingMutation:void 0});return{async beforeMutation(l){if(NF.includes(l.command)){if(n){let d=r("mut");n.qualityPlanSummary=jF(n.qualityPlanSummary,vE(l)),n.pendingMutation={mutationId:d,startedMs:e(),args:l}}return}if(!ude.includes(l.command))return;n&&n.contextId!==l.contextId&&await s("context_change"),n||(n=c(l)),n.qualityPlanSummary=jF(n.qualityPlanSummary,vE(l)),l.targetPath&&!(l.targetPath in n.beforeSnapshots)&&(n.affectedPaths.add(l.targetPath),l.command==="manage_ui_create_tree"?n.beforeSnapshots[l.targetPath]=null:n.beforeSnapshots[l.targetPath]=(await o(l.targetPath)).snapshotId,l.command==="manage_ui_delete"?n.afterSnapshotsPathPolicy[l.targetPath]="skip":l.targetPath in n.afterSnapshotsPathPolicy||(n.afterSnapshotsPathPolicy[l.targetPath]="capture"));let u=r("mut");n.pendingMutation={mutationId:u,startedMs:e(),args:l}},async afterMutation(l){if(!n?.pendingMutation)return;let{mutationId:u,args:d}=n.pendingMutation,p={mutationId:u,requestId:n.requestId,ts:new Date(e()).toISOString(),command:d.command,origin:d.origin,...d.targetPath!==void 0&&{targetPath:d.targetPath},...d.briefId!==void 0&&{briefId:d.briefId},...l.diffSummary!==void 0&&{diffSummary:l.diffSummary},...l.changeDetails!==void 0&&{changeDetails:l.changeDetails},ok:l.ok,...l.error!==void 0&&{error:l.error},durationMs:l.durationMs};if(await AF(t.placeDir,p),n.pendingMutation=void 0,NF.includes(d.command)){n.latestDesignCheckSummary=zF(n.latestDesignCheckSummary,MF("check_results",l.designCheckSummary,d.targetPath,l.snapshotId,l.capturedAt));return}if(n.mutationCount++,n.childMutationIds.push(u),l.ok||(n.ok=!1),!n.contextId){await s("legacy_single");return}a()},async flushNow(){n&&await s("explicit")}}}var CE=100,Yde=12e4,Xde=15e3,i8=new Set(["manage_ui_create_tree","manage_ui_update","manage_ui_delete"]),Qde={Text:{category:"text",label:"\uD14D\uC2A4\uD2B8"},PlaceholderText:{category:"text",label:"\uD50C\uB808\uC774\uC2A4\uD640\uB354"},TextColor3:{category:"color",label:"\uAE00\uC790\uC0C9"},BackgroundColor3:{category:"color",label:"\uBC30\uACBD\uC0C9"},BorderColor3:{category:"color",label:"\uD14C\uB450\uB9AC\uC0C9"},ImageColor3:{category:"color",label:"\uC774\uBBF8\uC9C0\uC0C9"},Size:{category:"size",label:"\uC0AC\uC774\uC988"},TextSize:{category:"size",label:"\uAE00\uC790 \uD06C\uAE30"},Position:{category:"layout",label:"\uC704\uCE58"},AnchorPoint:{category:"layout",label:"\uC575\uCEE4"},Rotation:{category:"layout",label:"\uD68C\uC804"},LayoutOrder:{category:"layout",label:"\uB808\uC774\uC544\uC6C3 \uC21C\uC11C"},Visible:{category:"state",label:"\uD45C\uC2DC \uC0C1\uD0DC"},Active:{category:"state",label:"\uD65C\uC131 \uC0C1\uD0DC"},Interactable:{category:"state",label:"\uC0C1\uD638\uC791\uC6A9 \uC0C1\uD0DC"},Selectable:{category:"state",label:"\uC120\uD0DD \uAC00\uB2A5"},Enabled:{category:"state",label:"\uD65C\uC131\uD654"},Image:{category:"asset",label:"\uC774\uBBF8\uC9C0"},Texture:{category:"asset",label:"\uD14D\uC2A4\uCC98"},SoundId:{category:"asset",label:"\uC0AC\uC6B4\uB4DC"}},epe={text:"\uD14D\uC2A4\uD2B8",color:"\uCEEC\uB7EC",size:"\uC0AC\uC774\uC988",layout:"\uB808\uC774\uC544\uC6C3",state:"\uC0C1\uD0DC",asset:"\uC5D0\uC14B",property:"\uC18D\uC131"},tpe=Object.entries(Xc).reduce((t,[e,n])=>{for(let i of n.validActions)t[`${e}_${i}`]=e;return t},{});function XF(t){return{toolName:tpe[t]||t,commandName:t}}function QF(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 kb(t,e){if(t!=="manage_sync_status_current_place")return e;let{placeId:n,...i}=e;return i}async function npe(t,e){let n=t.syncController;if(!n)return null;try{let i=e.placeId,r=n.getDefaultRuntimePlaceId(),a=null;if(typeof i=="number"&&Number.isFinite(i))a=i;else if(typeof i=="string"&&i.trim().length>0){let s=Number.parseInt(i,10);Number.isFinite(s)&&(a=s)}if(a===null&&(a=r),a===null||!Number.isFinite(a))return null;let o=a===r?n.getDefaultRuntimePlaceName():null;return typeof o=="string"&&o.length>0?await n.config.resolvePlaceRoot(a,o):n.config.getPlaceRoot(a)}catch{return null}}function rpe(t){let e=t.tree;if(!e||typeof e!="object")return;let n=e,i=typeof n.name=="string"&&n.name.length>0?n.name:void 0;return i?`${typeof n.parent=="string"&&n.parent.length>0?n.parent:"StarterGui"}.${i}`:void 0}function ipe(t,e){let n=e.targetPath;if(typeof n=="string"&&n.length>0)return n;if(t==="manage_ui_create_tree")return rpe(e)}function ape(t){let e=Qde[t];if(e)return e;let n=t.toLowerCase();return n.includes("color")?{category:"color",label:"\uC0C9\uC0C1"}:t==="Size"||n.endsWith("size")||n.includes("scale")?{category:"size",label:"\uC0AC\uC774\uC988"}:n.includes("position")||n.includes("anchor")||n.includes("rotation")||n.includes("layout")?{category:"layout",label:"\uB808\uC774\uC544\uC6C3"}:n.includes("visible")||n.includes("active")||n.includes("enabled")||n.includes("selectable")||n.includes("interactable")?{category:"state",label:"\uC0C1\uD0DC"}:n.includes("image")||n.includes("texture")||n.includes("sound")||n.includes("asset")?{category:"asset",label:"\uC5D0\uC14B"}:n.includes("text")||n.includes("placeholder")?{category:"text",label:"\uD14D\uC2A4\uD2B8"}:{category:"property",label:t}}function e8(t,e){return Object.prototype.hasOwnProperty.call(t,e)}function ope(t){if(!t||typeof t!="object")return new Map;let e=t,n=e.changed_properties??e.changedProperties;if(!Array.isArray(n))return new Map;let i=new Map;for(let r of n){if(!r||typeof r!="object")continue;let a=r,o=a.property;if(typeof o!="string"||o.length===0)continue;let s=e8(a,"before"),c=e8(a,"after");i.set(o,{...s&&{before:a.before},...c&&{after:a.after},hasBefore:s,hasAfter:c})}return i}function a8(t,e){let i=t.changes?.properties;if(!i||typeof i!="object")return;let r=ope(e),a=Object.entries(i).map(([o,s])=>{let c=ape(o),l=r.get(o),u={property:o,category:c.category,label:c.label,after:l?.hasAfter?l.after:s};return l?.hasBefore&&(u.before=l.before),u});return a.length>0?a:void 0}function o8(t){if(!t||t.length===0)return;let e=new Map;for(let r of t)e.set(r.category,(e.get(r.category)??0)+1);let i=["text","color","size","layout","state","asset","property"].map(r=>{let a=e.get(r)??0;return a>0?`${epe[r]} ${a}\uAC1C`:null}).filter(r=>r!==null);return i.length>0?`${i.join(", ")} \uBCC0\uACBD`:void 0}function t8(t,e){if(t==="manage_ui_update"){let n=e.changes,i=o8(a8(e));if(i)return i;let r=n?.properties?Object.keys(n.properties).length:0,a=Array.isArray(n?.addChildren)?n.addChildren.length:0,o=Array.isArray(n?.removeChildren)?n.removeChildren.length:0;return`${r} props, +${a}, -${o}`}if(t==="manage_ui_create_tree")return"tree \uC0DD\uC131";if(t==="manage_ui_delete")return"\uC81C\uAC70\uB428"}function Tp(t){return t&&typeof t=="object"?t:void 0}function Cb(t,e){let n=t[e];return typeof n=="number"&&Number.isFinite(n)?n:0}function s8(t){let e=Tp(t),n=Tp(e?.design_check_summary);if(n)return{priority_high:Cb(n,"priority_high"),priority_medium:Cb(n,"priority_medium"),priority_low:Cb(n,"priority_low"),total:Cb(n,"total"),...typeof n.scope=="string"&&{scope:n.scope},...typeof n.target=="string"&&{target:n.target}}}function c8(t){let n=Tp(t)?.snapshot_id;return typeof n=="string"?n:null}function n8(t){if(typeof t=="string"&&t.trim().length>0)return t;if(typeof t=="number"&&Number.isFinite(t)){let e=t<1e10?t*1e3:t;return new Date(e).toISOString()}}function l8(t){let e=Tp(t),n=Tp(e?.meta);return n8(n?.captured_at)??n8(n?.evaluated_at)}function spe(t,e){let n=t.uiStudioRecorders.get(e);if(n)return n;let i=LF({placeDir:e,idleMs:Xde,capturePreview:async r=>{try{let a=await To(t,"manage_ui_preview",{targetPath:r},PE());if(!a.success||!a.data||typeof a.data!="object")return null;let o=s8(a.data),s=l8(a.data);return{snapshotId:c8(a.data),...o!==void 0&&{designCheckSummary:o},...s!==void 0&&{capturedAt:s}}}catch{return null}}});return t.uiStudioRecorders.set(e,i),i}async function cpe(t,e,n){if(!i8.has(e))return null;let i=await npe(t,n);if(!i)return null;let r=spe(t,i),a=e,o=typeof n.contextId=="string"?n.contextId:void 0,s=n.contextSummary,c=s!==void 0&&typeof s=="object"?s:void 0,l=ipe(a,n),u=typeof n.briefId=="string"?n.briefId:void 0,d={command:a,...o!==void 0&&{contextId:o},...c!==void 0&&{contextSummary:c},...l!==void 0&&{targetPath:l},...u!==void 0&&{briefId:u},origin:n.__origin==="dashboard"?"dashboard":"ai"};try{return await r.beforeMutation(d),{recorder:r,command:a,params:n,startedAt:Date.now()}}catch(p){return g.debug("UI Studio recorder beforeMutation \uC2E4\uD328 (\uBB34\uC2DC)",{error:p}),null}}async function r8(t,e){if(t)try{let n=t.command==="manage_ui_update"?a8(t.params,e.data):void 0,i=t.command==="manage_ui_update"?o8(n)??t8(t.command,t.params):t8(t.command,t.params),r=s8(e.data),a=c8(e.data),o=l8(e.data);await t.recorder.afterMutation({ok:e.success,durationMs:Date.now()-t.startedAt,...i!==void 0&&{diffSummary:i},...n!==void 0&&{changeDetails:n},...e.error!==void 0&&{error:e.error},...r!==void 0&&{designCheckSummary:r},...a!==null&&{snapshotId:a},...o!==void 0&&{capturedAt:o}})}catch(n){g.debug("UI Studio recorder afterMutation \uC2E4\uD328 (\uBB34\uC2DC)",{error:n})}}async function IE(t,e,n,i,r,a){if(!t.historyManager)return;let o=!r.success&&r.error?el(n,r.error):void 0,s=Qc({resultSuccess:r.success,resultData:r.data,resultError:r.error,tier:qn(n),...o?{failureClassification:o}:{}});if(r.success){let l=s.kind==="success_fallback"&&r.data&&typeof r.data=="object"?{...r.data,outcomeStatus:"fallback",requestedCommand:n,executedCommand:s.fallbackCommand,...s.alternatives?{alternatives:s.alternatives}:{}}:r.data;await t.historyManager.recordSuccess(e,kb(n,i),l,a,n);return}if(s.kind==="blocked_unsupported"){await t.historyManager.recordBlockedOutcome(e,kb(n,i),r.error||"Pro action blocked in Basic mode","blocked_unsupported",{executionTimeMs:a,command:n});return}if(s.kind==="blocked_fallback_failed"){await t.historyManager.recordBlockedOutcome(e,kb(n,i),r.error||"Pro action blocked in Basic mode","blocked_fallback_failed",{executionTimeMs:a,command:n,...s.fallbackCommand?{fallbackCommand:s.fallbackCommand}:{},...s.blockedDetail?{blockedDetail:s.blockedDetail}:{}});return}await(s.kind==="warning"?t.historyManager.recordWarning.bind(t.historyManager):t.historyManager.recordFailure.bind(t.historyManager))(e,kb(n,i),r.error||"Unknown error",a,o?.errorType.toUpperCase(),n)}function lpe(t,e,n){if(!e)return;let i=t.pluginClients.get(e);i?.inFlightRequestId===n&&(i.inFlightRequestId=void 0)}function upe(t,e,n){if(g.debug("Broadcasting command",{command:e.data.command,requestId:e.data.requestId,targetClientId:n||"all",activeWebsocketClients:_o(t).length}),n){let i=t.pendingCommands.get(n)||[];i.length>=CE&&i.shift(),i.push(e),t.pendingCommands.set(n,i)}else{let i=_o(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>=CE&&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>=CE&&t.globalPendingCommands.shift(),t.globalPendingCommands.push(e)}Bv(t,n)}async function u8(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=XF(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==="manage_camera_play_screenshot"){let m="manage_camera.play_screenshot is not supported. Screenshot capture is available only in Edit mode via manage_camera.screenshot.",h=Date.now()-i;t.historyManager&&await t.historyManager.recordFailure("manage_camera",a,m,h,"UNSUPPORTED_COMMAND",r),n.json({requestId:s,success:!1,error:m});return}if(r==="get_cached_selection"){let m=a.maxAge,h=xp(t,m!==void 0?m:3e4),y=Date.now()-i;if(!h){let x={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,x.data,y,c.commandName),n.json(x);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 yb(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(vb(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=QF(m,h.data);t.historyManager&&await IE(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 d=await To(t,r,a,s,l),p=Date.now()-i,f={...QF(a,d.data),__sessionId:u};t.historyManager&&await IE(t,c.toolName,c.commandName,f,d,p),n.json(d)}catch(o){let s=Date.now()-i,c=wn(o);if(t.historyManager&&r){let l=XF(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 d8(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=PE(),{action:l,...u}=r;try{t.serverLastCommandAt=Date.now();let d=await To(t,s,u,c),p=Date.now()-i;t.historyManager&&await IE(t,"manage_properties",s,u,d,p),n.json(d)}catch(d){let p=Date.now()-i,f=wn(d);t.historyManager&&await t.historyManager.recordFailure("manage_properties",u,f,p,"EXCEPTION",s),g.error("/api/properties \uC694\uCCAD \uCC98\uB9AC \uC2E4\uD328",d),n.status(500).json({success:!1,error:f})}}async function To(t,e,n,i,r){if(g.debug("Executing command locally",{command:e,requestId:i}),s6(t,i)){let l=`Duplicate requestId '${i}' for command '${e}'. Generate a unique requestId for each command.`;return g.warn("Duplicate execute request rejected",{command:e,requestId:i}),{requestId:i,success:!1,error:l}}let a=i8.has(e)?await cpe(t,e,n):null,o=t.config.requestTimeout,s=r?.timeout??Math.max(t.config.requestTimeout,Yde),c=new Promise((l,u)=>{let d={requestId:i,command:e,params:n,timestamp:Date.now(),queueTimeoutMs:o,ackedTimeoutMs:s,state:xn.QUEUED,resolve:l,reject:u,timeoutId:null};t.commandQueue.set(i,d),d.timeoutId=setTimeout(()=>{t.commandQueue.delete(d.requestId),lpe(t,d.targetClientId,d.requestId),d.reject(new Error(`Command timeout after ${o}ms while ${d.state}: ${d.command}`))},o)});return upe(t,{event:"command",id:PE(),data:{command:e,requestId:i,params:n}}),c.then(async l=>{let u=l;if(e==="manage_ui_preview"&&l.success&&l.data)try{let{processPreviewResult:d,resolvePreviewPlaceDirFromCtx:p}=await Promise.resolve().then(()=>(kE(),wE)),f=await p(t,n),m=await d("manage_ui_preview",l.data,f);u={...l,data:m.data,...m.mcpContent?{mcpContent:m.mcpContent}:{}}}catch(d){g.warn("preview post-processing failed",{error:d instanceof Error?d.message:String(d)})}if(e==="manage_ui_check"&&l.success&&l.data&&n.includeVisualAnalysis===!0)try{let{resolvePreviewPlaceDirFromCtx:d}=await Promise.resolve().then(()=>(kE(),wE)),{processCheckResult:p}=await Promise.resolve().then(()=>(YF(),KF)),f=await d(t,n),m=await p("manage_ui_check",l.data,f,n);u={...u,data:m.data}}catch(d){g.warn("check visual post-processing failed",{error:d instanceof Error?d.message:String(d)})}return u}).then(async l=>(await r8(a,{success:l.success,...l.data!==void 0&&{data:l.data},...l.error!==void 0&&{error:l.error}}),l),async l=>{throw await r8(a,{success:!1,error:wn(l)}),l})}ae();function Ib(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),ct(t,"license",n)}function jr(t,e,n,i){t.status(e).json({success:!1,error:{code:n,message:i}})}function dpe(t){if(typeof t!="string")return;let e=t.trim().toLowerCase();return e.length>0?e:void 0}function Pb(t,e){if(e==="plugin")return t;let n={...t};return delete n.sessionToken,n}function $p(t,e,n,i){let r=dpe(i??e.query.provider)??"auto";return t.licenseState?.supportsProvider(r)===!1?(jr(n,400,"LICENSE_PROVIDER_UNSUPPORTED",`provider "${r}" is not supported`),null):r}function Op(t,e){return t.licenseState?!0:(jr(e,503,"LICENSE_NOT_INITIALIZED","License system is not initialized"),!1)}function ppe(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(!Op(t,n))return;let r=e.body,a=typeof r?.licenseKey=="string"?r.licenseKey:"";if(!a.trim()){jr(n,400,"LICENSE_KEY_REQUIRED","licenseKey is required");return}let o=$p(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"),Ib(t,{provider:u.provider,tier:u.canUsePro?"pro":"basic",status:u.status,canUsePro:u.canUsePro}),n.json(Pb(u,i))}).catch(u=>{g.warn("License activate failed",{error:u instanceof Error?u.message:"unknown_error"}),jr(n,502,"LICENSE_ACTIVATE_FAILED",u instanceof Error?u.message:"License activation failed")})}function Dp(t,e,n,i="plugin"){if(!Op(t,n))return;let r=$p(t,e,n);if(r)try{let a=t.licenseState.getStatus(r);t.analyticsManager?.setTier(a.canUsePro?"pro":"basic"),n.json(Pb(a,i))}catch(a){g.warn("License status check failed",{error:a instanceof Error?a.message:"unknown_error"}),jr(n,502,"LICENSE_STATUS_FAILED",a instanceof Error?a.message:"License status check failed")}}function Np(t,e,n,i="plugin"){if(!Op(t,n))return;let r=e.body,a=$p(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"),Ib(t,{provider:s.provider,tier:s.canUsePro?"pro":"basic",status:s.status,canUsePro:s.canUsePro}),n.json(Pb(s,i))}).catch(s=>{g.warn("License refresh failed",{error:s instanceof Error?s.message:"unknown_error"}),jr(n,502,"LICENSE_REFRESH_FAILED",s instanceof Error?s.message:"License refresh failed")})}function jp(t,e,n,i="plugin"){if(!Op(t,n))return;let r=e.body,a=$p(t,e,n,r?.provider);a&&t.licenseState.resetGateway({provider:a,clientType:i}).then(o=>{t.analyticsManager?.setTier("basic"),Ib(t,{provider:o.provider,tier:"basic",status:o.status,canUsePro:o.canUsePro,cleared:!0}),n.json(Pb(o,i))}).catch(o=>{g.warn("License reset failed",{error:o instanceof Error?o.message:"unknown_error"}),jr(n,502,"LICENSE_RESET_FAILED",o instanceof Error?o.message:"License reset failed")})}function EE(t,e,n){if(!Op(t,n))return;let i=e.body,r=$p(t,e,n,i?.provider);if(!r)return;if(i?.clientType!=="plugin"){jr(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=ppe(i?.snapshot)?i.snapshot:void 0;if(!o&&!a){jr(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"),Ib(t,{provider:c.provider,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"}),jr(n,502,"LICENSE_BOOTSTRAP_FAILED",c instanceof Error?c.message:"License bootstrap failed")})}function p8(t){t.app.post("/license/bootstrap",(e,n)=>EE(t,e,n)),t.app.post("/license/activate",(e,n)=>Ap(t,e,n,"plugin")),t.app.post("/license/refresh",(e,n)=>Np(t,e,n,"plugin")),t.app.post("/license/reset",(e,n)=>jp(t,e,n,"plugin")),t.app.get("/license/status",(e,n)=>Dp(t,e,n,"plugin"))}mt();ae();import{promises as Eb}from"fs";import RE from"path";function fpe(t){let e=t.config.appDataDir??ft();return RE.join(e,"observability","logs")}async function al(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=fpe(t);await Eb.mkdir(a,{recursive:!0});let c=`plugin-${new Date().toISOString().split("T")[0]}.log`,l=RE.join(a,c),u=RE.join(a,"current.log"),d=r.logs.map(p=>`[${new Date(p.timestamp*1e3).toISOString().replace("T"," ").substring(0,19)}] [${p.level}] ${p.message}`).join(`
|
|
505
505
|
`)+`
|
|
506
506
|
`;await Eb.appendFile(l,d,"utf-8");try{let p="";try{p=await Eb.readFile(u,"utf-8")}catch{}let m=(p+d).split(`
|
|
507
507
|
`).slice(-500).join(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weppy/roblox-mcp",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.14",
|
|
4
4
|
"description": "MCP (Model Context Protocol) server for Roblox Studio integration - enables AI coding agents to interact with Roblox Studio in real-time",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
Binary file
|