@weppy/roblox-mcp 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +25 -0
- package/docs/en/pro-upgrade.md +1 -0
- package/docs/es/pro-upgrade.md +1 -0
- package/docs/id/pro-upgrade.md +1 -0
- package/docs/ja/pro-upgrade.md +1 -0
- package/docs/ko/pro-upgrade.md +1 -0
- package/docs/pt-br/pro-upgrade.md +1 -0
- package/package.json +1 -1
- package/plugins/weppy-roblox-mcp/.claude-plugin/plugin.json +1 -1
- package/plugins/weppy-roblox-mcp/dist/index.js +2 -2
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Roblox MCP server and tools for AI-powered game development",
|
|
9
|
-
"version": "1.5.
|
|
9
|
+
"version": "1.5.1"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "weppy-roblox-mcp",
|
|
14
14
|
"source": "./plugins/weppy-roblox-mcp",
|
|
15
15
|
"description": "MCP server for Roblox Studio integration - AI-powered game development with specialized agents and skills",
|
|
16
|
-
"version": "1.5.
|
|
16
|
+
"version": "1.5.1",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "hope1026"
|
|
19
19
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,31 @@ All notable changes to this project will be documented in this file.
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [1.5.1] - 2026-03-14
|
|
14
|
+
|
|
15
|
+
### Improved
|
|
16
|
+
|
|
17
|
+
- Overall stability improvements and internal configuration optimization
|
|
18
|
+
- Restore tier comparison UI overlay
|
|
19
|
+
- Refine telemetry parameters
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## [1.5.0] - 2026-03-13
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
- Play Test support — run, stop, pause, resume play tests and monitor output directly from AI agents
|
|
27
|
+
- Log streaming with incremental cursor-based retrieval (`sinceSeq`)
|
|
28
|
+
|
|
29
|
+
### Improved
|
|
30
|
+
|
|
31
|
+
- MCP server stability improvements and license refresh logic refinement
|
|
32
|
+
- Plugin connection handling simplified — removed redundant connect-refresh cycle
|
|
33
|
+
- Enhanced command routing and error handling
|
|
34
|
+
|
|
35
|
+
|
|
11
36
|
## [1.4.2] - 2026-03-11
|
|
12
37
|
|
|
13
38
|
### Improved
|
package/docs/en/pro-upgrade.md
CHANGED
|
@@ -53,5 +53,6 @@ Terrain generation, asset search, spatial analysis, animation, audio, and produc
|
|
|
53
53
|
| Resync / Rebuild state | ❌ | ✅ |
|
|
54
54
|
| Change History | ❌ | ✅ |
|
|
55
55
|
| Multi-place Sync | ❌ | ✅ Up to 3 places, each with isolated storage |
|
|
56
|
+
| Playtest Control (Play/Stop/Pause/Resume) | ❌ | ✅ |
|
|
56
57
|
| Advanced tool coverage | Core set | Broader advanced set |
|
|
57
58
|
| AI token efficiency | Standard | Better with bulk / high-leverage actions |
|
package/docs/es/pro-upgrade.md
CHANGED
|
@@ -53,5 +53,6 @@ Generación de terreno, búsqueda de assets, análisis espacial, animación, aud
|
|
|
53
53
|
| Resync / Reconstruir estado | ❌ | ✅ |
|
|
54
54
|
| Historial de cambios | ❌ | ✅ |
|
|
55
55
|
| Sync multi-place | ❌ | ✅ Hasta 3 places con almacenamiento aislado |
|
|
56
|
+
| Control de Playtest (Reproducir/Detener/Pausar/Reanudar) | ❌ | ✅ |
|
|
56
57
|
| Cobertura avanzada de tools | Conjunto base | Conjunto avanzado más amplio |
|
|
57
58
|
| Eficiencia de tokens IA | Estándar | Mejor con acciones masivas / de alto impacto |
|
package/docs/id/pro-upgrade.md
CHANGED
|
@@ -53,5 +53,6 @@ Pembuatan terrain, pencarian aset, analisis spasial, animasi, audio, dan otomasi
|
|
|
53
53
|
| Resync / Bangun ulang state | ❌ | ✅ |
|
|
54
54
|
| Riwayat Perubahan | ❌ | ✅ |
|
|
55
55
|
| Sync multi-place | ❌ | ✅ Hingga 3 place dengan penyimpanan terisolasi |
|
|
56
|
+
| Kontrol Playtest (Putar/Hentikan/Jeda/Lanjutkan) | ❌ | ✅ |
|
|
56
57
|
| Cakupan tools lanjutan | Set dasar | Set lanjutan lebih luas |
|
|
57
58
|
| Efisiensi token AI | Standar | Lebih efisien dengan aksi massal / berdampak tinggi |
|
package/docs/ja/pro-upgrade.md
CHANGED
package/docs/ko/pro-upgrade.md
CHANGED
|
@@ -53,5 +53,6 @@ Geração de terrain, busca de assets, análise espacial, animação, áudio e a
|
|
|
53
53
|
| Resync / Reconstruir estado | ❌ | ✅ |
|
|
54
54
|
| Histórico de mudanças | ❌ | ✅ |
|
|
55
55
|
| Sync multi-place | ❌ | ✅ Até 3 places com armazenamento isolado |
|
|
56
|
+
| Controle de Playtest (Reproduzir/Parar/Pausar/Retomar) | ❌ | ✅ |
|
|
56
57
|
| Cobertura avançada de tools | Conjunto base | Conjunto avançado mais amplo |
|
|
57
58
|
| Eficiência de tokens IA | Padrão | Melhor com ações em massa / de alto impacto |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weppy/roblox-mcp",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "MCP (Model Context Protocol) server for Roblox Studio integration - enables AI coding agents to interact with Roblox Studio in real-time",
|
|
5
5
|
"main": "plugins/weppy-roblox-mcp/dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -114,7 +114,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
114
114
|
`).filter(g=>g.length>0)}catch(h){if(h.code==="ENOENT"){r.status(200).json({entries:[],total:0,hasMore:!1});return}throw h}let p=[];for(let h=u.length-1;h>=0;h--)try{let g=JSON.parse(u[h]);if(o&&g.direction!==o||s&&g.type!==s)continue;p.push(g)}catch{continue}let d=p.length,m={entries:p.slice(a,a+i),total:d,hasMore:a+i<d};r.status(200).json(m)}getStatusSummary(){if(this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0)return{active:!0,placeId:this.ctx.activeFullSyncPlaceId};for(let[e,r]of this.ctx.places.entries())if(r.state==="syncing")return{active:!0,placeId:e};return{active:!1}}getDirectionForCategory(e){let r;if(this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0&&(r=this.ctx.places.get(this.ctx.activeFullSyncPlaceId)),!r){let i=this.ctx.getDefaultRuntimePlaceId();i!=null&&(r=this.ctx.places.get(i))}if(!r)return"forward";let n=e;return r.directions[n]??"forward"}getStatusDirect(e){let r=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(r==null)return{state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.ctx.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};let n=this.ctx.places.get(r);if(!n)return{state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.ctx.config.getPlaceRoot(r),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};let i=n.fileWatcher?.getPendingCount()??0;return{state:n.state,instanceCount:n.instanceCount,scriptCount:n.scriptCount,lastFullSync:n.lastFullSync,lastIncrementalSync:n.lastIncrementalSync,syncRoot:this.ctx.config.getPlaceRoot(r),activeClientId:n.activeClientId,reverseSyncAvailable:i>0,modifiedFileCount:i}}getConfigDirect(){return this.ctx.config.getConfig()}async getHistoryDirect(e,r){let n=parseInt(e,10),i=this.ctx.places.get(n);i&&await i.writer.flushHistory();let a=Math.min(Math.max(r?.limit??50,1),200),o=Math.max(r?.offset??0,0),s=this.ctx.config.getHistoryPath(n),c=[];try{c=(await Ja.readFile(s,"utf-8")).split(`
|
|
115
115
|
`).filter(f=>f.length>0)}catch(d){if(d.code==="ENOENT")return{entries:[],total:0,hasMore:!1};throw d}let l=[];for(let d=c.length-1;d>=0;d--)try{l.push(JSON.parse(c[d]))}catch{continue}let u=l.length;return{entries:l.slice(o,o+a),total:u,hasMore:o+a<u}}getDirectionsDirect(e){let r=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(r==null)return{...vi};let n=this.ctx.places.get(r);return n?{...n.directions}:{...vi}}getProgressDirect(e){let r=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(r==null)return{state:"idle",isSyncing:!1};let n=this.ctx.places.get(r);if(!n)return{state:"idle",isSyncing:!1};if(!n.syncProgress)return{state:n.state,isSyncing:!1,lastSync:{instanceCount:n.instanceCount,scriptCount:n.scriptCount,completedAt:n.lastFullSync}};let i=n.syncProgress,a=Date.now()-i.syncStartTime,o=i.totalInstances>0?Math.min(100,Math.round(i.processedInstances/i.totalInstances*100)):0,s;if(i.processedInstances>0&&o<100){let l=a/i.processedInstances,u=i.totalInstances-i.processedInstances;s=Math.round(l*u)}let c=a>0?Math.round(i.bytesReceived/a*1e3):0;return{state:n.state,isSyncing:!0,progressPercent:o,currentService:i.currentService,currentChunk:{index:i.currentChunkIndex,total:i.currentTotalChunks},instances:{processed:i.processedInstances,total:i.totalInstances},services:{processed:i.processedServices,total:i.totalServices},elapsedMs:a,estimatedRemainingMs:s,bytesReceived:i.bytesReceived,bytesPerSecond:c}}async readSyncedFile(e,r){let n=parseInt(e,10),i=this.ctx.places.get(n);if(!i)throw new Error(`Place ${e} not found in sync cache`);let a=i.index.resolvePropsPath(r);try{return{content:await Ja.readFile(a,"utf-8"),path:a}}catch(s){if(s.code!=="ENOENT")throw s}for(let s of Ua){let c=i.index.resolveScriptPath(r,s,!1);try{return{content:await Ja.readFile(c,"utf-8"),path:c}}catch{continue}}let o=i.index.resolveValuePath(r);try{return{content:await Ja.readFile(o,"utf-8"),path:o}}catch(s){if(s.code!=="ENOENT")throw s}throw new Error(`No synced file found for instance: ${r}`)}async writeSyncedFile(e,r,n){let i=parseInt(e,10),a=this.ctx.places.get(i);if(!a)throw new Error(`Place ${e} not found in sync cache`);await a.writer.writeScript(r,"Script",n,!1)}async executeViaDisk(e,r){let n=this.ctx.getDefaultRuntimePlaceId();if(n==null)throw new Error("No active sync place for disk execution");let i=this.ctx.places.get(n);if(!i)throw new Error(`Place ${n} not found in sync cache`);switch(e){case"set_script_source":{let a=r.scriptType||r.className||"Script";return await i.writer.writeScript(r.path,a,r.source,!1),{success:!0,path:r.path}}case"set_property":return await i.writer.writeProps(r.path,{className:r.className||"Instance",name:wt(r.path),properties:{[r.property]:r.value}}),{success:!0,path:r.path};default:throw new Error(`Disk execution not supported for action: ${e}`)}}};import $e from"path";import{randomUUID as SJ}from"crypto";import{promises as qt}from"fs";var Fd=class{constructor(e){this.ctx=e}preserveLocalFilesMap=new Map;pendingServiceTrees=new Map;async handleInitStart(e,r,n){if(r.previousPlaceId!==void 0&&r.previousPlaceId!==e&&(v.info("Place promotion detected",{from:r.previousPlaceId,to:e}),await this.ctx.config.promotePlaceRoot(r.previousPlaceId,e,r.placeName),this.ctx.places.get(r.previousPlaceId)&&this.ctx.places.delete(r.previousPlaceId),this.ctx.activeFullSyncPlaceId===r.previousPlaceId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(r.previousPlaceId)),this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0&&this.ctx.activeFullSyncPlaceId!==e){n.status(409).json({error:"Conflict",message:`Place ${this.ctx.activeFullSyncPlaceId} is currently syncing. Only one place can sync at a time.`});return}let i=await this.ctx.getOrCreatePlaceContext(e,r.placeName);if(i.activeClientId&&i.activeClientId!==r.clientId&&i.activeFullSyncSessionId!==null){n.status(409).json({error:"Conflict",message:`Another client (${i.activeClientId}) is currently syncing this place`});return}if(this.ctx.activeFullSyncPlaceId=e,this.ctx.touchRuntimePlace(e),i.activeClientId=r.clientId,i.placeName=r.placeName,r.directions&&typeof r.directions=="object"){let p=r.directions,d=f=>f==="forward"||f==="reverse"||f==="bidirectional";d(p.scripts)&&(i.directions.scripts=p.scripts),d(p.values)&&(i.directions.values=p.values),d(p.containers)&&(i.directions.containers=p.containers),d(p.data)&&(i.directions.data=p.data),d(p.services)&&(i.directions.services=p.services),v.info("Sync directions received",{placeId:e,directions:i.directions})}else i.directions={...vi};i.forwardRestoreQueue=[];let a=SJ();i.activeFullSyncSessionId=a,this.pendingServiceTrees.set(a,new Map),i.instanceCount=0,i.scriptCount=0;let o=this.ctx.config.getPlaceRoot(e),s=$e.join(o,`explorer_tmp_${a}`);await qt.mkdir(s,{recursive:!0}),i.tmpIndex=new Dn(o,s),i.tmpWriter=new qa(this.ctx.config,i.tmpIndex,e),i.collisionDirMap=new Map;let c=r.preserveLocalFiles;Array.isArray(c)&&c.length>0&&(this.setPreserveLocalFiles(a,c),v.info("PreserveLocalFiles set for sync",{syncId:a,fileCount:c.length}));let l={version:1,placeId:r.placeId,placeName:r.placeName,lastFullSync:null,lastIncrementalSync:null,instanceCount:0,scriptCount:0,syncMode:"mirror",propertyMode:this.ctx.config.getPropertyMode()},u=$e.join(o,".sync-meta.json");await qt.mkdir($e.dirname(u),{recursive:!0}),await this.ctx.atomicWriteFile(u,JSON.stringify(l,null,2)+`
|
|
116
116
|
`),this.startTTLTimerForPlace(i,a),i.state="initializing",i.index.resetNameCounters(),i.syncProgress={syncStartTime:Date.now(),totalInstances:r.totalInstances,totalServices:r.totalServices,processedInstances:0,processedServices:0,currentService:null,currentChunkIndex:0,currentTotalChunks:0,bytesReceived:0,processedChunks:0},i.writer.appendChangeLog(`FULL_SYNC_START clientId=${r.clientId} placeId=${r.placeId}`),i.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncStart",direction:"forward",path:`place_${r.placeId}`,details:`services:${r.totalServices} instances:${r.totalInstances}`}),v.info("Full sync started",{syncId:a,clientId:r.clientId,placeId:r.placeId,placeName:r.placeName,totalServices:r.totalServices,totalInstances:r.totalInstances}),n.status(200).json({status:"started",syncId:i.activeFullSyncSessionId})}async handleInitChunk(e,r,n){let i=this.ctx.places.get(e);if(!i||!i.activeFullSyncSessionId||!i.tmpIndex||!i.tmpWriter){n.status(400).json({error:"No active sync session",message:"Call sync/init with phase=start first"});return}if(i.activeClientId&&r.clientId&&i.activeClientId!==r.clientId){n.status(409).json({error:"Conflict",message:`This sync session belongs to client ${i.activeClientId}`});return}let a=i.activeFullSyncSessionId,o=this.getOrCreatePendingServiceTree(a,r),s=0,c=i.collisionDirMap;for(let u of r.instances){if(this.ctx.config.isForbiddenPath(u.path))continue;let p=i.tmpIndex.resolveParentDir(u.path),d=c.get(p)??p,{resolved:f,retroactiveRename:m}=i.tmpIndex.registerCollision(u.path,u.siblingIndex??void 0,d);m&&(await On(i.tmpIndex,d,m.from,m.to),this.rewritePendingEffectivePaths(o,i.tmpIndex.getExplorerRoot(),$e.join(d,m.from),$e.join(d,m.to)));let h=i.tmpIndex.resolveChildrenDir(u.path),g=$e.join(d,f);g!==h&&c.set(h,g);let b=i.tmpIndex.sanitizeName(u.name),w=u;if(d!==p||f!==b){let k=i.tmpIndex.getExplorerRoot(),$=$e.relative(k,d).split($e.sep).filter(A=>A.length>0),N=We(["game",...$,f]);w={...u,path:N}}let _=await i.tmpWriter.writeInstance(w);i.tmpIndex.setClassName(u.path,u.className,u.siblingIndex),s++,(_.propsWritten||_.valueWritten)&&i.instanceCount++,_.scriptWritten&&i.scriptCount++,o.instances.push({effectivePath:w.path,originalPath:u.path,className:u.className})}let l=this.isLastChunk(o,r.chunkIndex,r.totalChunks);if(l){let u=this.buildServiceTree(i,o);await i.tmpWriter.writeTree(r.serviceName,u);let p=this.pendingServiceTrees.get(a);p?.delete(r.serviceName),p&&p.size===0&&this.pendingServiceTrees.delete(a)}i.syncProgress&&(i.syncProgress.processedInstances+=s,i.syncProgress.currentService=r.serviceName,i.syncProgress.currentChunkIndex=r.chunkIndex,i.syncProgress.currentTotalChunks=r.totalChunks,i.syncProgress.processedChunks++,i.syncProgress.bytesReceived+=JSON.stringify(r).length,l&&i.syncProgress.processedServices++),v.debug("Sync chunk processed",{placeId:e,serviceName:r.serviceName,chunkIndex:r.chunkIndex,totalChunks:r.totalChunks,processed:s}),n.status(200).json({processed:s,service:r.serviceName})}async handleInitComplete(e,r,n){let i=this.ctx.places.get(e);if(!i||!i.activeFullSyncSessionId){n.status(400).json({error:"No active sync session",message:"Call sync/init with phase=start first"});return}if(i.activeClientId&&r.clientId&&i.activeClientId!==r.clientId){n.status(409).json({error:"Conflict",message:`This sync session belongs to client ${i.activeClientId}`});return}let a=i.activeFullSyncSessionId,o=this.ctx.config.getPlaceRoot(e),s=$e.join(o,"explorer"),c=$e.join(o,`explorer_tmp_${a}`),l=this.getAndClearPreserveLocalFiles(a),u=new Map,p=[];if(l.length>0){for(let m of l){let h=$e.resolve(o,m);try{let g=await qt.readFile(h,"utf-8");u.set(m,g)}catch{p.push(m)}}v.info("Backed up local files for preservation",{placeId:e,requested:l.length,backed:u.size,deleted:p.length})}try{await qt.rm(s,{recursive:!0,force:!0})}catch{}await qt.rename(c,s),i.instanceCount=r.instanceCount,i.scriptCount=r.scriptCount,i.lastFullSync=new Date().toISOString();let d={version:1,placeId:i.placeId,placeName:i.placeName,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,instanceCount:i.instanceCount,scriptCount:i.scriptCount,syncMode:"mirror",propertyMode:this.ctx.config.getPropertyMode()},f=$e.join(o,".sync-meta.json");if(await this.ctx.atomicWriteFile(f,JSON.stringify(d,null,2)+`
|
|
117
|
-
`),this.clearTTLTimerForPlace(i,a),i.index.clearAllHashes(),i.index.clearClassMappings(),i.tmpIndex){let m=i.tmpIndex.getExplorerRoot(),h=i.index.getExplorerRoot();for(let[g,b]of i.tmpIndex.getAllHashes()){let w=$e.relative(m,g),_=$e.resolve(h,w);i.index.updateHashByValue(_,b)}for(let[g,b]of i.tmpIndex.getAllFileHashes()){let w=$e.relative(m,g),_=$e.resolve(h,w);i.index.updateFileHashByValue(_,b)}i.index.resetNameCounters(),i.index.mergeNameMappingsFrom(i.tmpIndex)}if(i.tmpIndex=null,i.tmpWriter&&(i.tmpWriter.stopChangeLogFlusher(),i.tmpWriter=null),this.pendingServiceTrees.delete(a),i.collisionDirMap=null,await i.index.saveToDisk(),i.state="syncing",i.activeFullSyncSessionId=null,i.syncProgress=null,this.ctx.touchRuntimePlace(e),this.ctx.activeFullSyncPlaceId=null,await this.ctx.startFileWatcherForPlace(i),i.fileWatcher&&await i.fileWatcher.waitUntilReady(),u.size>0){for(let[m,h]of u){let g=$e.resolve(o,m);try{await qt.mkdir($e.dirname(g),{recursive:!0}),await qt.writeFile(g,h,"utf-8")}catch(b){v.warn("Failed to restore preserved file",{path:m,error:b instanceof Error?b.message:String(b)})}}v.info("Restored preserved local files",{placeId:e,count:u.size})}if(p.length>0){let m=0;for(let h of p){let g=$e.resolve(o,h);try{await qt.unlink(g),i.index.removeHash(g),m++}catch(b){b.code!=="ENOENT"&&v.warn("Failed to delete preserved-as-deleted file",{path:h,error:b instanceof Error?b.message:String(b)})}}m>0&&(await i.index.saveToDisk(),v.info("Deleted locally-removed files from new sync",{placeId:e,count:m}))}i.writer.appendChangeLog(`FULL_SYNC_COMPLETE instances=${i.instanceCount} scripts=${i.scriptCount}`),i.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncComplete",direction:"forward",path:`place_${e}`,details:`instances:${i.instanceCount} scripts:${i.scriptCount}`}),v.info("Full sync completed",{placeId:e,instanceCount:i.instanceCount,scriptCount:i.scriptCount}),n.status(200).json({status:"completed",instanceCount:i.instanceCount,scriptCount:i.scriptCount,syncRoot:this.ctx.config.getPlaceRoot(e)})}setPreserveLocalFiles(e,r){this.preserveLocalFilesMap.set(e,r)}getAndClearPreserveLocalFiles(e){let r=this.preserveLocalFilesMap.get(e)||[];return this.preserveLocalFilesMap.delete(e),r}clearPreserveLocalFiles(e){this.preserveLocalFilesMap.delete(e)}clearPendingServiceTrees(e){this.pendingServiceTrees.delete(e)}startTTLTimerForPlace(e,r){let n=setTimeout(async()=>{v.warn("Incomplete sync TTL expired, cleaning up",{placeId:e.placeId,syncId:r});let i=this.ctx.config.getPlaceRoot(e.placeId),a=$e.join(i,`explorer_tmp_${r}`);try{await qt.rm(a,{recursive:!0,force:!0})}catch(o){v.error("Failed to clean up expired temp dir",o instanceof Error?o:new Error(String(o)))}e.incompleteSyncTimer=null,e.activeFullSyncSessionId===r&&(e.activeFullSyncSessionId=null,e.activeClientId=null,e.state="idle",e.tmpIndex=null,this.pendingServiceTrees.delete(r),e.collisionDirMap=null,e.tmpWriter&&(e.tmpWriter.stopChangeLogFlusher(),e.tmpWriter=null),this.ctx.activeFullSyncPlaceId===e.placeId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(e.placeId))},Az);n&&typeof n=="object"&&"unref"in n&&n.unref(),e.incompleteSyncTimer=n}getOrCreatePendingServiceTree(e,r){let n=this.pendingServiceTrees.get(e);n||(n=new Map,this.pendingServiceTrees.set(e,n));let i=n.get(r.serviceName);if(i)return i;let a={serviceName:r.tree?.name??r.serviceName,serviceClassName:r.tree?.className??r.serviceClassName,zeroBasedChunkIndex:r.chunkIndex===0,instances:[]};return n.set(r.serviceName,a),a}isLastChunk(e,r,n){return n<=1?!0:e.zeroBasedChunkIndex?r>=n-1:r>=n}buildServiceTree(e,r){let n={name:r.serviceName,className:r.serviceClassName,childCount:0,children:[],syncedAt:new Date().toISOString()};for(let i of r.instances){let a=this.resolveEffectiveSegments(e,i.effectivePath),o=pt(i.originalPath);this.upsertTreeNode(n,a,o,i.className)}return this.recomputeTreeChildCounts(n),n.syncedAt=new Date().toISOString(),n}resolveEffectiveSegments(e,r){return e.tmpIndex?pt(r).map(n=>e.tmpIndex.sanitizeName(n)):pt(r)}rewritePendingEffectivePaths(e,r,n,i){let a=$e.relative(r,n).split($e.sep).filter(l=>l.length>0),o=$e.relative(r,i).split($e.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=We(["game",...a]),c=We(["game",...o]);for(let l of e.instances){if(l.effectivePath===s){l.effectivePath=c;continue}(l.effectivePath.startsWith(`${s}.`)||l.effectivePath.startsWith(`${s}[`))&&(l.effectivePath=`${c}${l.effectivePath.slice(s.length)}`)}}upsertTreeNode(e,r,n,i){if(r.length<=1)return;let a=e.children;for(let o=1;o<r.length;o++){let s=r[o],c=n[o],l=o===r.length-1,u=a.find(p=>p.name===s);u?l&&(u.className=i,c!==void 0&&c!==s&&(u.originalName=c)):(u={name:s,className:l?i:"Folder",childCount:0,children:[]},c!==void 0&&c!==s&&(u.originalName=c),a.push(u)),u.children||(u.children=[]),a=u.children}}recomputeTreeChildCounts(e){let r=n=>{let i=n.children??[];n.children=i;for(let a of i)r(a);n.childCount=i.length};for(let n of e.children)r(n);e.childCount=e.children.length}clearTTLTimerForPlace(e,r){e.incompleteSyncTimer&&e.activeFullSyncSessionId===r&&(clearTimeout(e.incompleteSyncTimer),e.incompleteSyncTimer=null)}async cleanupStaleTempDirs(){let e=this.ctx.config.getSyncRoot();try{let r=await qt.readdir(e,{withFileTypes:!0});for(let n of r)if(n.isDirectory()){if(n.name.startsWith("explorer_tmp_")){let i=$e.join(e,n.name);v.warn("Removing stale temp directory from crashed sync",{dir:n.name});try{await qt.rm(i,{recursive:!0,force:!0})}catch(a){v.error(`Failed to remove stale temp dir: ${n.name}`,a instanceof Error?a:new Error(String(a)))}}if(n.name.startsWith("place_")){let i=$e.join(e,n.name);try{let a=await qt.readdir(i,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=$e.join(i,o.name);v.warn("Removing stale temp directory from crashed sync",{dir:`${n.name}/${o.name}`});try{await qt.rm(s,{recursive:!0,force:!0})}catch(c){v.error(`Failed to remove stale temp dir: ${n.name}/${o.name}`,c instanceof Error?c:new Error(String(c)))}}}catch{continue}}}}catch(r){if(r.code==="ENOENT")return;v.warn("Failed to scan for stale temp dirs",{error:r instanceof Error?r.message:String(r)})}}};import jr from"path";import{promises as Zz}from"fs";var Ld=class{constructor(e){this.ctx=e}async handleReversePending(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({pending:0,hasConflicts:!1,lastDetected:null});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}let a={pending:i.fileWatcher?.getPendingCount()??0,hasConflicts:!1,lastDetected:i.fileWatcher?.getLastDetected()??null,forwardRestoreNeeded:i.forwardRestoreQueue.length};this.ctx.touchRuntimePlace(n),r.status(200).json(a)}async handleReverseSyncChanges(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({changes:[],count:0});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}if(i.state!=="syncing"){r.status(400).json({error:"Not syncing",message:"Reverse sync is only available when sync is active"});return}let a=i.fileWatcher?.drainPendingChanges()??[],o=a.length>0?await i.reader.buildChangesFromPending(a):[];this.ctx.touchRuntimePlace(n),r.status(200).json({changes:o,count:o.length})}async handleReverseSyncResult(e,r){let n=e.body,i=n.placeId??this.ctx.getDefaultRuntimePlaceId();if(i==null){r.status(400).json({error:"Validation error",message:"placeId is required (in body or via active sync session)"});return}let a=n.appliedFiles??n.appliedPaths;if(!a||!Array.isArray(a)){r.status(400).json({error:"Validation error",message:"appliedFiles (or appliedPaths) must be an array of relative file paths"});return}let o=this.ctx.places.get(i);if(!o){r.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}this.ctx.touchRuntimePlace(i);let s=this.ctx.config.getPlaceRoot(i),c=0,l=[];for(let u of a){let p=jr.resolve(s,u);if(!Ad(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await Zz.readFile(p,"utf-8"),f=o.index.computeHash(d);o.index.updateHashByValue(p,f),o.index.updateFileHashByValue(p,f),c++}catch(d){let f=d.code;if(f==="ENOENT"){o.index.removeHash(p),o.index.removeHashesUnder(p);let m=this.resolveInstancePathForAppliedPath(o.index,p);if(m){let h=wt(m),g=St(m);g&&h&&await o.writer.removeFromTree(g,h)}c++}else f==="EISDIR"?c++:l.push({path:u,error:d instanceof Error?d.message:String(d)})}}c>0&&await o.index.saveToDisk(),c>0&&o.writer.appendHistory({timestamp:new Date().toISOString(),type:"reverseApply",direction:"reverse",path:`place_${i}`,details:`applied:${c} failed:${l.length}`}),r.status(200).json({updated:c,failed:l.length,errors:l})}async handleResolveConflict(e,r){let n=e.body;if(!n.fsPath||!n.resolution){r.status(400).json({error:"Validation error",message:"fsPath and resolution are required"});return}let{fsPath:i,resolution:a}=n,o=this.ctx.config.getSyncRoot();if(!Ad(o,jr.resolve(o,i))){r.status(403).json({error:"Forbidden",message:"Path is outside the sync root"});return}if(a==="skip"){r.status(200).json({status:"skipped",fsPath:i});return}let s;if(n.placeId&&(s=this.ctx.places.get(n.placeId)),!s){let d=i.match(/^place_(\d+)(?:_[^/]+)?\//);if(d){let f=parseInt(d[1],10);s=this.ctx.places.get(f)}else s=Array.from(this.ctx.places.values())[0]}if(!s){r.status(404).json({error:"No active place context",message:"No sync session is active. Start a sync first."});return}let c=this.ctx.config.getPlaceRoot(s.placeId),l=jr.resolve(c,i);if(!Ad(c,l)){r.status(403).json({error:"Forbidden",message:"Path is outside the place root"});return}let u=s.index,p=s.reader;if(a==="apply-studio"){u.resolveFile(l,"apply-studio"),await u.saveToDisk(),r.status(200).json({status:"resolved",resolution:"apply-studio",fsPath:i});return}if(a==="apply-file"){let d=await Zz.readFile(l,"utf-8"),f=u.computeHash(d);u.resolveFile(l,"apply-file",f),await u.saveToDisk();let m=p.getFileType(l),h=p.resolveInstancePathFromFile(l);r.status(200).json({status:"resolved",resolution:"apply-file",fsPath:i,instancePath:h,fileType:m,content:d});return}r.status(400).json({error:"Invalid resolution",message:`Unknown resolution: ${a}`})}async handleReverseRescan(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({added:0});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}if(!i.fileWatcher){r.status(200).json({added:0});return}let a=await i.fileWatcher.rescan();this.ctx.touchRuntimePlace(n),v.info("Reverse rescan completed",{placeId:n,added:a}),r.status(200).json({added:a})}resolveInstancePathForAppliedPath(e,r){let n=e.resolveInstancePathFromFsPath(r);if(n)return n;let i=e.getExplorerRoot(),a=jr.relative(i,r);if(a.startsWith("..")||a===""||jr.isAbsolute(a))return null;let o=a.split(jr.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=jr.basename(r),c=jr.dirname(r),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(Ma.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=i;for(let f of o){d=jr.join(d,f);let m=jr.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return We(p)}};function Hz(t){if(!t||typeof t!="object")return;let e=t;if(Array.isArray(e.instances))for(let r of e.instances)Array.isArray(r.properties)&&r.properties.length===0&&(r.properties={}),Array.isArray(r.attributes)&&r.attributes.length===0&&(r.attributes={});if(Array.isArray(e.changes))for(let r of e.changes)Array.isArray(r.properties)&&r.properties.length===0&&(r.properties={}),Array.isArray(r.attributes)&&r.attributes.length===0&&(r.attributes={})}var qd=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;constructor(e){this.config=new _d(e),this.apiHandler=new Ud(this),this.changeProcessor=new Md(this),this.initHandler=new Fd(this),this.reverseHandler=new Ld(this),this.places=new xd({max:3,dispose:(r,n)=>{v.info("Disposing place context (LRU eviction)",{placeId:n}),this.activeFullSyncPlaceId===n&&(this.activeFullSyncPlaceId=null),this.activeRuntimeSyncPlaceId===n&&(this.activeRuntimeSyncPlaceId=null),r.fileWatcher&&(r.fileWatcher.stop().catch(i=>{v.error("Error stopping file watcher during dispose",i)}),r.fileWatcher=null),r.writer.stopChangeLogFlusher(),r.incompleteSyncTimer&&(clearTimeout(r.incompleteSyncTimer),r.incompleteSyncTimer=null),r.index.saveToDisk().catch(i=>{v.error("Error saving index during dispose",i)}),r.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(r.activeFullSyncSessionId),r.tmpWriter&&(r.tmpWriter.stopChangeLogFlusher(),r.tmpWriter=null),r.tmpIndex=null,r.collisionDirMap=null}})}async getOrCreatePlaceContext(e,r){let n=this.places.get(e);if(n&&r){let i=this.config.getPlaceRoot(e),a=await this.config.resolvePlaceRoot(e,r);a!==i&&(v.info("Place root migrated, recreating context",{placeId:e,from:i,to:a}),this.places.delete(e),n=void 0)}if(!n){let i=await this.config.resolvePlaceRoot(e,r),a=x_.join(i,"explorer");await Ga.mkdir(i,{recursive:!0}),await Ga.mkdir(a,{recursive:!0});let o=new Dn(i,a);await o.loadFromDisk();let s=new qa(this.config,o,e),c=new kd(this.config,o,i);s.startChangeLogFlusher(),n={placeId:e,placeName:"",index:o,writer:s,reader:c,fileWatcher:null,state:"idle",activeClientId:null,activeFullSyncSessionId:null,instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,tmpIndex:null,tmpWriter:null,incompleteSyncTimer:null,changesSinceLastSave:0,directions:{...vi},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null},this.places.set(e,n),v.info("Created new place context",{placeId:e,placeRoot:i})}return n}async handleSyncInit(e,r){try{Hz(e.body);let n=Uz(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data,a=i.phase==="start"?i.placeId??null:i.phase==="chunk"||i.phase==="complete"?this.activeFullSyncPlaceId:null;if(a==null){r.status(400).json({error:"Missing placeId",message:"placeId is required in start phase or must be set by previous start"});return}switch(i.phase){case"start":await this.initHandler.handleInitStart(a,i,r);break;case"chunk":await this.initHandler.handleInitChunk(a,i,r);break;case"complete":await this.initHandler.handleInitComplete(a,i,r);break}}catch(n){this.sendError(r,n,"handleSyncInit")}}async handleSyncUpdate(e,r){try{Hz(e.body);let n=Fz(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data,a=i.placeId??this.getDefaultRuntimePlaceId();if(a==null){r.status(400).json({error:"Missing placeId",message:"placeId is required in body or must have an active sync session"});return}let o=await this.getOrCreatePlaceContext(a);if(o.activeFullSyncSessionId!==null||o.state==="initializing"){r.status(409).json({error:"Conflict",message:`Full sync in progress for place ${a}`});return}o.activeClientId=i.clientId,this.touchRuntimePlace(a);let s=Nz(o,i.changes);v.info("Sync update received",{placeId:a,clientId:i.clientId,changeCount:s.length,receivedCount:i.changes.length,types:s.map(f=>f.type)});let c=[],l=[],u=0,p=new Map;for(let f of s)try{let m=await this.changeProcessor.processChangeForPlace(o,f,p);m?l.push(m):u++}catch(m){let h="path"in f?f.path:"oldPath"in f?f.oldPath:"unknown";c.push({path:h,error:m instanceof Error?m.message:String(m)})}for(let f of p.values())try{await Dd(o,f)}catch(m){c.push({path:f.instancePath,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=Mz&&(await o.index.saveToDisk(),o.changesSinceLastSave=0),o.lastIncrementalSync=new Date().toISOString();let d={processed:u,failed:c.length,errors:c,syncedAt:o.lastIncrementalSync};l.length>0&&(d.conflicts=l),r.status(200).json(d)}catch(n){this.sendError(r,n,"handleSyncUpdate")}}getDefaultRuntimePlaceId(){if(this.activeRuntimeSyncPlaceId!==null&&this.activeRuntimeSyncPlaceId!==void 0){if(this.places.has(this.activeRuntimeSyncPlaceId))return this.activeRuntimeSyncPlaceId;this.activeRuntimeSyncPlaceId=null}if(this.activeFullSyncPlaceId!==null&&this.activeFullSyncPlaceId!==void 0&&this.places.has(this.activeFullSyncPlaceId))return this.activeRuntimeSyncPlaceId=this.activeFullSyncPlaceId,this.activeRuntimeSyncPlaceId;for(let[e,r]of this.places.entries())if(r.state==="syncing"||r.state==="initializing")return this.activeRuntimeSyncPlaceId=e,e;return this.activeRuntimeSyncPlaceId=null,null}touchRuntimePlace(e){this.activeRuntimeSyncPlaceId=e}clearRuntimePlaceIfMatch(e){this.activeRuntimeSyncPlaceId===e&&(this.activeRuntimeSyncPlaceId=null)}resolveQueryPlaceId(e,r="runtime"){let n=e.query.placeId;if(n){let i=parseInt(n,10);if(!isNaN(i))return i}return r==="full"?this.activeFullSyncPlaceId:this.getDefaultRuntimePlaceId()}async handleSyncStatus(e,r){try{let n=this.resolveQueryPlaceId(e);if(n==null){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};r.status(200).json(s);return}let i=this.places.get(n);if(!i){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getPlaceRoot(n),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};r.status(200).json(s);return}let a=i.fileWatcher?.getPendingCount()??0;this.touchRuntimePlace(n);let o={state:i.state,instanceCount:i.instanceCount,scriptCount:i.scriptCount,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,syncRoot:this.config.getPlaceRoot(n),activeClientId:i.activeClientId,reverseSyncAvailable:a>0,modifiedFileCount:a,directions:i.directions,fileWatcherActive:i.fileWatcher!==null,forwardOnlyClasses:[...Fa]};r.status(200).json(o)}catch(n){this.sendError(r,n,"handleSyncStatus")}}async handleSyncStop(e,r){try{let n=e.body,i=n.placeId??this.getDefaultRuntimePlaceId();if(i==null){r.status(200).json({status:"idle",state:"idle",placeId:null,message:"No active sync place"});return}let a=this.places.get(i);if(!a){r.status(200).json({status:"idle",state:"idle",placeId:i,message:`No sync context for place ${i}`});return}if(n.clientId&&a.activeClientId&&n.clientId!==a.activeClientId&&(a.activeFullSyncSessionId!==null||a.state==="syncing"||a.state==="initializing")){r.status(409).json({error:"Conflict",message:`This sync session belongs to client ${a.activeClientId}`});return}let o=a.activeFullSyncSessionId;if(o&&(this.initHandler.clearPreserveLocalFiles(o),this.initHandler.clearPendingServiceTrees(o)),a.fileWatcher&&(await a.fileWatcher.stop(),a.fileWatcher=null),a.incompleteSyncTimer&&(clearTimeout(a.incompleteSyncTimer),a.incompleteSyncTimer=null),o){let s=this.config.getPlaceRoot(i),c=x_.join(s,`explorer_tmp_${o}`);await Ga.rm(c,{recursive:!0,force:!0}).catch(()=>{})}a.tmpWriter&&(a.tmpWriter.stopChangeLogFlusher(),a.tmpWriter=null),a.tmpIndex=null,a.collisionDirMap=null,a.activeFullSyncSessionId=null,a.syncProgress=null,a.state="idle",a.activeClientId=null,a.instanceCount=0,a.scriptCount=0,this.activeFullSyncPlaceId===i&&(this.activeFullSyncPlaceId=null),this.clearRuntimePlaceIfMatch(i),v.info("Sync stopped",{placeId:i,reason:n.reason??"requested"}),r.status(200).json({status:"stopped",state:"idle",placeId:i})}catch(n){this.sendError(r,n,"handleSyncStop")}}async handleSyncConfig(e,r){try{if(e.method==="GET"){r.status(200).json(this.config.getConfig());return}let n=Lz(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data;i.propertyMode!==void 0&&this.config.setPropertyMode(i.propertyMode),i.maxDepth!==void 0&&this.config.updateConfig({maxDepth:i.maxDepth}),i.maxInstances!==void 0&&this.config.updateConfig({maxInstances:i.maxInstances}),r.status(200).json({status:"updated",config:this.config.getConfig()})}catch(n){this.sendError(r,n,"handleSyncConfig")}}async initialize(){try{await this.config.loadFromMeta(),await this.initHandler.cleanupStaleTempDirs(),v.info("SyncController initialized")}catch(e){v.error("SyncController initialization failed",e instanceof Error?e:new Error(String(e)))}}async shutdown(){try{this.places.clear(),v.info("SyncController shut down")}catch(e){v.error("SyncController shutdown error",e instanceof Error?e:new Error(String(e)))}}async atomicWriteFile(e,r){let n=e+".tmp."+kJ().slice(0,8);try{await Ga.writeFile(n,r,"utf-8"),await Ga.rename(n,e)}catch(i){throw await Ga.unlink(n).catch(()=>{}),i}}async handlePreCheck(e,r){try{await this.apiHandler.handlePreCheck(e,r)}catch(n){this.sendError(r,n,"handlePreCheck")}}async handleSyncDirections(e,r){try{await this.apiHandler.handleSyncDirections(e,r)}catch(n){this.sendError(r,n,"handleSyncDirections")}}async handleForwardRestoreList(e,r){try{await this.apiHandler.handleForwardRestoreList(e,r)}catch(n){this.sendError(r,n,"handleForwardRestoreList")}}async handleReversePending(e,r){try{await this.reverseHandler.handleReversePending(e,r)}catch(n){this.sendError(r,n,"handleReversePending")}}async handleReverseSyncChanges(e,r){try{await this.reverseHandler.handleReverseSyncChanges(e,r)}catch(n){this.sendError(r,n,"handleReverseSyncChanges")}}async handleReverseSyncResult(e,r){try{await this.reverseHandler.handleReverseSyncResult(e,r)}catch(n){this.sendError(r,n,"handleReverseSyncResult")}}async handleResolveConflict(e,r){try{await this.reverseHandler.handleResolveConflict(e,r)}catch(n){this.sendError(r,n,"handleResolveConflict")}}async handleReverseRescan(e,r){try{await this.reverseHandler.handleReverseRescan(e,r)}catch(n){this.sendError(r,n,"handleReverseRescan")}}async handleSyncHistory(e,r){try{await this.apiHandler.handleSyncHistory(e,r)}catch(n){this.sendError(r,n,"handleSyncHistory")}}async startFileWatcherForPlace(e){if(e.state!=="syncing"){v.debug("Skipping file watcher start - place not syncing",{placeId:e.placeId,state:e.state});return}e.fileWatcher&&await e.fileWatcher.stop();let r=x_.join(this.config.getPlaceRoot(e.placeId),"explorer");e.fileWatcher=new jd(r,e.index),e.writer.setOnWriteCallback(n=>{e.fileWatcher?.suppressPath(n)}),e.fileWatcher.setDirectionChecker(n=>{let i=oz(n);return e.directions[i]}),e.fileWatcher.setOnForwardViolation(n=>{e.forwardRestoreQueue.includes(n)||(e.forwardRestoreQueue.push(n),v.info("Forward violation queued for restore",{placeId:e.placeId,relativePath:n,queueSize:e.forwardRestoreQueue.length}))}),await e.fileWatcher.start(),v.info("File watcher started for reverse sync",{placeId:e.placeId})}getStatusSummary(){return this.apiHandler.getStatusSummary()}getDirectionForCategory(e){return this.apiHandler.getDirectionForCategory(e)}getStatusDirect(e){return this.apiHandler.getStatusDirect(e)}getConfigDirect(){return this.apiHandler.getConfigDirect()}async getHistoryDirect(e,r){return this.apiHandler.getHistoryDirect(e,r)}getDirectionsDirect(e){return this.apiHandler.getDirectionsDirect(e)}getProgressDirect(e){return this.apiHandler.getProgressDirect(e)}async readSyncedFile(e,r){return this.apiHandler.readSyncedFile(e,r)}async writeSyncedFile(e,r,n){await this.apiHandler.writeSyncedFile(e,r,n)}async executeViaDisk(e,r){return this.apiHandler.executeViaDisk(e,r)}sendError(e,r,n){let i=r instanceof Error?r.message:String(r);if(i.includes("Path traversal detected")){e.status(403).json({error:"Forbidden",message:i});return}let a=r.code;if(a==="ENOSPC"||a==="EPERM"||a==="EACCES"){e.status(500).json({error:"Disk error",message:i});return}v.error(`SyncController.${n} failed`,r instanceof Error?r:new Error(i)),e.status(500).json({error:"Internal error",message:`${n}: ${i}`})}};import{randomUUID as $J}from"crypto";function Bz(t,e){let r=$J(),n=r.replace(/-/g,"").substring(0,8).toUpperCase(),i=`${n.substring(0,4)}-${n.substring(4,8)}`;return{config:t,app:e,instanceId:r,sessionId:i,startTime:Date.now(),baseUrl:`http://${t.httpHost}:${t.httpPort}`,commandQueue:new Map,pendingCommands:new Map,globalPendingCommands:[],totalCommandsProcessed:0,pluginClients:new Map,mcpInstances:new Map,sseClients:new Set,cachedSelectionMap:new Map,isClientMode:!1,clientModeHealthTimer:null,clientModeConsecutiveHealthFailures:0,clientModeUpstreamReachable:!0,clientModeLastHealthSuccessAt:null,clientModeLastHealthFailureAt:null,clientModeLastHealthError:null,historyManager:null,analyticsManager:null,licenseState:null,syncController:null,internalActionExecutor:null,playtestControlCommand:null,aiClientName:"",pluginVersion:"",syncedSessionToken:null}}var Vz=ro(hd(),1);function Wz(t){let e=Vz.default.json({limit:"5mb"});t.app.use((r,n,i)=>{if(r.path.startsWith("/sync/")){i();return}e(r,n,i)}),t.app.use((r,n,i)=>{n.setHeader("Access-Control-Allow-Origin","http://localhost:3002"),n.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),n.setHeader("Access-Control-Allow-Headers","Content-Type"),i()}),t.app.use((r,n,i)=>{v.debug(`${r.method} ${r.path}`,{ip:r.ip}),i()})}import{randomUUID as Ec}from"crypto";var Xe="1.4.2";function Jz(t){let e=new Set;t.aiClientName&&e.add(t.aiClientName);for(let r of t.mcpInstances.values())r.aiClientName&&e.add(r.aiClientName);return Array.from(e)}function IJ(t){let r=Date.now();return Array.from(t.pluginClients.values()).filter(n=>r-n.lastSeen<1e4)}function Gz(t){let r=Date.now();for(let[n,i]of t.mcpInstances)i.lastSeen&&r-i.lastSeen>15e3&&(t.mcpInstances.delete(n),v.debug("Removed stale MCP instance",{instanceId:n,lastSeen:i.lastSeen}))}function Kz(t,e,r){let n=e.query.clientId;if(n&&t.pluginClients.has(n)){let i=t.pluginClients.get(n);i.lastSeen=Date.now()}try{let i=e.body;if(!i||!Array.isArray(i.selection)||typeof i.count!="number"){v.warn("Invalid selection update request",{body:i}),r.status(400).json({error:"Invalid request body"});return}let a=n||"unknown",o=Date.now();t.cachedSelectionMap.set(a,{selection:i.selection,count:i.count,timestamp:o,clientId:a}),v.debug("Selection cache updated",{count:i.count,clientId:a,timestamp:o}),r.json({status:"ok",timestamp:o})}catch(i){v.error("Error handling selection update",i),r.status(500).json({error:"Internal server error"})}}function Xz(t,e,r){let n=parseInt(e.query.maxAge)||3e4,i=Tc(t,n);i?r.json({cached:!0,...i}):r.json({cached:!1,message:"No cached selection available"})}function Tc(t,e=3e4,r){if(t.cachedSelectionMap.size===0)return null;let n;if(r)n=t.cachedSelectionMap.get(r);else for(let a of t.cachedSelectionMap.values())(!n||a.timestamp>n.timestamp)&&(n=a);if(!n)return null;let i=Date.now()-n.timestamp;return e===0||i<=e?n:null}function Yz(t,e,r){try{let n=e.body;if(!n.clientId){r.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.get(n.clientId),a=Date.now(),o={clientId:n.clientId,projectName:n.projectName,placeName:n.placeName,placeId:n.placeId,connectedAt:i?.connectedAt||a,lastSeen:a,commandsProcessed:i?.commandsProcessed||0,connectionType:"polling"};t.pluginClients.set(n.clientId,o),t.pendingCommands.has(n.clientId)||t.pendingCommands.set(n.clientId,[]),n.pluginVersion&&(t.pluginVersion=n.pluginVersion),t.analyticsManager&&(n.pluginVersion&&t.analyticsManager.setPluginVersion(n.pluginVersion),t.analyticsManager.trackPluginConnected()),v.info("Plugin client registered",{clientId:n.clientId,projectName:n.projectName,placeName:n.placeName,isReconnect:!!i}),r.json({status:"ok",clientId:n.clientId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Xe,connectedAt:o.connectedAt,aiClientNames:Jz(t),serverStartTime:t.startTime,mcpInstanceCount:t.mcpInstances.size+1})}catch(n){v.error("Error registering plugin client",n),r.status(500).json({error:"Internal server error"})}}function Qz(t,e,r){let n=e.body?.clientId;if(!n){r.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.has(n);t.pluginClients.delete(n),t.pendingCommands.delete(n),v.info("Plugin client unregistered",{clientId:n,existed:i}),r.json({status:"ok",existed:i})}function ej(t,e,r){try{let n=e.body;if(!n.instanceId){r.status(400).json({error:"Missing instanceId"});return}let i=Date.now(),a={instanceId:n.instanceId,pid:n.pid,connectedAt:i,isServer:!1,lastSeen:i};n.aiClientName&&(a.aiClientName=n.aiClientName),n.cwd&&(a.cwd=n.cwd),t.mcpInstances.set(n.instanceId,a),v.info("MCP instance registered (client mode)",{instanceId:n.instanceId,pid:n.pid,cwd:n.cwd}),r.json({status:"ok",instanceId:n.instanceId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Xe,mcpInstanceCount:t.mcpInstances.size+1})}catch(n){v.error("Error registering MCP instance",n),r.status(500).json({error:"Internal server error"})}}function tj(t,e,r){let n=e.body?.instanceId;if(!n){r.status(400).json({error:"Missing instanceId"});return}let i=t.mcpInstances.has(n);t.mcpInstances.delete(n),v.info("MCP instance unregistered",{instanceId:n,existed:i}),r.json({status:"ok",existed:i})}function rj(t,e,r){let{instanceId:n,aiClientName:i}=e.body;if(n&&i){let a=t.mcpInstances.get(n);a&&(a.aiClientName=i)}r.json({status:"ok"})}function nj(t){let r=Date.now();for(let[n,i]of t.pluginClients)r-i.lastSeen>3e4&&(t.pluginClients.delete(n),t.pendingCommands.delete(n));return Gz(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Xe,uptime:r-t.startTime,serverStartTime:t.startTime,serverExecutable:process.execPath,serverHost:t.config.httpHost,serverPort:t.config.httpPort,serverPid:process.pid,mcpInstances:[{instanceId:t.instanceId,pid:process.pid,connectedAt:t.startTime,isServer:!0,cwd:process.cwd()},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function ij(t,e){e.json(nj(t))}function aj(t,e,r){let n=e.query.instanceId;n&&t.mcpInstances.has(n)&&(t.mcpInstances.get(n).lastSeen=Date.now()),Gz(t);let i=IJ(t),a=t.sseClients.size,c={...{status:"online",connectedClients:a+i.length,queuedCommands:t.commandQueue.size,uptime:Date.now()-t.startTime,version:Xe,isClientMode:t.isClientMode,pid:process.pid,sessionId:t.sessionId},instanceId:t.instanceId,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:Jz(t),pluginVersion:t.pluginVersion||void 0,sseClients:a,pollingClients:i.length,pluginClients:i.map(l=>({clientId:l.clientId,projectName:l.projectName,lastSeen:Date.now()-l.lastSeen})),...t.isClientMode?{upstream:{reachable:t.clientModeUpstreamReachable,consecutiveFailures:t.clientModeConsecutiveHealthFailures,lastSuccessAt:t.clientModeLastHealthSuccessAt,lastFailureAt:t.clientModeLastHealthFailureAt,lastError:t.clientModeLastHealthError,baseUrl:t.baseUrl}}:{}};r.json(c)}function oj(t,e,r,n){let i=e.ip||e.socket.remoteAddress||"";if(!(i==="127.0.0.1"||i==="::1"||i==="::ffff:127.0.0.1"||i==="localhost")){v.warn("Shutdown request rejected from non-localhost",{ip:i}),r.status(403).json({error:"Forbidden: localhost only"});return}v.info("Shutdown request received, initiating graceful shutdown",{requestedBy:i,uptime:Date.now()-t.startTime}),r.json({status:"shutting_down",message:"Server will shutdown gracefully",pid:process.pid}),setTimeout(async()=>{try{await n(),v.info("Graceful shutdown completed"),process.exit(0)}catch(o){v.error("Error during graceful shutdown",o),process.exit(1)}},100)}async function Zd(t){if(t.isClientMode)try{let e=await fetch(`${t.baseUrl}/connection-info`);if(e.ok)return await e.json()}catch(e){v.warn("Failed to fetch connection info from server",{error:e})}return nj(t)}async function Hd(t,e,r){if(!r)return{success:!1,error:"Sync controller not available. Project sync is not initialized."};try{switch(t){case"sync_status":{let n=e.placeId;return{success:!0,data:r.getStatusDirect(n)}}case"sync_config":return{success:!0,data:r.getConfigDirect()};case"sync_history":{let n=e.placeId,i=e.query;return n==null?{success:!1,error:"placeId is required for sync_history"}:{success:!0,data:await r.getHistoryDirect(n,i)}}case"sync_directions":{let n=e.placeId;return{success:!0,data:r.getDirectionsDirect(n)}}case"sync_progress":{let n=e.placeId;return{success:!0,data:r.getProgressDirect(n)}}case"sync_read_file":{let n=e.placeId,i=e.instancePath;return n==null||!i?{success:!1,error:"placeId and instancePath are required"}:{success:!0,data:await r.readSyncedFile(n,i)}}case"sync_write_file":{let n=e.placeId,i=e.instancePath,a=e.content;return n==null||!i||a===void 0?{success:!1,error:"placeId, instancePath, and content are required"}:(await r.writeSyncedFile(n,i,a),{success:!0,data:{written:!0,placeId:n,instancePath:i}})}default:return{success:!1,error:`Unknown sync action: ${t}`}}}catch(n){let i=n instanceof Error?n.message:"Unknown error";return v.error("Sync action failed",{action:t,error:i}),{success:!1,error:i}}}async function b_(t,e,r){if(!r)return{success:!1,error:"Sync controller not available for disk operations."};try{return{success:!0,data:await r.executeViaDisk(t,e)}}catch(n){let i=n instanceof Error?n.message:"Unknown error";return v.error("Disk action failed",{action:t,error:i}),{success:!1,error:i}}}var PJ={get_cached_selection:"internal",sync_status:"internal",sync_config:"internal",sync_history:"internal",sync_directions:"internal",sync_read_file:"internal",sync_write_file:"internal",sync_progress:"internal",get_connection_info:"internal",run_test:"internal"};function Bd(t){return PJ[t]||"plugin"}var Vd=100;function sj(t,e,r){v.info("Plugin connected via SSE"),r.setHeader("Content-Type","text/event-stream"),r.setHeader("Cache-Control","no-cache"),r.setHeader("Connection","keep-alive"),t.sseClients.add(r),__(r,{event:"command",id:Ec(),data:{action:"connected",requestId:Ec(),params:{serverVersion:Xe,timestamp:Date.now()}}});let n=setInterval(()=>{__(r,{event:"command",id:Ec(),data:{action:"keepalive",requestId:Ec(),params:{timestamp:Date.now()}}})},3e4);r.on("close",()=>{v.info("Plugin disconnected from SSE"),clearInterval(n),t.sseClients.delete(r)})}function __(t,e){let r=JSON.stringify(e.data);t.write(`event: ${e.event}
|
|
117
|
+
`),this.clearTTLTimerForPlace(i,a),i.index.clearAllHashes(),i.index.clearClassMappings(),i.tmpIndex){let m=i.tmpIndex.getExplorerRoot(),h=i.index.getExplorerRoot();for(let[g,b]of i.tmpIndex.getAllHashes()){let w=$e.relative(m,g),_=$e.resolve(h,w);i.index.updateHashByValue(_,b)}for(let[g,b]of i.tmpIndex.getAllFileHashes()){let w=$e.relative(m,g),_=$e.resolve(h,w);i.index.updateFileHashByValue(_,b)}i.index.resetNameCounters(),i.index.mergeNameMappingsFrom(i.tmpIndex)}if(i.tmpIndex=null,i.tmpWriter&&(i.tmpWriter.stopChangeLogFlusher(),i.tmpWriter=null),this.pendingServiceTrees.delete(a),i.collisionDirMap=null,await i.index.saveToDisk(),i.state="syncing",i.activeFullSyncSessionId=null,i.syncProgress=null,this.ctx.touchRuntimePlace(e),this.ctx.activeFullSyncPlaceId=null,await this.ctx.startFileWatcherForPlace(i),i.fileWatcher&&await i.fileWatcher.waitUntilReady(),u.size>0){for(let[m,h]of u){let g=$e.resolve(o,m);try{await qt.mkdir($e.dirname(g),{recursive:!0}),await qt.writeFile(g,h,"utf-8")}catch(b){v.warn("Failed to restore preserved file",{path:m,error:b instanceof Error?b.message:String(b)})}}v.info("Restored preserved local files",{placeId:e,count:u.size})}if(p.length>0){let m=0;for(let h of p){let g=$e.resolve(o,h);try{await qt.unlink(g),i.index.removeHash(g),m++}catch(b){b.code!=="ENOENT"&&v.warn("Failed to delete preserved-as-deleted file",{path:h,error:b instanceof Error?b.message:String(b)})}}m>0&&(await i.index.saveToDisk(),v.info("Deleted locally-removed files from new sync",{placeId:e,count:m}))}i.writer.appendChangeLog(`FULL_SYNC_COMPLETE instances=${i.instanceCount} scripts=${i.scriptCount}`),i.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncComplete",direction:"forward",path:`place_${e}`,details:`instances:${i.instanceCount} scripts:${i.scriptCount}`}),v.info("Full sync completed",{placeId:e,instanceCount:i.instanceCount,scriptCount:i.scriptCount}),n.status(200).json({status:"completed",instanceCount:i.instanceCount,scriptCount:i.scriptCount,syncRoot:this.ctx.config.getPlaceRoot(e)})}setPreserveLocalFiles(e,r){this.preserveLocalFilesMap.set(e,r)}getAndClearPreserveLocalFiles(e){let r=this.preserveLocalFilesMap.get(e)||[];return this.preserveLocalFilesMap.delete(e),r}clearPreserveLocalFiles(e){this.preserveLocalFilesMap.delete(e)}clearPendingServiceTrees(e){this.pendingServiceTrees.delete(e)}startTTLTimerForPlace(e,r){let n=setTimeout(async()=>{v.warn("Incomplete sync TTL expired, cleaning up",{placeId:e.placeId,syncId:r});let i=this.ctx.config.getPlaceRoot(e.placeId),a=$e.join(i,`explorer_tmp_${r}`);try{await qt.rm(a,{recursive:!0,force:!0})}catch(o){v.error("Failed to clean up expired temp dir",o instanceof Error?o:new Error(String(o)))}e.incompleteSyncTimer=null,e.activeFullSyncSessionId===r&&(e.activeFullSyncSessionId=null,e.activeClientId=null,e.state="idle",e.tmpIndex=null,this.pendingServiceTrees.delete(r),e.collisionDirMap=null,e.tmpWriter&&(e.tmpWriter.stopChangeLogFlusher(),e.tmpWriter=null),this.ctx.activeFullSyncPlaceId===e.placeId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(e.placeId))},Az);n&&typeof n=="object"&&"unref"in n&&n.unref(),e.incompleteSyncTimer=n}getOrCreatePendingServiceTree(e,r){let n=this.pendingServiceTrees.get(e);n||(n=new Map,this.pendingServiceTrees.set(e,n));let i=n.get(r.serviceName);if(i)return i;let a={serviceName:r.tree?.name??r.serviceName,serviceClassName:r.tree?.className??r.serviceClassName,zeroBasedChunkIndex:r.chunkIndex===0,instances:[]};return n.set(r.serviceName,a),a}isLastChunk(e,r,n){return n<=1?!0:e.zeroBasedChunkIndex?r>=n-1:r>=n}buildServiceTree(e,r){let n={name:r.serviceName,className:r.serviceClassName,childCount:0,children:[],syncedAt:new Date().toISOString()};for(let i of r.instances){let a=this.resolveEffectiveSegments(e,i.effectivePath),o=pt(i.originalPath);this.upsertTreeNode(n,a,o,i.className)}return this.recomputeTreeChildCounts(n),n.syncedAt=new Date().toISOString(),n}resolveEffectiveSegments(e,r){return e.tmpIndex?pt(r).map(n=>e.tmpIndex.sanitizeName(n)):pt(r)}rewritePendingEffectivePaths(e,r,n,i){let a=$e.relative(r,n).split($e.sep).filter(l=>l.length>0),o=$e.relative(r,i).split($e.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=We(["game",...a]),c=We(["game",...o]);for(let l of e.instances){if(l.effectivePath===s){l.effectivePath=c;continue}(l.effectivePath.startsWith(`${s}.`)||l.effectivePath.startsWith(`${s}[`))&&(l.effectivePath=`${c}${l.effectivePath.slice(s.length)}`)}}upsertTreeNode(e,r,n,i){if(r.length<=1)return;let a=e.children;for(let o=1;o<r.length;o++){let s=r[o],c=n[o],l=o===r.length-1,u=a.find(p=>p.name===s);u?l&&(u.className=i,c!==void 0&&c!==s&&(u.originalName=c)):(u={name:s,className:l?i:"Folder",childCount:0,children:[]},c!==void 0&&c!==s&&(u.originalName=c),a.push(u)),u.children||(u.children=[]),a=u.children}}recomputeTreeChildCounts(e){let r=n=>{let i=n.children??[];n.children=i;for(let a of i)r(a);n.childCount=i.length};for(let n of e.children)r(n);e.childCount=e.children.length}clearTTLTimerForPlace(e,r){e.incompleteSyncTimer&&e.activeFullSyncSessionId===r&&(clearTimeout(e.incompleteSyncTimer),e.incompleteSyncTimer=null)}async cleanupStaleTempDirs(){let e=this.ctx.config.getSyncRoot();try{let r=await qt.readdir(e,{withFileTypes:!0});for(let n of r)if(n.isDirectory()){if(n.name.startsWith("explorer_tmp_")){let i=$e.join(e,n.name);v.warn("Removing stale temp directory from crashed sync",{dir:n.name});try{await qt.rm(i,{recursive:!0,force:!0})}catch(a){v.error(`Failed to remove stale temp dir: ${n.name}`,a instanceof Error?a:new Error(String(a)))}}if(n.name.startsWith("place_")){let i=$e.join(e,n.name);try{let a=await qt.readdir(i,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=$e.join(i,o.name);v.warn("Removing stale temp directory from crashed sync",{dir:`${n.name}/${o.name}`});try{await qt.rm(s,{recursive:!0,force:!0})}catch(c){v.error(`Failed to remove stale temp dir: ${n.name}/${o.name}`,c instanceof Error?c:new Error(String(c)))}}}catch{continue}}}}catch(r){if(r.code==="ENOENT")return;v.warn("Failed to scan for stale temp dirs",{error:r instanceof Error?r.message:String(r)})}}};import jr from"path";import{promises as Zz}from"fs";var Ld=class{constructor(e){this.ctx=e}async handleReversePending(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({pending:0,hasConflicts:!1,lastDetected:null});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}let a={pending:i.fileWatcher?.getPendingCount()??0,hasConflicts:!1,lastDetected:i.fileWatcher?.getLastDetected()??null,forwardRestoreNeeded:i.forwardRestoreQueue.length};this.ctx.touchRuntimePlace(n),r.status(200).json(a)}async handleReverseSyncChanges(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({changes:[],count:0});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}if(i.state!=="syncing"){r.status(400).json({error:"Not syncing",message:"Reverse sync is only available when sync is active"});return}let a=i.fileWatcher?.drainPendingChanges()??[],o=a.length>0?await i.reader.buildChangesFromPending(a):[];this.ctx.touchRuntimePlace(n),r.status(200).json({changes:o,count:o.length})}async handleReverseSyncResult(e,r){let n=e.body,i=n.placeId??this.ctx.getDefaultRuntimePlaceId();if(i==null){r.status(400).json({error:"Validation error",message:"placeId is required (in body or via active sync session)"});return}let a=n.appliedFiles??n.appliedPaths;if(!a||!Array.isArray(a)){r.status(400).json({error:"Validation error",message:"appliedFiles (or appliedPaths) must be an array of relative file paths"});return}let o=this.ctx.places.get(i);if(!o){r.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}this.ctx.touchRuntimePlace(i);let s=this.ctx.config.getPlaceRoot(i),c=0,l=[];for(let u of a){let p=jr.resolve(s,u);if(!Ad(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await Zz.readFile(p,"utf-8"),f=o.index.computeHash(d);o.index.updateHashByValue(p,f),o.index.updateFileHashByValue(p,f),c++}catch(d){let f=d.code;if(f==="ENOENT"){o.index.removeHash(p),o.index.removeHashesUnder(p);let m=this.resolveInstancePathForAppliedPath(o.index,p);if(m){let h=wt(m),g=St(m);g&&h&&await o.writer.removeFromTree(g,h)}c++}else f==="EISDIR"?c++:l.push({path:u,error:d instanceof Error?d.message:String(d)})}}c>0&&await o.index.saveToDisk(),c>0&&o.writer.appendHistory({timestamp:new Date().toISOString(),type:"reverseApply",direction:"reverse",path:`place_${i}`,details:`applied:${c} failed:${l.length}`}),r.status(200).json({updated:c,failed:l.length,errors:l})}async handleResolveConflict(e,r){let n=e.body;if(!n.fsPath||!n.resolution){r.status(400).json({error:"Validation error",message:"fsPath and resolution are required"});return}let{fsPath:i,resolution:a}=n,o=this.ctx.config.getSyncRoot();if(!Ad(o,jr.resolve(o,i))){r.status(403).json({error:"Forbidden",message:"Path is outside the sync root"});return}if(a==="skip"){r.status(200).json({status:"skipped",fsPath:i});return}let s;if(n.placeId&&(s=this.ctx.places.get(n.placeId)),!s){let d=i.match(/^place_(\d+)(?:_[^/]+)?\//);if(d){let f=parseInt(d[1],10);s=this.ctx.places.get(f)}else s=Array.from(this.ctx.places.values())[0]}if(!s){r.status(404).json({error:"No active place context",message:"No sync session is active. Start a sync first."});return}let c=this.ctx.config.getPlaceRoot(s.placeId),l=jr.resolve(c,i);if(!Ad(c,l)){r.status(403).json({error:"Forbidden",message:"Path is outside the place root"});return}let u=s.index,p=s.reader;if(a==="apply-studio"){u.resolveFile(l,"apply-studio"),await u.saveToDisk(),r.status(200).json({status:"resolved",resolution:"apply-studio",fsPath:i});return}if(a==="apply-file"){let d=await Zz.readFile(l,"utf-8"),f=u.computeHash(d);u.resolveFile(l,"apply-file",f),await u.saveToDisk();let m=p.getFileType(l),h=p.resolveInstancePathFromFile(l);r.status(200).json({status:"resolved",resolution:"apply-file",fsPath:i,instancePath:h,fileType:m,content:d});return}r.status(400).json({error:"Invalid resolution",message:`Unknown resolution: ${a}`})}async handleReverseRescan(e,r){let n=this.ctx.resolveQueryPlaceId(e);if(n==null){r.status(200).json({added:0});return}let i=this.ctx.places.get(n);if(!i){r.status(404).json({error:"Place not found",message:`No sync context for place ${n}`});return}if(!i.fileWatcher){r.status(200).json({added:0});return}let a=await i.fileWatcher.rescan();this.ctx.touchRuntimePlace(n),v.info("Reverse rescan completed",{placeId:n,added:a}),r.status(200).json({added:a})}resolveInstancePathForAppliedPath(e,r){let n=e.resolveInstancePathFromFsPath(r);if(n)return n;let i=e.getExplorerRoot(),a=jr.relative(i,r);if(a.startsWith("..")||a===""||jr.isAbsolute(a))return null;let o=a.split(jr.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=jr.basename(r),c=jr.dirname(r),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(Ma.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=i;for(let f of o){d=jr.join(d,f);let m=jr.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return We(p)}};function Hz(t){if(!t||typeof t!="object")return;let e=t;if(Array.isArray(e.instances))for(let r of e.instances)Array.isArray(r.properties)&&r.properties.length===0&&(r.properties={}),Array.isArray(r.attributes)&&r.attributes.length===0&&(r.attributes={});if(Array.isArray(e.changes))for(let r of e.changes)Array.isArray(r.properties)&&r.properties.length===0&&(r.properties={}),Array.isArray(r.attributes)&&r.attributes.length===0&&(r.attributes={})}var qd=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;constructor(e){this.config=new _d(e),this.apiHandler=new Ud(this),this.changeProcessor=new Md(this),this.initHandler=new Fd(this),this.reverseHandler=new Ld(this),this.places=new xd({max:3,dispose:(r,n)=>{v.info("Disposing place context (LRU eviction)",{placeId:n}),this.activeFullSyncPlaceId===n&&(this.activeFullSyncPlaceId=null),this.activeRuntimeSyncPlaceId===n&&(this.activeRuntimeSyncPlaceId=null),r.fileWatcher&&(r.fileWatcher.stop().catch(i=>{v.error("Error stopping file watcher during dispose",i)}),r.fileWatcher=null),r.writer.stopChangeLogFlusher(),r.incompleteSyncTimer&&(clearTimeout(r.incompleteSyncTimer),r.incompleteSyncTimer=null),r.index.saveToDisk().catch(i=>{v.error("Error saving index during dispose",i)}),r.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(r.activeFullSyncSessionId),r.tmpWriter&&(r.tmpWriter.stopChangeLogFlusher(),r.tmpWriter=null),r.tmpIndex=null,r.collisionDirMap=null}})}async getOrCreatePlaceContext(e,r){let n=this.places.get(e);if(n&&r){let i=this.config.getPlaceRoot(e),a=await this.config.resolvePlaceRoot(e,r);a!==i&&(v.info("Place root migrated, recreating context",{placeId:e,from:i,to:a}),this.places.delete(e),n=void 0)}if(!n){let i=await this.config.resolvePlaceRoot(e,r),a=x_.join(i,"explorer");await Ga.mkdir(i,{recursive:!0}),await Ga.mkdir(a,{recursive:!0});let o=new Dn(i,a);await o.loadFromDisk();let s=new qa(this.config,o,e),c=new kd(this.config,o,i);s.startChangeLogFlusher(),n={placeId:e,placeName:"",index:o,writer:s,reader:c,fileWatcher:null,state:"idle",activeClientId:null,activeFullSyncSessionId:null,instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,tmpIndex:null,tmpWriter:null,incompleteSyncTimer:null,changesSinceLastSave:0,directions:{...vi},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null},this.places.set(e,n),v.info("Created new place context",{placeId:e,placeRoot:i})}return n}async handleSyncInit(e,r){try{Hz(e.body);let n=Uz(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data,a=i.phase==="start"?i.placeId??null:i.phase==="chunk"||i.phase==="complete"?this.activeFullSyncPlaceId:null;if(a==null){r.status(400).json({error:"Missing placeId",message:"placeId is required in start phase or must be set by previous start"});return}switch(i.phase){case"start":await this.initHandler.handleInitStart(a,i,r);break;case"chunk":await this.initHandler.handleInitChunk(a,i,r);break;case"complete":await this.initHandler.handleInitComplete(a,i,r);break}}catch(n){this.sendError(r,n,"handleSyncInit")}}async handleSyncUpdate(e,r){try{Hz(e.body);let n=Fz(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data,a=i.placeId??this.getDefaultRuntimePlaceId();if(a==null){r.status(400).json({error:"Missing placeId",message:"placeId is required in body or must have an active sync session"});return}let o=await this.getOrCreatePlaceContext(a);if(o.activeFullSyncSessionId!==null||o.state==="initializing"){r.status(409).json({error:"Conflict",message:`Full sync in progress for place ${a}`});return}o.activeClientId=i.clientId,this.touchRuntimePlace(a);let s=Nz(o,i.changes);v.info("Sync update received",{placeId:a,clientId:i.clientId,changeCount:s.length,receivedCount:i.changes.length,types:s.map(f=>f.type)});let c=[],l=[],u=0,p=new Map;for(let f of s)try{let m=await this.changeProcessor.processChangeForPlace(o,f,p);m?l.push(m):u++}catch(m){let h="path"in f?f.path:"oldPath"in f?f.oldPath:"unknown";c.push({path:h,error:m instanceof Error?m.message:String(m)})}for(let f of p.values())try{await Dd(o,f)}catch(m){c.push({path:f.instancePath,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=Mz&&(await o.index.saveToDisk(),o.changesSinceLastSave=0),o.lastIncrementalSync=new Date().toISOString();let d={processed:u,failed:c.length,errors:c,syncedAt:o.lastIncrementalSync};l.length>0&&(d.conflicts=l),r.status(200).json(d)}catch(n){this.sendError(r,n,"handleSyncUpdate")}}getDefaultRuntimePlaceId(){if(this.activeRuntimeSyncPlaceId!==null&&this.activeRuntimeSyncPlaceId!==void 0){if(this.places.has(this.activeRuntimeSyncPlaceId))return this.activeRuntimeSyncPlaceId;this.activeRuntimeSyncPlaceId=null}if(this.activeFullSyncPlaceId!==null&&this.activeFullSyncPlaceId!==void 0&&this.places.has(this.activeFullSyncPlaceId))return this.activeRuntimeSyncPlaceId=this.activeFullSyncPlaceId,this.activeRuntimeSyncPlaceId;for(let[e,r]of this.places.entries())if(r.state==="syncing"||r.state==="initializing")return this.activeRuntimeSyncPlaceId=e,e;return this.activeRuntimeSyncPlaceId=null,null}touchRuntimePlace(e){this.activeRuntimeSyncPlaceId=e}clearRuntimePlaceIfMatch(e){this.activeRuntimeSyncPlaceId===e&&(this.activeRuntimeSyncPlaceId=null)}resolveQueryPlaceId(e,r="runtime"){let n=e.query.placeId;if(n){let i=parseInt(n,10);if(!isNaN(i))return i}return r==="full"?this.activeFullSyncPlaceId:this.getDefaultRuntimePlaceId()}async handleSyncStatus(e,r){try{let n=this.resolveQueryPlaceId(e);if(n==null){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};r.status(200).json(s);return}let i=this.places.get(n);if(!i){let s={state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.config.getPlaceRoot(n),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0};r.status(200).json(s);return}let a=i.fileWatcher?.getPendingCount()??0;this.touchRuntimePlace(n);let o={state:i.state,instanceCount:i.instanceCount,scriptCount:i.scriptCount,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,syncRoot:this.config.getPlaceRoot(n),activeClientId:i.activeClientId,reverseSyncAvailable:a>0,modifiedFileCount:a,directions:i.directions,fileWatcherActive:i.fileWatcher!==null,forwardOnlyClasses:[...Fa]};r.status(200).json(o)}catch(n){this.sendError(r,n,"handleSyncStatus")}}async handleSyncStop(e,r){try{let n=e.body,i=n.placeId??this.getDefaultRuntimePlaceId();if(i==null){r.status(200).json({status:"idle",state:"idle",placeId:null,message:"No active sync place"});return}let a=this.places.get(i);if(!a){r.status(200).json({status:"idle",state:"idle",placeId:i,message:`No sync context for place ${i}`});return}if(n.clientId&&a.activeClientId&&n.clientId!==a.activeClientId&&(a.activeFullSyncSessionId!==null||a.state==="syncing"||a.state==="initializing")){r.status(409).json({error:"Conflict",message:`This sync session belongs to client ${a.activeClientId}`});return}let o=a.activeFullSyncSessionId;if(o&&(this.initHandler.clearPreserveLocalFiles(o),this.initHandler.clearPendingServiceTrees(o)),a.fileWatcher&&(await a.fileWatcher.stop(),a.fileWatcher=null),a.incompleteSyncTimer&&(clearTimeout(a.incompleteSyncTimer),a.incompleteSyncTimer=null),o){let s=this.config.getPlaceRoot(i),c=x_.join(s,`explorer_tmp_${o}`);await Ga.rm(c,{recursive:!0,force:!0}).catch(()=>{})}a.tmpWriter&&(a.tmpWriter.stopChangeLogFlusher(),a.tmpWriter=null),a.tmpIndex=null,a.collisionDirMap=null,a.activeFullSyncSessionId=null,a.syncProgress=null,a.state="idle",a.activeClientId=null,a.instanceCount=0,a.scriptCount=0,this.activeFullSyncPlaceId===i&&(this.activeFullSyncPlaceId=null),this.clearRuntimePlaceIfMatch(i),v.info("Sync stopped",{placeId:i,reason:n.reason??"requested"}),r.status(200).json({status:"stopped",state:"idle",placeId:i})}catch(n){this.sendError(r,n,"handleSyncStop")}}async handleSyncConfig(e,r){try{if(e.method==="GET"){r.status(200).json(this.config.getConfig());return}let n=Lz(e.body);if(!n.success){r.status(400).json({error:"Validation error",message:n.error});return}let i=n.data;i.propertyMode!==void 0&&this.config.setPropertyMode(i.propertyMode),i.maxDepth!==void 0&&this.config.updateConfig({maxDepth:i.maxDepth}),i.maxInstances!==void 0&&this.config.updateConfig({maxInstances:i.maxInstances}),r.status(200).json({status:"updated",config:this.config.getConfig()})}catch(n){this.sendError(r,n,"handleSyncConfig")}}async initialize(){try{await this.config.loadFromMeta(),await this.initHandler.cleanupStaleTempDirs(),v.info("SyncController initialized")}catch(e){v.error("SyncController initialization failed",e instanceof Error?e:new Error(String(e)))}}async shutdown(){try{this.places.clear(),v.info("SyncController shut down")}catch(e){v.error("SyncController shutdown error",e instanceof Error?e:new Error(String(e)))}}async atomicWriteFile(e,r){let n=e+".tmp."+kJ().slice(0,8);try{await Ga.writeFile(n,r,"utf-8"),await Ga.rename(n,e)}catch(i){throw await Ga.unlink(n).catch(()=>{}),i}}async handlePreCheck(e,r){try{await this.apiHandler.handlePreCheck(e,r)}catch(n){this.sendError(r,n,"handlePreCheck")}}async handleSyncDirections(e,r){try{await this.apiHandler.handleSyncDirections(e,r)}catch(n){this.sendError(r,n,"handleSyncDirections")}}async handleForwardRestoreList(e,r){try{await this.apiHandler.handleForwardRestoreList(e,r)}catch(n){this.sendError(r,n,"handleForwardRestoreList")}}async handleReversePending(e,r){try{await this.reverseHandler.handleReversePending(e,r)}catch(n){this.sendError(r,n,"handleReversePending")}}async handleReverseSyncChanges(e,r){try{await this.reverseHandler.handleReverseSyncChanges(e,r)}catch(n){this.sendError(r,n,"handleReverseSyncChanges")}}async handleReverseSyncResult(e,r){try{await this.reverseHandler.handleReverseSyncResult(e,r)}catch(n){this.sendError(r,n,"handleReverseSyncResult")}}async handleResolveConflict(e,r){try{await this.reverseHandler.handleResolveConflict(e,r)}catch(n){this.sendError(r,n,"handleResolveConflict")}}async handleReverseRescan(e,r){try{await this.reverseHandler.handleReverseRescan(e,r)}catch(n){this.sendError(r,n,"handleReverseRescan")}}async handleSyncHistory(e,r){try{await this.apiHandler.handleSyncHistory(e,r)}catch(n){this.sendError(r,n,"handleSyncHistory")}}async startFileWatcherForPlace(e){if(e.state!=="syncing"){v.debug("Skipping file watcher start - place not syncing",{placeId:e.placeId,state:e.state});return}e.fileWatcher&&await e.fileWatcher.stop();let r=x_.join(this.config.getPlaceRoot(e.placeId),"explorer");e.fileWatcher=new jd(r,e.index),e.writer.setOnWriteCallback(n=>{e.fileWatcher?.suppressPath(n)}),e.fileWatcher.setDirectionChecker(n=>{let i=oz(n);return e.directions[i]}),e.fileWatcher.setOnForwardViolation(n=>{e.forwardRestoreQueue.includes(n)||(e.forwardRestoreQueue.push(n),v.info("Forward violation queued for restore",{placeId:e.placeId,relativePath:n,queueSize:e.forwardRestoreQueue.length}))}),await e.fileWatcher.start(),v.info("File watcher started for reverse sync",{placeId:e.placeId})}getStatusSummary(){return this.apiHandler.getStatusSummary()}getDirectionForCategory(e){return this.apiHandler.getDirectionForCategory(e)}getStatusDirect(e){return this.apiHandler.getStatusDirect(e)}getConfigDirect(){return this.apiHandler.getConfigDirect()}async getHistoryDirect(e,r){return this.apiHandler.getHistoryDirect(e,r)}getDirectionsDirect(e){return this.apiHandler.getDirectionsDirect(e)}getProgressDirect(e){return this.apiHandler.getProgressDirect(e)}async readSyncedFile(e,r){return this.apiHandler.readSyncedFile(e,r)}async writeSyncedFile(e,r,n){await this.apiHandler.writeSyncedFile(e,r,n)}async executeViaDisk(e,r){return this.apiHandler.executeViaDisk(e,r)}sendError(e,r,n){let i=r instanceof Error?r.message:String(r);if(i.includes("Path traversal detected")){e.status(403).json({error:"Forbidden",message:i});return}let a=r.code;if(a==="ENOSPC"||a==="EPERM"||a==="EACCES"){e.status(500).json({error:"Disk error",message:i});return}v.error(`SyncController.${n} failed`,r instanceof Error?r:new Error(i)),e.status(500).json({error:"Internal error",message:`${n}: ${i}`})}};import{randomUUID as $J}from"crypto";function Bz(t,e){let r=$J(),n=r.replace(/-/g,"").substring(0,8).toUpperCase(),i=`${n.substring(0,4)}-${n.substring(4,8)}`;return{config:t,app:e,instanceId:r,sessionId:i,startTime:Date.now(),baseUrl:`http://${t.httpHost}:${t.httpPort}`,commandQueue:new Map,pendingCommands:new Map,globalPendingCommands:[],totalCommandsProcessed:0,pluginClients:new Map,mcpInstances:new Map,sseClients:new Set,cachedSelectionMap:new Map,isClientMode:!1,clientModeHealthTimer:null,clientModeConsecutiveHealthFailures:0,clientModeUpstreamReachable:!0,clientModeLastHealthSuccessAt:null,clientModeLastHealthFailureAt:null,clientModeLastHealthError:null,historyManager:null,analyticsManager:null,licenseState:null,syncController:null,internalActionExecutor:null,playtestControlCommand:null,aiClientName:"",pluginVersion:"",syncedSessionToken:null}}var Vz=ro(hd(),1);function Wz(t){let e=Vz.default.json({limit:"5mb"});t.app.use((r,n,i)=>{if(r.path.startsWith("/sync/")){i();return}e(r,n,i)}),t.app.use((r,n,i)=>{n.setHeader("Access-Control-Allow-Origin","http://localhost:3002"),n.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),n.setHeader("Access-Control-Allow-Headers","Content-Type"),i()}),t.app.use((r,n,i)=>{v.debug(`${r.method} ${r.path}`,{ip:r.ip}),i()})}import{randomUUID as Ec}from"crypto";var Xe="1.5.1";function Jz(t){let e=new Set;t.aiClientName&&e.add(t.aiClientName);for(let r of t.mcpInstances.values())r.aiClientName&&e.add(r.aiClientName);return Array.from(e)}function IJ(t){let r=Date.now();return Array.from(t.pluginClients.values()).filter(n=>r-n.lastSeen<1e4)}function Gz(t){let r=Date.now();for(let[n,i]of t.mcpInstances)i.lastSeen&&r-i.lastSeen>15e3&&(t.mcpInstances.delete(n),v.debug("Removed stale MCP instance",{instanceId:n,lastSeen:i.lastSeen}))}function Kz(t,e,r){let n=e.query.clientId;if(n&&t.pluginClients.has(n)){let i=t.pluginClients.get(n);i.lastSeen=Date.now()}try{let i=e.body;if(!i||!Array.isArray(i.selection)||typeof i.count!="number"){v.warn("Invalid selection update request",{body:i}),r.status(400).json({error:"Invalid request body"});return}let a=n||"unknown",o=Date.now();t.cachedSelectionMap.set(a,{selection:i.selection,count:i.count,timestamp:o,clientId:a}),v.debug("Selection cache updated",{count:i.count,clientId:a,timestamp:o}),r.json({status:"ok",timestamp:o})}catch(i){v.error("Error handling selection update",i),r.status(500).json({error:"Internal server error"})}}function Xz(t,e,r){let n=parseInt(e.query.maxAge)||3e4,i=Tc(t,n);i?r.json({cached:!0,...i}):r.json({cached:!1,message:"No cached selection available"})}function Tc(t,e=3e4,r){if(t.cachedSelectionMap.size===0)return null;let n;if(r)n=t.cachedSelectionMap.get(r);else for(let a of t.cachedSelectionMap.values())(!n||a.timestamp>n.timestamp)&&(n=a);if(!n)return null;let i=Date.now()-n.timestamp;return e===0||i<=e?n:null}function Yz(t,e,r){try{let n=e.body;if(!n.clientId){r.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.get(n.clientId),a=Date.now(),o={clientId:n.clientId,projectName:n.projectName,placeName:n.placeName,placeId:n.placeId,connectedAt:i?.connectedAt||a,lastSeen:a,commandsProcessed:i?.commandsProcessed||0,connectionType:"polling"};t.pluginClients.set(n.clientId,o),t.pendingCommands.has(n.clientId)||t.pendingCommands.set(n.clientId,[]),n.pluginVersion&&(t.pluginVersion=n.pluginVersion),t.analyticsManager&&(n.pluginVersion&&t.analyticsManager.setPluginVersion(n.pluginVersion),t.analyticsManager.trackPluginConnected()),v.info("Plugin client registered",{clientId:n.clientId,projectName:n.projectName,placeName:n.placeName,isReconnect:!!i}),r.json({status:"ok",clientId:n.clientId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Xe,connectedAt:o.connectedAt,aiClientNames:Jz(t),serverStartTime:t.startTime,mcpInstanceCount:t.mcpInstances.size+1})}catch(n){v.error("Error registering plugin client",n),r.status(500).json({error:"Internal server error"})}}function Qz(t,e,r){let n=e.body?.clientId;if(!n){r.status(400).json({error:"Missing clientId"});return}let i=t.pluginClients.has(n);t.pluginClients.delete(n),t.pendingCommands.delete(n),v.info("Plugin client unregistered",{clientId:n,existed:i}),r.json({status:"ok",existed:i})}function ej(t,e,r){try{let n=e.body;if(!n.instanceId){r.status(400).json({error:"Missing instanceId"});return}let i=Date.now(),a={instanceId:n.instanceId,pid:n.pid,connectedAt:i,isServer:!1,lastSeen:i};n.aiClientName&&(a.aiClientName=n.aiClientName),n.cwd&&(a.cwd=n.cwd),t.mcpInstances.set(n.instanceId,a),v.info("MCP instance registered (client mode)",{instanceId:n.instanceId,pid:n.pid,cwd:n.cwd}),r.json({status:"ok",instanceId:n.instanceId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Xe,mcpInstanceCount:t.mcpInstances.size+1})}catch(n){v.error("Error registering MCP instance",n),r.status(500).json({error:"Internal server error"})}}function tj(t,e,r){let n=e.body?.instanceId;if(!n){r.status(400).json({error:"Missing instanceId"});return}let i=t.mcpInstances.has(n);t.mcpInstances.delete(n),v.info("MCP instance unregistered",{instanceId:n,existed:i}),r.json({status:"ok",existed:i})}function rj(t,e,r){let{instanceId:n,aiClientName:i}=e.body;if(n&&i){let a=t.mcpInstances.get(n);a&&(a.aiClientName=i)}r.json({status:"ok"})}function nj(t){let r=Date.now();for(let[n,i]of t.pluginClients)r-i.lastSeen>3e4&&(t.pluginClients.delete(n),t.pendingCommands.delete(n));return Gz(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Xe,uptime:r-t.startTime,serverStartTime:t.startTime,serverExecutable:process.execPath,serverHost:t.config.httpHost,serverPort:t.config.httpPort,serverPid:process.pid,mcpInstances:[{instanceId:t.instanceId,pid:process.pid,connectedAt:t.startTime,isServer:!0,cwd:process.cwd()},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function ij(t,e){e.json(nj(t))}function aj(t,e,r){let n=e.query.instanceId;n&&t.mcpInstances.has(n)&&(t.mcpInstances.get(n).lastSeen=Date.now()),Gz(t);let i=IJ(t),a=t.sseClients.size,c={...{status:"online",connectedClients:a+i.length,queuedCommands:t.commandQueue.size,uptime:Date.now()-t.startTime,version:Xe,isClientMode:t.isClientMode,pid:process.pid,sessionId:t.sessionId},instanceId:t.instanceId,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:Jz(t),pluginVersion:t.pluginVersion||void 0,sseClients:a,pollingClients:i.length,pluginClients:i.map(l=>({clientId:l.clientId,projectName:l.projectName,lastSeen:Date.now()-l.lastSeen})),...t.isClientMode?{upstream:{reachable:t.clientModeUpstreamReachable,consecutiveFailures:t.clientModeConsecutiveHealthFailures,lastSuccessAt:t.clientModeLastHealthSuccessAt,lastFailureAt:t.clientModeLastHealthFailureAt,lastError:t.clientModeLastHealthError,baseUrl:t.baseUrl}}:{}};r.json(c)}function oj(t,e,r,n){let i=e.ip||e.socket.remoteAddress||"";if(!(i==="127.0.0.1"||i==="::1"||i==="::ffff:127.0.0.1"||i==="localhost")){v.warn("Shutdown request rejected from non-localhost",{ip:i}),r.status(403).json({error:"Forbidden: localhost only"});return}v.info("Shutdown request received, initiating graceful shutdown",{requestedBy:i,uptime:Date.now()-t.startTime}),r.json({status:"shutting_down",message:"Server will shutdown gracefully",pid:process.pid}),setTimeout(async()=>{try{await n(),v.info("Graceful shutdown completed"),process.exit(0)}catch(o){v.error("Error during graceful shutdown",o),process.exit(1)}},100)}async function Zd(t){if(t.isClientMode)try{let e=await fetch(`${t.baseUrl}/connection-info`);if(e.ok)return await e.json()}catch(e){v.warn("Failed to fetch connection info from server",{error:e})}return nj(t)}async function Hd(t,e,r){if(!r)return{success:!1,error:"Sync controller not available. Project sync is not initialized."};try{switch(t){case"sync_status":{let n=e.placeId;return{success:!0,data:r.getStatusDirect(n)}}case"sync_config":return{success:!0,data:r.getConfigDirect()};case"sync_history":{let n=e.placeId,i=e.query;return n==null?{success:!1,error:"placeId is required for sync_history"}:{success:!0,data:await r.getHistoryDirect(n,i)}}case"sync_directions":{let n=e.placeId;return{success:!0,data:r.getDirectionsDirect(n)}}case"sync_progress":{let n=e.placeId;return{success:!0,data:r.getProgressDirect(n)}}case"sync_read_file":{let n=e.placeId,i=e.instancePath;return n==null||!i?{success:!1,error:"placeId and instancePath are required"}:{success:!0,data:await r.readSyncedFile(n,i)}}case"sync_write_file":{let n=e.placeId,i=e.instancePath,a=e.content;return n==null||!i||a===void 0?{success:!1,error:"placeId, instancePath, and content are required"}:(await r.writeSyncedFile(n,i,a),{success:!0,data:{written:!0,placeId:n,instancePath:i}})}default:return{success:!1,error:`Unknown sync action: ${t}`}}}catch(n){let i=n instanceof Error?n.message:"Unknown error";return v.error("Sync action failed",{action:t,error:i}),{success:!1,error:i}}}async function b_(t,e,r){if(!r)return{success:!1,error:"Sync controller not available for disk operations."};try{return{success:!0,data:await r.executeViaDisk(t,e)}}catch(n){let i=n instanceof Error?n.message:"Unknown error";return v.error("Disk action failed",{action:t,error:i}),{success:!1,error:i}}}var PJ={get_cached_selection:"internal",sync_status:"internal",sync_config:"internal",sync_history:"internal",sync_directions:"internal",sync_read_file:"internal",sync_write_file:"internal",sync_progress:"internal",get_connection_info:"internal",run_test:"internal"};function Bd(t){return PJ[t]||"plugin"}var Vd=100;function sj(t,e,r){v.info("Plugin connected via SSE"),r.setHeader("Content-Type","text/event-stream"),r.setHeader("Cache-Control","no-cache"),r.setHeader("Connection","keep-alive"),t.sseClients.add(r),__(r,{event:"command",id:Ec(),data:{action:"connected",requestId:Ec(),params:{serverVersion:Xe,timestamp:Date.now()}}});let n=setInterval(()=>{__(r,{event:"command",id:Ec(),data:{action:"keepalive",requestId:Ec(),params:{timestamp:Date.now()}}})},3e4);r.on("close",()=>{v.info("Plugin disconnected from SSE"),clearInterval(n),t.sseClients.delete(r)})}function __(t,e){let r=JSON.stringify(e.data);t.write(`event: ${e.event}
|
|
118
118
|
`),t.write(`id: ${e.id}
|
|
119
119
|
`),t.write(`data: ${r}
|
|
120
120
|
|
|
@@ -126,7 +126,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
126
126
|
`)}this.initialized=!0,v.info("Tool history manager initialized",{sessionId:e,localHistoryEnabled:this.config.enableLocalHistory,localStatisticsEnabled:this.config.enableLocalStatistics,dataDir:this.config.dataDir})}catch(r){throw v.error("Failed to initialize tool history manager",r),r}}async ensureDirectories(){await Or.promises.mkdir(this.historyDir,{recursive:!0}),await Or.promises.mkdir(this.statisticsDir,{recursive:!0})}async loadStatistics(){try{let e=await Or.promises.readFile(this.statisticsFilePath,"utf-8");this.statistics=JSON.parse(e),v.debug("Loaded existing statistics",{totalCalls:this.statistics.totalCalls,totalSessions:this.statistics.totalSessions})}catch(e){e.code==="ENOENT"?(this.statistics=R_(),v.debug("No existing statistics file, starting fresh")):(v.error("Failed to load statistics, starting fresh",e),this.statistics=R_())}}async saveStatistics(){if(this.config.enableLocalStatistics){if(this.saveStatisticsDebounceTimer){this.pendingStatisticsSave=!0;return}this.statistics.lastUpdated=new Date().toISOString();try{await Or.promises.writeFile(this.statisticsFilePath,JSON.stringify(this.statistics,null,2))}catch(e){v.error("Failed to save statistics",e)}this.saveStatisticsDebounceTimer=setTimeout(async()=>{this.saveStatisticsDebounceTimer=null,this.pendingStatisticsSave&&(this.pendingStatisticsSave=!1,await this.saveStatistics())},1e3)}}async recordSuccess(e,r,n,i,a){if(!this.initialized){v.warn("Tool history manager not initialized, skipping record");return}let o=Ka(e),s=new Date().toISOString();if(this.config.enableLocalStatistics&&(this.updateToolStats(e,o,!0,i,s),await this.saveStatistics()),this.config.enableLocalHistory&&this.historyFilePath){this.sequenceNumber++;let c={id:C_(),sessionId:this.sessionId,timestamp:s,sequenceNumber:this.sequenceNumber,toolName:e,tier:o,parameters:r,result:n,executionTimeMs:i,clientId:a};try{await Or.promises.appendFile(this.historyFilePath,JSON.stringify(c)+`
|
|
127
127
|
`)}catch(l){v.error("Failed to write history entry",l)}}}async recordFailure(e,r,n,i,a){if(!this.initialized){v.warn("Tool history manager not initialized, skipping record");return}let o=Ka(e),s=new Date().toISOString();if(this.config.enableLocalStatistics){this.updateToolStats(e,o,!1,i||0,s),await this.saveStatistics();let c={id:C_(),sessionId:this.sessionId,timestamp:s,toolName:e,tier:o,parameters:r,errorMessage:n,errorCode:a,executionTimeMs:i};try{await Or.promises.appendFile(this.failureLogPath,JSON.stringify(c)+`
|
|
128
128
|
`)}catch(l){v.error("Failed to write failure log",l)}}}updateToolStats(e,r,n,i,a){this.statistics.totalCalls++,n?this.statistics.totalSuccessCalls++:this.statistics.totalFailedCalls++,this.statistics.tierSummary[r].totalCalls++,n?this.statistics.tierSummary[r].successCount++:this.statistics.tierSummary[r].failureCount++,this.statistics.tools[e]||(this.statistics.tools[e]={toolName:e,tier:r,totalCalls:0,successCount:0,failureCount:0,totalExecutionTimeMs:0,avgExecutionTimeMs:0,firstUsed:a,lastUsed:a});let o=this.statistics.tools[e];o.totalCalls++,o.totalExecutionTimeMs+=i,o.avgExecutionTimeMs=Math.round(o.totalExecutionTimeMs/o.totalCalls),o.lastUsed=a,n?o.successCount++:o.failureCount++}getStatistics(){return{...this.statistics}}getTopTools(e=10){return Object.values(this.statistics.tools).sort((r,n)=>n.totalCalls-r.totalCalls).slice(0,e)}async shutdown(){if(this.initialized){if(this.saveStatisticsDebounceTimer&&(clearTimeout(this.saveStatisticsDebounceTimer),this.saveStatisticsDebounceTimer=null),this.config.enableLocalStatistics){this.statistics.lastUpdated=new Date().toISOString();try{await Or.promises.writeFile(this.statisticsFilePath,JSON.stringify(this.statistics,null,2))}catch(e){v.error("Failed to save final statistics",e)}}if(this.config.enableLocalHistory&&this.historyFilePath){let e={sessionId:this.sessionId,startTime:"",endTime:new Date().toISOString(),totalCommands:this.sequenceNumber,pid:process.pid,version:Xe};try{await Or.promises.appendFile(this.historyFilePath,`# SESSION_END ${JSON.stringify(e)}
|
|
129
|
-
`)}catch(r){v.error("Failed to write session end marker",r)}}this.initialized=!1,v.info("Tool history manager shutdown",{sessionId:this.sessionId,totalCommands:this.sequenceNumber})}}isLocalHistoryEnabled(){return this.config.enableLocalHistory}isLocalStatisticsEnabled(){return this.config.enableLocalStatistics}getHistoryDir(){return this.historyDir}getStatisticsDir(){return this.statisticsDir}};import{createHash as WJ,randomUUID as JJ}from"crypto";import*as Ya from"fs";import*as j_ from"path";import*as Ej from"os";var z_={"claude-code":"Claude Code",claude:"Claude",cursor:"Cursor","codex-cli":"Codex CLI","openai-codex":"Codex CLI","gemini-cli":"Gemini CLI",windsurf:"Windsurf",cline:"Cline",continue:"Continue",zed:"Zed",vscode:"VS Code"};function Tj(t){let e=t.toLowerCase();if(z_[e])return z_[e];for(let[r,n]of Object.entries(z_))if(e.includes(r))return n;return t}function ef(){return process.env.CLAUDECODE||process.env.CLAUDE_CODE_ENTRYPOINT?{name:"Claude Code",version:""}:process.env.CURSOR_EDITOR?{name:"Cursor",version:""}:process.env.WINDSURF_EDITOR?{name:"Windsurf",version:""}:process.env.CONTINUE_GLOBAL_DIR?{name:"Continue",version:""}:process.env.CLINE_CONTEXT?{name:"Cline",version:""}:null}var GJ="https://www.google-analytics.com/mp/collect",KJ="G-87FMYC2KLT",XJ="ANCUVw5rTZ6ZRMOBd_RLMw",YJ=6e4,QJ=20,eG=25,tG=5e3,rG=".weppy-roblox-mcp",nG="device-id.json",tf=class{enabled;clientId;deviceKey;sessionId="";sessionStartTime=0;toolCallsTotal=0;toolCallsSuccess=0;toolCallsFailed=0;eventQueue=[];flushTimer=null;aiClient="unknown";pluginVersion="";tier="basic";userPropertiesDirty=!0;constructor(e){this.enabled=this.resolveEnabled(e.enabled),this.clientId=this.loadOrCreateDeviceId(),this.deviceKey=this.createDeviceKey(this.clientId),this.enabled||v.debug("Telemetry disabled")}getAnalyticsDirPath(){return j_.join(Ej.homedir(),rG)}getDeviceIdPath(){return j_.join(this.getAnalyticsDirPath(),nG)}loadOrCreateDeviceId(){let e=this.getDeviceIdPath();try{let n=Ya.readFileSync(e,"utf-8"),i=JSON.parse(n);if(typeof i.deviceId=="string"&&i.deviceId.length>0)return i.deviceId}catch{}let r=JJ();try{Ya.mkdirSync(this.getAnalyticsDirPath(),{recursive:!0}),Ya.writeFileSync(e,JSON.stringify({deviceId:r},null,2),"utf-8")}catch(n){v.warn("Failed to persist telemetry device ID",{error:n})}return r}createDeviceKey(e){return WJ("sha256").update(e).digest("hex").slice(0,32)}resolveEnabled(e){let r=process.env.ENABLE_TELEMETRY;return r!==void 0&&r!==""?r.toLowerCase()==="true"||r==="1":e??!0}initialize(e){if(!this.enabled)return;let r=Date.now();this.sessionId=e,this.sessionStartTime=r;let n=ef();n&&(this.aiClient=n.name),this.flushTimer=setInterval(()=>this.flush(),YJ),this.flushTimer.unref(),v.debug("Telemetry initialized",{sessionId:e})}getCommonEventParams(){return{device_key:this.deviceKey,mcp_version:Xe,plugin_version:this.pluginVersion||"unknown",platform:process.platform,user_tier:this.tier,ai_client:this.aiClient,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"unknown",locale:Intl.DateTimeFormat().resolvedOptions().locale||"unknown"}}trackSessionStart(){this.enabled&&this.enqueue({name:"mcp_session_start",params:{...this.getCommonEventParams(),server_session_id:this.sessionId}})}trackSessionEnd(){if(!this.enabled)return;let e=Math.round((Date.now()-this.sessionStartTime)/1e3);this.enqueue({name:"mcp_session_end",params:{...this.getCommonEventParams(),server_session_id:this.sessionId,duration_sec:e,tool_calls_total:this.toolCallsTotal,tool_calls_success:this.toolCallsSuccess,tool_calls_failed:this.toolCallsFailed}})}trackToolCall(e,r,n,i,a,o){if(!this.enabled)return;this.toolCallsTotal++,n?this.toolCallsSuccess++:this.toolCallsFailed++;let s={...this.getCommonEventParams(),tool_name:e,required_tier:r,success:n?1:0,tool_result:n?"success":"error"};i&&(s.action=i),!n&&a&&(s.error_type=a),!n&&o&&(s.error_detail=o),this.enqueue({name:"mcp_tool_call",params:s})}trackToolError(e,r,n,i,a){if(!this.enabled)return;let o={...this.getCommonEventParams(),tool_name:e,required_tier:r};n&&(o.action=n),i&&(o.error_type=i),a&&(o.error_detail=a),this.enqueue({name:"mcp_tool_error",params:o})}trackPluginConnected(){this.enabled&&this.enqueue({name:"mcp_plugin_connected",params:{...this.getCommonEventParams(),server_session_id:this.sessionId}})}setAiClient(e){this.aiClient=e,this.userPropertiesDirty=!0}setPluginVersion(e){this.pluginVersion=e,this.userPropertiesDirty=!0}setTier(e){this.tier=e,this.userPropertiesDirty=!0}isEnabled(){return this.enabled}enqueue(e){this.eventQueue.push(e),this.eventQueue.length>=QJ&&this.flush()}flush(){if(this.eventQueue.length===0)return;let e=this.eventQueue.splice(0,eG),r={client_id:this.clientId,events:e};this.userPropertiesDirty&&(r.user_properties={mcp_version:{value:Xe},plugin_version:{value:this.pluginVersion||"unknown"},device_key:{value:this.deviceKey},platform:{value:process.platform},user_tier:{value:this.tier},ai_client:{value:this.aiClient}},this.userPropertiesDirty=!1),this.sendToGA4(r),this.eventQueue.length>0&&this.flush()}sendToGA4(e){let r=`${GJ}?measurement_id=${KJ}&api_secret=${XJ}`,n=new AbortController,i=setTimeout(()=>n.abort(),tG);fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:n.signal}).then(()=>{clearTimeout(i),v.debug("Telemetry events sent",{count:e.events.length})}).catch(()=>{clearTimeout(i)})}async shutdown(){this.enabled&&(this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null),this.eventQueue.length>0&&this.flush(),await new Promise(e=>setTimeout(e,500)),v.debug("Telemetry shutdown complete"))}};import Rj from"path";import iG from"os";import{promises as D_}from"fs";var aG="license-state.json",O_=4;function oG(){return Rj.join(iG.homedir(),".weppy-roblox-mcp",aG)}var sG=new Set(["active","unknown","grace","revoked","invalid","unlicensed"]);function cG(){return{canUsePro:!1,status:"unlicensed",checkedAt:Math.floor(Date.now()/1e3)}}function rf(){return{version:O_,updatedAt:Date.now(),state:cG()}}function lG(){return Math.floor(Date.now()/1e3)}function Cj(t){let e=typeof t?.status=="string"&&sG.has(t.status)?t.status:"unlicensed",r={canUsePro:t?.canUsePro===!0,status:e,checkedAt:typeof t?.checkedAt=="number"?t.checkedAt:lG()};return typeof t?.maskedKey=="string"&&(r.maskedKey=t.maskedKey),typeof t?.billingState=="string"&&(r.billingState=t.billingState),typeof t?.reason=="string"&&(r.reason=t.reason),(t?.decisionSource==="provider_live"||t?.decisionSource==="provider_webhook"||t?.decisionSource==="license_server_cache")&&(r.decisionSource=t.decisionSource),t?.abuse&&typeof t.abuse=="object"&&t.abuse.blocked!==void 0&&(r.abuse={blocked:t.abuse.blocked===!0},typeof t.abuse.blockedUntil=="number"&&(r.abuse.blockedUntil=t.abuse.blockedUntil),typeof t.abuse.blockReason=="string"&&(r.abuse.blockReason=t.abuse.blockReason)),typeof t?.tokenStatus=="string"&&(r.tokenStatus=t.tokenStatus),typeof t?.statusDetail=="string"&&(r.statusDetail=t.statusDetail),typeof t?.lastLicenseServerSuccessAt=="number"&&(r.lastLicenseServerSuccessAt=t.lastLicenseServerSuccessAt),typeof t?.graceUntil=="number"&&(r.graceUntil=t.graceUntil),typeof t?.lastSuccessfulCheckAt=="number"&&(r.lastSuccessfulCheckAt=t.lastSuccessfulCheckAt),typeof t?.nextRecheckAt=="number"&&(r.nextRecheckAt=t.nextRecheckAt),typeof t?.fallbackUntil=="number"&&(r.fallbackUntil=t.fallbackUntil),typeof r.lastLicenseServerSuccessAt=="number"&&r.lastSuccessfulCheckAt===void 0&&(r.lastSuccessfulCheckAt=r.lastLicenseServerSuccessAt),typeof r.lastSuccessfulCheckAt=="number"&&r.lastLicenseServerSuccessAt===void 0&&(r.lastLicenseServerSuccessAt=r.lastSuccessfulCheckAt),typeof r.graceUntil=="number"&&r.fallbackUntil===void 0&&(r.fallbackUntil=r.graceUntil),typeof r.fallbackUntil=="number"&&r.graceUntil===void 0&&(r.graceUntil=r.fallbackUntil),r}var nf=class{cacheFilePath;cache=rf();constructor(e){this.cacheFilePath=e??oG()}getState(){return{...this.cache.state}}getProvider(){return this.cache.provider}getSessionToken(){return this.cache.sessionToken}async initialize(){try{let e=await D_.readFile(this.cacheFilePath,"utf8"),r=JSON.parse(e);if(!r||typeof r!="object"){this.cache=rf();return}let n=r,i=n.state&&typeof n.state=="object"?n.state:void 0,a=Cj(i),o={version:O_,updatedAt:typeof n.updatedAt=="number"?n.updatedAt:Date.now(),state:a};typeof n.provider=="string"&&n.provider.trim()!==""&&(o.provider=n.provider.trim().toLowerCase()),typeof n.sessionToken=="string"&&n.sessionToken.length>0&&(o.sessionToken=n.sessionToken),this.cache=o}catch{this.cache=rf()}}async persistState(e,r,n){let i={version:O_,updatedAt:Date.now(),state:Cj(e)},a=r??this.cache.provider;typeof a=="string"&&a.trim()!==""&&(i.provider=a.trim().toLowerCase());let o=n??e.sessionToken??this.cache.sessionToken;typeof o=="string"&&o.length>0&&(i.sessionToken=o),this.cache=i,await this.write()}getBackupData(){let e=this.cache.sessionToken,r=this.cache.provider;return!r||!e?null:{sessionToken:e,provider:r,savedAt:this.cache.updatedAt}}async clear(){this.cache=rf(),await this.write()}async write(){await D_.mkdir(Rj.dirname(this.cacheFilePath),{recursive:!0}),await D_.writeFile(this.cacheFilePath,JSON.stringify(this.cache,null,2),{mode:384})}};var zj=10*60*60,uG=14*60*60;function Nn(){return Math.floor(Date.now()/1e3)}function pG(t){return{...t,checkedAt:t.checkedAt||Nn()}}var af=class{client;cache;mcpInstanceId;fallbackMaxSeconds;random;refreshTimer=null;refreshInFlight=!1;constructor(e,r,n,i={}){this.client=e,this.cache=r,this.mcpInstanceId=n,this.fallbackMaxSeconds=Math.max(1,i.fallbackMaxHours??48)*60*60,this.random=i.random??Math.random}async initialize(){await this.cache.initialize(),this.scheduleFromCurrentState()}async shutdown(){this.clearScheduledRefresh()}isConfigured(){return this.client.isConfigured()}clearScheduledRefresh(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null)}computeNextRefreshAt(e=Nn()){let r=uG-zj,n=zj+Math.floor(this.random()*(r+1));return e+n}scheduleRefreshAt(e){this.clearScheduledRefresh();let r=Nn(),n=Math.max(0,(e-r)*1e3);this.refreshTimer=setTimeout(()=>{this.runScheduledRefresh()},n),this.refreshTimer.unref?.()}scheduleFromCurrentState(){if(!this.client.isConfigured()){this.clearScheduledRefresh();return}let e=!!this.cache.getSessionToken(),r=this.cache.getProvider();if(!e||!r){this.clearScheduledRefresh();return}let n=this.cache.getState(),i=typeof n.nextRecheckAt=="number"?n.nextRecheckAt:this.computeNextRefreshAt();this.scheduleRefreshAt(i)}getLastSuccessAt(e){return e.lastLicenseServerSuccessAt??e.lastSuccessfulCheckAt}getGraceUntil(e){return e.graceUntil??e.fallbackUntil}withCompatibilityFields(e){let r={...e},n=this.getLastSuccessAt(r);typeof n=="number"&&(r.lastLicenseServerSuccessAt=n,r.lastSuccessfulCheckAt=n);let i=this.getGraceUntil(r);return typeof i=="number"&&(r.graceUntil=i,r.fallbackUntil=i),r}async runScheduledRefresh(){if(!this.refreshInFlight){this.refreshInFlight=!0;try{await this.refresh({})}catch(e){v.warn("Scheduled license refresh failed",{error:e instanceof Error?e.message:"unknown_error"})}finally{this.refreshInFlight=!1,this.scheduleFromCurrentState()}}}withLocalTransitionDiagnostics(e,r,n){let{tokenStatus:i,tokenReason:a,statusDetail:o,...s}=e;return{...s,...r,statusDetail:n}}buildSuccessfulRemoteState(e,r,n){let i={...e,checkedAt:r,nextRecheckAt:n},a=this.getLastSuccessAt(e),o=this.getGraceUntil(e);return i.lastLicenseServerSuccessAt=typeof a=="number"?a:r,i.lastSuccessfulCheckAt=i.lastLicenseServerSuccessAt,i.graceUntil=typeof o=="number"?o:r+this.fallbackMaxSeconds,i.fallbackUntil=i.graceUntil,this.withCompatibilityFields(i)}evaluateEffectiveState(e){let r=pG(e),n=Nn();if(r.status==="revoked"||r.status==="invalid"||r.status==="unlicensed")return{...r,canUsePro:!1};if(r.status==="active")return r.canUsePro?r:this.withLocalTransitionDiagnostics(r,{canUsePro:!1,status:"invalid",reason:r.reason??"invalid_active_state"},"invalid_local_evaluation");if(r.status==="grace"){let i=this.getGraceUntil(r);return typeof i=="number"&&n<=i?{...r,canUsePro:!0}:this.withLocalTransitionDiagnostics(r,{canUsePro:!1,status:"unlicensed",reason:"network_unavailable_fallback_window_exceeded"},"unlicensed_local_evaluation")}if(r.status==="unknown"){let i=this.getLastSuccessAt(r);return typeof i=="number"&&n<=i+this.fallbackMaxSeconds&&r.canUsePro?this.withLocalTransitionDiagnostics(r,{status:"grace",canUsePro:!0,graceUntil:i+this.fallbackMaxSeconds,fallbackUntil:i+this.fallbackMaxSeconds,reason:"network_unavailable_fallback_to_last_success"},"grace_local_evaluation"):this.withLocalTransitionDiagnostics(r,{canUsePro:!1,status:"unlicensed",reason:r.reason??"license_check_unavailable"},"unlicensed_local_evaluation")}return this.withLocalTransitionDiagnostics(r,{canUsePro:!1,status:"unlicensed",reason:r.reason??"license_unknown_state"},"unlicensed_local_evaluation")}buildPersistedState(e){let r=e.checkedAt||Nn(),n=this.computeNextRefreshAt(r),i={...e,checkedAt:r,nextRecheckAt:n};return e.canUsePro&&(e.status==="active"||e.status==="grace")?this.buildSuccessfulRemoteState(e,r,n):this.withCompatibilityFields(i)}buildFallbackState(e){let r=Nn(),n=this.computeNextRefreshAt(r);if(e.canUsePro&&typeof this.getLastSuccessAt(e)=="number"&&r<=this.getLastSuccessAt(e)+this.fallbackMaxSeconds){let i=this.getLastSuccessAt(e);return this.withLocalTransitionDiagnostics(e,{canUsePro:!0,status:"grace",checkedAt:r,lastLicenseServerSuccessAt:i,lastSuccessfulCheckAt:i,graceUntil:i+this.fallbackMaxSeconds,fallbackUntil:i+this.fallbackMaxSeconds,nextRecheckAt:n,reason:"network_unavailable_fallback_to_last_success"},"grace_network_fallback")}return this.withCompatibilityFields(this.withLocalTransitionDiagnostics(e,{canUsePro:!1,status:"unlicensed",checkedAt:r,nextRecheckAt:n,reason:"network_unavailable_fallback_window_exceeded"},"unlicensed_fallback_expired"))}getStatus(){let e=this.cache.getState(),r=this.evaluateEffectiveState(e),n=!!this.cache.getSessionToken();return this.client.isConfigured()&&n&&this.cache.getProvider()&&typeof e.nextRecheckAt=="number"&&Nn()>=e.nextRecheckAt&&!this.refreshInFlight&&this.runScheduledRefresh(),this.withCompatibilityFields(r)}async activate(e){if(!this.client.isConfigured())throw new Error("License API is not configured. Set LICENSE_API_BASE_URL, LICENSE_PROJECT_ID, LICENSE_PROVIDER.");let r={licenseKey:e.licenseKey,provider:e.provider.trim().toLowerCase(),mcpInstanceId:this.mcpInstanceId};if(!r.provider)throw new Error("provider is required");e.pluginClientId&&(r.pluginClientId=e.pluginClientId),e.deviceId&&(r.deviceId=e.deviceId);let n=await this.client.activate(r),i=this.buildPersistedState(n);await this.cache.persistState(i,r.provider,n.sessionToken??""),this.scheduleFromCurrentState();let a=this.withCompatibilityFields(this.evaluateEffectiveState(i));return n.sessionToken&&(a.sessionToken=n.sessionToken),a}async refresh(e){if(!this.client.isConfigured())return this.getStatus();let r=(e.provider??this.cache.getProvider())?.trim().toLowerCase();if(!r){let a={canUsePro:!1,status:"unlicensed",checkedAt:Nn(),reason:"license_provider_missing"};return await this.cache.persistState(a),this.clearScheduledRefresh(),a}let n=e.sessionToken??this.cache.getSessionToken();if(n)try{let a={sessionToken:n,provider:r,mcpInstanceId:this.mcpInstanceId};e.pluginClientId&&(a.pluginClientId=e.pluginClientId),e.deviceId&&(a.deviceId=e.deviceId);let o=await this.client.refresh(a),s=this.buildPersistedState(o);return await this.cache.persistState(s,r,o.sessionToken),this.scheduleFromCurrentState(),this.withCompatibilityFields(this.evaluateEffectiveState(s))}catch(a){v.warn("License refresh failed, falling back",{error:a instanceof Error?a.message:"unknown_error"});let o=this.buildFallbackState(this.cache.getState());return await this.cache.persistState(o,r),this.scheduleFromCurrentState(),this.withCompatibilityFields(this.evaluateEffectiveState(o))}let i=this.evaluateEffectiveState(this.cache.getState());return await this.cache.persistState(i,r),this.scheduleFromCurrentState(),this.withCompatibilityFields(i)}async evaluateProAccess(){let e=this.getStatus();if(e.canUsePro)return{allowed:!0,state:e};if(e.reason==="device_temporarily_blocked")return{allowed:!1,state:e,reason:"This device is temporarily blocked due to multi-device activity."};let r="License required";return e.status==="revoked"?r="License revoked. Pro tools are disabled.":e.status==="invalid"?r="License invalid. Please re-enter a valid license key.":(e.status==="unknown"||e.status==="grace")&&(r=e.reason??"License check is temporarily unavailable."),{allowed:!1,state:e,reason:r}}getBackupData(){return this.cache.getBackupData()}async persistSyncedToken(e,r,n){let i=n?this.buildPersistedState(n):this.cache.getState();await this.cache.persistState(i,r,e),this.scheduleFromCurrentState()}async clearCredentials(){await this.cache.clear(),this.clearScheduledRefresh()}};import{mkdir as dG,rename as fG,writeFile as mG}from"fs/promises";import Qa from"path";import{randomUUID as hG}from"crypto";var gG="test-report.md",vG="test-log.txt";function Cc(t){return String(t).padStart(2,"0")}function yG(t){return[t.getFullYear(),Cc(t.getMonth()+1),Cc(t.getDate()),"-",Cc(t.getHours()),Cc(t.getMinutes()),Cc(t.getSeconds())].join("")}function N_(t){return typeof t!="number"?"-":new Date(t).toISOString()}async function jj(t,e){let r=`${t}.${hG()}.tmp`;await mG(r,e,"utf8"),await fG(r,t)}function xG(t){let e=t.timedOut?"timed_out":t.passed?"passed":"failed",r=t.signals.find(a=>a.kind==="START"),n=t.signals.find(a=>a.kind==="FINISHED"),i=["# Test Report","",`- Status: ${e}`,`- Test Name: ${t.testName}`,`- Mode: ${t.mode}`,`- Place ID: ${t.placeId}`,`- Place Name: ${t.placeName||"(unknown)"}`,`- Duration (ms): ${t.durationMs}`,`- Total Logs: ${t.logs.length}`,`- Signal Count: ${t.signals.length}`,"","## Signals","",`- START: ${r?N_(r.timestampMs):"missing"}`,`- FINISHED: ${n?N_(n.timestampMs):"missing"}`];return t.errorMessage&&i.push("","## Error","","```text",t.errorMessage,"```"),`${i.join(`
|
|
129
|
+
`)}catch(r){v.error("Failed to write session end marker",r)}}this.initialized=!1,v.info("Tool history manager shutdown",{sessionId:this.sessionId,totalCommands:this.sequenceNumber})}}isLocalHistoryEnabled(){return this.config.enableLocalHistory}isLocalStatisticsEnabled(){return this.config.enableLocalStatistics}getHistoryDir(){return this.historyDir}getStatisticsDir(){return this.statisticsDir}};import{createHash as WJ,randomUUID as JJ}from"crypto";import*as Ya from"fs";import*as j_ from"path";import*as Ej from"os";var z_={"claude-code":"Claude Code",claude:"Claude",cursor:"Cursor","codex-cli":"Codex CLI","openai-codex":"Codex CLI","gemini-cli":"Gemini CLI",windsurf:"Windsurf",cline:"Cline",continue:"Continue",zed:"Zed",vscode:"VS Code"};function Tj(t){let e=t.toLowerCase();if(z_[e])return z_[e];for(let[r,n]of Object.entries(z_))if(e.includes(r))return n;return t}function ef(){return process.env.CLAUDECODE||process.env.CLAUDE_CODE_ENTRYPOINT?{name:"Claude Code",version:""}:process.env.CURSOR_EDITOR?{name:"Cursor",version:""}:process.env.WINDSURF_EDITOR?{name:"Windsurf",version:""}:process.env.CONTINUE_GLOBAL_DIR?{name:"Continue",version:""}:process.env.CLINE_CONTEXT?{name:"Cline",version:""}:null}var GJ="https://www.google-analytics.com/mp/collect",KJ="G-87FMYC2KLT",XJ="ANCUVw5rTZ6ZRMOBd_RLMw",YJ=6e4,QJ=20,eG=25,tG=5e3,rG=".weppy-roblox-mcp",nG="device-id.json",tf=class{enabled;clientId;deviceKey;sessionId="";sessionStartTime=0;gaSessionId=0;lastEventTime=0;toolCallsTotal=0;toolCallsSuccess=0;toolCallsFailed=0;eventQueue=[];flushTimer=null;aiClient="unknown";pluginVersion="";tier="basic";userPropertiesDirty=!0;constructor(e){this.enabled=this.resolveEnabled(e.enabled),this.clientId=this.loadOrCreateDeviceId(),this.deviceKey=this.createDeviceKey(this.clientId),this.enabled||v.debug("Telemetry disabled")}getAnalyticsDirPath(){return j_.join(Ej.homedir(),rG)}getDeviceIdPath(){return j_.join(this.getAnalyticsDirPath(),nG)}loadOrCreateDeviceId(){let e=this.getDeviceIdPath();try{let n=Ya.readFileSync(e,"utf-8"),i=JSON.parse(n);if(typeof i.deviceId=="string"&&i.deviceId.length>0)return i.deviceId}catch{}let r=JJ();try{Ya.mkdirSync(this.getAnalyticsDirPath(),{recursive:!0}),Ya.writeFileSync(e,JSON.stringify({deviceId:r},null,2),"utf-8")}catch(n){v.warn("Failed to persist telemetry device ID",{error:n})}return r}createDeviceKey(e){return WJ("sha256").update(e).digest("hex").slice(0,32)}resolveEnabled(e){let r=process.env.ENABLE_TELEMETRY;return r!==void 0&&r!==""?r.toLowerCase()==="true"||r==="1":e??!0}initialize(e){if(!this.enabled)return;let r=Date.now();this.sessionId=e,this.sessionStartTime=r,this.gaSessionId=Math.floor(r/1e3),this.lastEventTime=r;let n=ef();n&&(this.aiClient=n.name),this.flushTimer=setInterval(()=>this.flush(),YJ),this.flushTimer.unref(),v.debug("Telemetry initialized",{sessionId:e})}getActivityEventParams(e){let r=this.gaSessionId||Math.floor((this.sessionStartTime||e)/1e3),n=this.lastEventTime||this.sessionStartTime||e,i=Math.max(1,e-n);return this.gaSessionId=r,this.lastEventTime=e,{session_id:r,engagement_time_msec:i,timestamp_micros:e*1e3}}createEventParams(e={}){let r=Date.now();return{device_key:this.deviceKey,mcp_version:Xe,plugin_version:this.pluginVersion||"unknown",platform:process.platform,user_tier:this.tier,ai_client:this.aiClient,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone||"unknown",locale:Intl.DateTimeFormat().resolvedOptions().locale||"unknown",...this.getActivityEventParams(r),...e}}trackSessionStart(){this.enabled&&this.enqueue({name:"mcp_session_start",params:this.createEventParams({server_session_id:this.sessionId})})}trackSessionEnd(){if(!this.enabled)return;let e=Math.round((Date.now()-this.sessionStartTime)/1e3);this.enqueue({name:"mcp_session_end",params:this.createEventParams({server_session_id:this.sessionId,duration_sec:e,tool_calls_total:this.toolCallsTotal,tool_calls_success:this.toolCallsSuccess,tool_calls_failed:this.toolCallsFailed})})}trackToolCall(e,r,n,i,a,o){if(!this.enabled)return;this.toolCallsTotal++,n?this.toolCallsSuccess++:this.toolCallsFailed++;let s=this.createEventParams({tool_name:e,required_tier:r,success:n?1:0,tool_result:n?"success":"error"});i&&(s.action=i),!n&&a&&(s.error_type=a),!n&&o&&(s.error_detail=o),this.enqueue({name:"mcp_tool_call",params:s})}trackToolError(e,r,n,i,a){if(!this.enabled)return;let o=this.createEventParams({tool_name:e,required_tier:r});n&&(o.action=n),i&&(o.error_type=i),a&&(o.error_detail=a),this.enqueue({name:"mcp_tool_error",params:o})}trackPluginConnected(){this.enabled&&this.enqueue({name:"mcp_plugin_connected",params:this.createEventParams({server_session_id:this.sessionId})})}setAiClient(e){this.aiClient=e,this.userPropertiesDirty=!0}setPluginVersion(e){this.pluginVersion=e,this.userPropertiesDirty=!0}setTier(e){this.tier=e,this.userPropertiesDirty=!0}isEnabled(){return this.enabled}enqueue(e){this.eventQueue.push(e),this.eventQueue.length>=QJ&&this.flush()}flush(){if(this.eventQueue.length===0)return;let e=this.eventQueue.splice(0,eG),r={client_id:this.clientId,events:e};this.userPropertiesDirty&&(r.user_properties={mcp_version:{value:Xe},plugin_version:{value:this.pluginVersion||"unknown"},device_key:{value:this.deviceKey},platform:{value:process.platform},user_tier:{value:this.tier},ai_client:{value:this.aiClient}},this.userPropertiesDirty=!1),this.sendToGA4(r),this.eventQueue.length>0&&this.flush()}sendToGA4(e){let r=`${GJ}?measurement_id=${KJ}&api_secret=${XJ}`,n=new AbortController,i=setTimeout(()=>n.abort(),tG);fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:n.signal}).then(()=>{clearTimeout(i),v.debug("Telemetry events sent",{count:e.events.length})}).catch(()=>{clearTimeout(i)})}async shutdown(){this.enabled&&(this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null),this.eventQueue.length>0&&this.flush(),await new Promise(e=>setTimeout(e,500)),v.debug("Telemetry shutdown complete"))}};import Rj from"path";import iG from"os";import{promises as D_}from"fs";var aG="license-state.json",O_=4;function oG(){return Rj.join(iG.homedir(),".weppy-roblox-mcp",aG)}var sG=new Set(["active","unknown","grace","revoked","invalid","unlicensed"]);function cG(){return{canUsePro:!1,status:"unlicensed",checkedAt:Math.floor(Date.now()/1e3)}}function rf(){return{version:O_,updatedAt:Date.now(),state:cG()}}function lG(){return Math.floor(Date.now()/1e3)}function Cj(t){let e=typeof t?.status=="string"&&sG.has(t.status)?t.status:"unlicensed",r={canUsePro:t?.canUsePro===!0,status:e,checkedAt:typeof t?.checkedAt=="number"?t.checkedAt:lG()};return typeof t?.maskedKey=="string"&&(r.maskedKey=t.maskedKey),typeof t?.billingState=="string"&&(r.billingState=t.billingState),typeof t?.reason=="string"&&(r.reason=t.reason),(t?.decisionSource==="provider_live"||t?.decisionSource==="provider_webhook"||t?.decisionSource==="license_server_cache")&&(r.decisionSource=t.decisionSource),t?.abuse&&typeof t.abuse=="object"&&t.abuse.blocked!==void 0&&(r.abuse={blocked:t.abuse.blocked===!0},typeof t.abuse.blockedUntil=="number"&&(r.abuse.blockedUntil=t.abuse.blockedUntil),typeof t.abuse.blockReason=="string"&&(r.abuse.blockReason=t.abuse.blockReason)),typeof t?.tokenStatus=="string"&&(r.tokenStatus=t.tokenStatus),typeof t?.statusDetail=="string"&&(r.statusDetail=t.statusDetail),typeof t?.lastLicenseServerSuccessAt=="number"&&(r.lastLicenseServerSuccessAt=t.lastLicenseServerSuccessAt),typeof t?.graceUntil=="number"&&(r.graceUntil=t.graceUntil),typeof t?.lastSuccessfulCheckAt=="number"&&(r.lastSuccessfulCheckAt=t.lastSuccessfulCheckAt),typeof t?.nextRecheckAt=="number"&&(r.nextRecheckAt=t.nextRecheckAt),typeof t?.fallbackUntil=="number"&&(r.fallbackUntil=t.fallbackUntil),typeof r.lastLicenseServerSuccessAt=="number"&&r.lastSuccessfulCheckAt===void 0&&(r.lastSuccessfulCheckAt=r.lastLicenseServerSuccessAt),typeof r.lastSuccessfulCheckAt=="number"&&r.lastLicenseServerSuccessAt===void 0&&(r.lastLicenseServerSuccessAt=r.lastSuccessfulCheckAt),typeof r.graceUntil=="number"&&r.fallbackUntil===void 0&&(r.fallbackUntil=r.graceUntil),typeof r.fallbackUntil=="number"&&r.graceUntil===void 0&&(r.graceUntil=r.fallbackUntil),r}var nf=class{cacheFilePath;cache=rf();constructor(e){this.cacheFilePath=e??oG()}getState(){return{...this.cache.state}}getProvider(){return this.cache.provider}getSessionToken(){return this.cache.sessionToken}async initialize(){try{let e=await D_.readFile(this.cacheFilePath,"utf8"),r=JSON.parse(e);if(!r||typeof r!="object"){this.cache=rf();return}let n=r,i=n.state&&typeof n.state=="object"?n.state:void 0,a=Cj(i),o={version:O_,updatedAt:typeof n.updatedAt=="number"?n.updatedAt:Date.now(),state:a};typeof n.provider=="string"&&n.provider.trim()!==""&&(o.provider=n.provider.trim().toLowerCase()),typeof n.sessionToken=="string"&&n.sessionToken.length>0&&(o.sessionToken=n.sessionToken),this.cache=o}catch{this.cache=rf()}}async persistState(e,r,n){let i={version:O_,updatedAt:Date.now(),state:Cj(e)},a=r??this.cache.provider;typeof a=="string"&&a.trim()!==""&&(i.provider=a.trim().toLowerCase());let o=n??e.sessionToken??this.cache.sessionToken;typeof o=="string"&&o.length>0&&(i.sessionToken=o),this.cache=i,await this.write()}getBackupData(){let e=this.cache.sessionToken,r=this.cache.provider;return!r||!e?null:{sessionToken:e,provider:r,savedAt:this.cache.updatedAt}}async clear(){this.cache=rf(),await this.write()}async write(){await D_.mkdir(Rj.dirname(this.cacheFilePath),{recursive:!0}),await D_.writeFile(this.cacheFilePath,JSON.stringify(this.cache,null,2),{mode:384})}};var zj=10*60*60,uG=14*60*60;function Nn(){return Math.floor(Date.now()/1e3)}function pG(t){return{...t,checkedAt:t.checkedAt||Nn()}}var af=class{client;cache;mcpInstanceId;fallbackMaxSeconds;random;refreshTimer=null;refreshInFlight=!1;constructor(e,r,n,i={}){this.client=e,this.cache=r,this.mcpInstanceId=n,this.fallbackMaxSeconds=Math.max(1,i.fallbackMaxHours??48)*60*60,this.random=i.random??Math.random}async initialize(){await this.cache.initialize(),this.scheduleFromCurrentState()}async shutdown(){this.clearScheduledRefresh()}isConfigured(){return this.client.isConfigured()}clearScheduledRefresh(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null)}computeNextRefreshAt(e=Nn()){let r=uG-zj,n=zj+Math.floor(this.random()*(r+1));return e+n}scheduleRefreshAt(e){this.clearScheduledRefresh();let r=Nn(),n=Math.max(0,(e-r)*1e3);this.refreshTimer=setTimeout(()=>{this.runScheduledRefresh()},n),this.refreshTimer.unref?.()}scheduleFromCurrentState(){if(!this.client.isConfigured()){this.clearScheduledRefresh();return}let e=!!this.cache.getSessionToken(),r=this.cache.getProvider();if(!e||!r){this.clearScheduledRefresh();return}let n=this.cache.getState(),i=typeof n.nextRecheckAt=="number"?n.nextRecheckAt:this.computeNextRefreshAt();this.scheduleRefreshAt(i)}getLastSuccessAt(e){return e.lastLicenseServerSuccessAt??e.lastSuccessfulCheckAt}getGraceUntil(e){return e.graceUntil??e.fallbackUntil}withCompatibilityFields(e){let r={...e},n=this.getLastSuccessAt(r);typeof n=="number"&&(r.lastLicenseServerSuccessAt=n,r.lastSuccessfulCheckAt=n);let i=this.getGraceUntil(r);return typeof i=="number"&&(r.graceUntil=i,r.fallbackUntil=i),r}async runScheduledRefresh(){if(!this.refreshInFlight){this.refreshInFlight=!0;try{await this.refresh({})}catch(e){v.warn("Scheduled license refresh failed",{error:e instanceof Error?e.message:"unknown_error"})}finally{this.refreshInFlight=!1,this.scheduleFromCurrentState()}}}withLocalTransitionDiagnostics(e,r,n){let{tokenStatus:i,tokenReason:a,statusDetail:o,...s}=e;return{...s,...r,statusDetail:n}}buildSuccessfulRemoteState(e,r,n){let i={...e,checkedAt:r,nextRecheckAt:n},a=this.getLastSuccessAt(e),o=this.getGraceUntil(e);return i.lastLicenseServerSuccessAt=typeof a=="number"?a:r,i.lastSuccessfulCheckAt=i.lastLicenseServerSuccessAt,i.graceUntil=typeof o=="number"?o:r+this.fallbackMaxSeconds,i.fallbackUntil=i.graceUntil,this.withCompatibilityFields(i)}evaluateEffectiveState(e){let r=pG(e),n=Nn();if(r.status==="revoked"||r.status==="invalid"||r.status==="unlicensed")return{...r,canUsePro:!1};if(r.status==="active")return r.canUsePro?r:this.withLocalTransitionDiagnostics(r,{canUsePro:!1,status:"invalid",reason:r.reason??"invalid_active_state"},"invalid_local_evaluation");if(r.status==="grace"){let i=this.getGraceUntil(r);return typeof i=="number"&&n<=i?{...r,canUsePro:!0}:this.withLocalTransitionDiagnostics(r,{canUsePro:!1,status:"unlicensed",reason:"network_unavailable_fallback_window_exceeded"},"unlicensed_local_evaluation")}if(r.status==="unknown"){let i=this.getLastSuccessAt(r);return typeof i=="number"&&n<=i+this.fallbackMaxSeconds&&r.canUsePro?this.withLocalTransitionDiagnostics(r,{status:"grace",canUsePro:!0,graceUntil:i+this.fallbackMaxSeconds,fallbackUntil:i+this.fallbackMaxSeconds,reason:"network_unavailable_fallback_to_last_success"},"grace_local_evaluation"):this.withLocalTransitionDiagnostics(r,{canUsePro:!1,status:"unlicensed",reason:r.reason??"license_check_unavailable"},"unlicensed_local_evaluation")}return this.withLocalTransitionDiagnostics(r,{canUsePro:!1,status:"unlicensed",reason:r.reason??"license_unknown_state"},"unlicensed_local_evaluation")}buildPersistedState(e){let r=e.checkedAt||Nn(),n=this.computeNextRefreshAt(r),i={...e,checkedAt:r,nextRecheckAt:n};return e.canUsePro&&(e.status==="active"||e.status==="grace")?this.buildSuccessfulRemoteState(e,r,n):this.withCompatibilityFields(i)}buildFallbackState(e){let r=Nn(),n=this.computeNextRefreshAt(r);if(e.canUsePro&&typeof this.getLastSuccessAt(e)=="number"&&r<=this.getLastSuccessAt(e)+this.fallbackMaxSeconds){let i=this.getLastSuccessAt(e);return this.withLocalTransitionDiagnostics(e,{canUsePro:!0,status:"grace",checkedAt:r,lastLicenseServerSuccessAt:i,lastSuccessfulCheckAt:i,graceUntil:i+this.fallbackMaxSeconds,fallbackUntil:i+this.fallbackMaxSeconds,nextRecheckAt:n,reason:"network_unavailable_fallback_to_last_success"},"grace_network_fallback")}return this.withCompatibilityFields(this.withLocalTransitionDiagnostics(e,{canUsePro:!1,status:"unlicensed",checkedAt:r,nextRecheckAt:n,reason:"network_unavailable_fallback_window_exceeded"},"unlicensed_fallback_expired"))}getStatus(){let e=this.cache.getState(),r=this.evaluateEffectiveState(e),n=!!this.cache.getSessionToken();return this.client.isConfigured()&&n&&this.cache.getProvider()&&typeof e.nextRecheckAt=="number"&&Nn()>=e.nextRecheckAt&&!this.refreshInFlight&&this.runScheduledRefresh(),this.withCompatibilityFields(r)}async activate(e){if(!this.client.isConfigured())throw new Error("License API is not configured. Set LICENSE_API_BASE_URL, LICENSE_PROJECT_ID, LICENSE_PROVIDER.");let r={licenseKey:e.licenseKey,provider:e.provider.trim().toLowerCase(),mcpInstanceId:this.mcpInstanceId};if(!r.provider)throw new Error("provider is required");e.pluginClientId&&(r.pluginClientId=e.pluginClientId),e.deviceId&&(r.deviceId=e.deviceId);let n=await this.client.activate(r),i=this.buildPersistedState(n);await this.cache.persistState(i,r.provider,n.sessionToken??""),this.scheduleFromCurrentState();let a=this.withCompatibilityFields(this.evaluateEffectiveState(i));return n.sessionToken&&(a.sessionToken=n.sessionToken),a}async refresh(e){if(!this.client.isConfigured())return this.getStatus();let r=(e.provider??this.cache.getProvider())?.trim().toLowerCase();if(!r){let a={canUsePro:!1,status:"unlicensed",checkedAt:Nn(),reason:"license_provider_missing"};return await this.cache.persistState(a),this.clearScheduledRefresh(),a}let n=e.sessionToken??this.cache.getSessionToken();if(n)try{let a={sessionToken:n,provider:r,mcpInstanceId:this.mcpInstanceId};e.pluginClientId&&(a.pluginClientId=e.pluginClientId),e.deviceId&&(a.deviceId=e.deviceId);let o=await this.client.refresh(a),s=this.buildPersistedState(o);return await this.cache.persistState(s,r,o.sessionToken),this.scheduleFromCurrentState(),this.withCompatibilityFields(this.evaluateEffectiveState(s))}catch(a){v.warn("License refresh failed, falling back",{error:a instanceof Error?a.message:"unknown_error"});let o=this.buildFallbackState(this.cache.getState());return await this.cache.persistState(o,r),this.scheduleFromCurrentState(),this.withCompatibilityFields(this.evaluateEffectiveState(o))}let i=this.evaluateEffectiveState(this.cache.getState());return await this.cache.persistState(i,r),this.scheduleFromCurrentState(),this.withCompatibilityFields(i)}async evaluateProAccess(){let e=this.getStatus();if(e.canUsePro)return{allowed:!0,state:e};if(e.reason==="device_temporarily_blocked")return{allowed:!1,state:e,reason:"This device is temporarily blocked due to multi-device activity."};let r="License required";return e.status==="revoked"?r="License revoked. Pro tools are disabled.":e.status==="invalid"?r="License invalid. Please re-enter a valid license key.":(e.status==="unknown"||e.status==="grace")&&(r=e.reason??"License check is temporarily unavailable."),{allowed:!1,state:e,reason:r}}getBackupData(){return this.cache.getBackupData()}async persistSyncedToken(e,r,n){let i=n?this.buildPersistedState(n):this.cache.getState();await this.cache.persistState(i,r,e),this.scheduleFromCurrentState()}async clearCredentials(){await this.cache.clear(),this.clearScheduledRefresh()}};import{mkdir as dG,rename as fG,writeFile as mG}from"fs/promises";import Qa from"path";import{randomUUID as hG}from"crypto";var gG="test-report.md",vG="test-log.txt";function Cc(t){return String(t).padStart(2,"0")}function yG(t){return[t.getFullYear(),Cc(t.getMonth()+1),Cc(t.getDate()),"-",Cc(t.getHours()),Cc(t.getMinutes()),Cc(t.getSeconds())].join("")}function N_(t){return typeof t!="number"?"-":new Date(t).toISOString()}async function jj(t,e){let r=`${t}.${hG()}.tmp`;await mG(r,e,"utf8"),await fG(r,t)}function xG(t){let e=t.timedOut?"timed_out":t.passed?"passed":"failed",r=t.signals.find(a=>a.kind==="START"),n=t.signals.find(a=>a.kind==="FINISHED"),i=["# Test Report","",`- Status: ${e}`,`- Test Name: ${t.testName}`,`- Mode: ${t.mode}`,`- Place ID: ${t.placeId}`,`- Place Name: ${t.placeName||"(unknown)"}`,`- Duration (ms): ${t.durationMs}`,`- Total Logs: ${t.logs.length}`,`- Signal Count: ${t.signals.length}`,"","## Signals","",`- START: ${r?N_(r.timestampMs):"missing"}`,`- FINISHED: ${n?N_(n.timestampMs):"missing"}`];return t.errorMessage&&i.push("","## Error","","```text",t.errorMessage,"```"),`${i.join(`
|
|
130
130
|
`)}
|
|
131
131
|
`}function bG(t){return`${t.map(e=>{let r=N_(e.timestampMs),n=typeof e.seq=="number"?`seq=${e.seq}`:"seq=-",i=e.type||"info";return`[${r}] [${n}] [${i}] ${e.message}`}).join(`
|
|
132
132
|
`)}
|