@weppy/roblox-mcp 2.6.1 → 2.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.github/ISSUE_TEMPLATE/bug_report.yml +1 -1
  3. package/CHANGELOG.md +12 -0
  4. package/package.json +1 -1
  5. package/plugins/weppy-roblox-mcp/.claude-plugin/plugin.json +1 -1
  6. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ChangelogDetailPage-kwE4pHZd.js → ChangelogDetailPage-DYQthxhC.js} +1 -1
  7. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ChangelogPage-BFfSWE_s.js → ChangelogPage-WFqQ5h8e.js} +1 -1
  8. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ConfirmModal-CeEegyOx.js → ConfirmModal-CM3ElkBC.js} +1 -1
  9. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ConnectionPage-XuHLuNwR.js → ConnectionPage-DdKcJZt6.js} +1 -1
  10. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{GameChangeDetail-BFTVWFJO.js → GameChangeDetail-DGphCFbI.js} +1 -1
  11. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{InfoLabel-MCIgnb3Z.js → InfoLabel-CMSu30c4.js} +1 -1
  12. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{OverviewPage-BrWw_dSf.js → OverviewPage-DM74adCU.js} +1 -1
  13. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{PlaytestPage-CxeJXqJZ.js → PlaytestPage-Dq3kV_rX.js} +1 -1
  14. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{SettingsPage-Qd2a2pgi.js → SettingsPage-QQd8aPdd.js} +1 -1
  15. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{StatusBadge-Dk0M7zQF.js → StatusBadge-BhRLY0yK.js} +1 -1
  16. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{SyncPage-Wu2tD8Lb.js → SyncPage-Col3nhBp.js} +1 -1
  17. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{TierComparison-DsesL0R1.js → TierComparison-CYfIb0tr.js} +1 -1
  18. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ToolsPage-C7ld3BhY.js → ToolsPage-Ba_6GnUZ.js} +1 -1
  19. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{TooltipText-CjH5N6RH.js → TooltipText-Bnvm1FcC.js} +1 -1
  20. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{WhatsNewPage-BwWOghgp.js → WhatsNewPage-M1EeHSH-.js} +1 -1
  21. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-B-nqZCE3.js +380 -0
  22. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-COXUWuq2.css +1 -0
  23. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{useLiveUptime-DExWULj9.js → useLiveUptime-Dc2iJiFi.js} +1 -1
  24. package/plugins/weppy-roblox-mcp/dashboard/dist/index.html +2 -2
  25. package/plugins/weppy-roblox-mcp/dist/index.js +1 -1
  26. package/plugins/weppy-roblox-mcp/roblox-plugin/WeppyRobloxMCP.rbxm +0 -0
  27. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-FDX6EnZH.js +0 -355
  28. package/plugins/weppy-roblox-mcp/dashboard/dist/assets/index-XEVy0XAx.css +0 -1
@@ -0,0 +1 @@
1
+ @import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=DM+Sans:wght@400;500;700&family=JetBrains+Mono:wght@400;500;700&display=swap";._container_6jg7e_2{position:fixed;bottom:16px;right:16px;z-index:2000;display:flex;flex-direction:column-reverse;gap:8px;pointer-events:none}._toast_6jg7e_14{pointer-events:auto;padding:10px 16px;border-radius:var(--radius-sm);font-family:var(--font-label);font-size:13px;color:var(--text-primary);box-shadow:0 4px 12px #0000004d;animation:_slideIn_6jg7e_1 .25s ease-out;max-width:320px}@keyframes _slideIn_6jg7e_1{0%{opacity:0;transform:translate(40px)}to{opacity:1;transform:translate(0)}}._success_6jg7e_39{background:color-mix(in srgb,var(--success) 22%,transparent);border-left:3px solid var(--success)}._info_6jg7e_44{background:color-mix(in srgb,var(--accent) 22%,transparent);border-left:3px solid var(--accent)}._warning_6jg7e_49{background:color-mix(in srgb,var(--warning) 22%,transparent);border-left:3px solid var(--warning)}._error_6jg7e_54{background:var(--error-bg);border-left:3px solid var(--error-border)}._wrapper_1uzud_2{position:relative;display:inline-flex}._tooltip_1uzud_8{position:absolute;bottom:calc(100% + 6px);left:50%;transform:translate(-50%);background:var(--bg-card);border:1px solid var(--border);color:var(--text-primary);font-size:12px;padding:4px 8px;border-radius:4px;width:max-content;max-width:min(320px,calc(100vw - 32px));white-space:normal;overflow-wrap:anywhere;text-align:left;pointer-events:none;opacity:0;transition:opacity var(--transition);z-index:1000}._tooltipLeft_1uzud_30{left:0;transform:none}._tooltipRight_1uzud_35{left:auto;right:0;transform:none}._wrapper_1uzud_2:hover ._tooltip_1uzud_8{opacity:1}._widget_c7n2n_1{margin:8px 12px 4px;padding:12px;border:1px solid var(--pro-border);border-radius:var(--radius);background:var(--pro-bg-soft);display:flex;flex-direction:column;gap:10px}._header_c7n2n_12{display:flex;align-items:center;gap:8px}._sparkle_c7n2n_18{color:var(--pro-text);flex-shrink:0}._title_c7n2n_23{font-family:var(--font-label);font-size:12px;font-weight:600;color:var(--pro-text);line-height:1.3}._cta_c7n2n_31{display:inline-flex;align-items:center;justify-content:center;padding:8px 12px;border-radius:var(--radius-sm);background:var(--pro-badge);color:#1a1408;font-family:var(--font-label);font-size:12px;font-weight:700;text-decoration:none;transition:filter var(--transition),transform var(--transition)}._cta_c7n2n_31:hover{filter:brightness(1.08);transform:translateY(-1px);text-decoration:none}._coupon_c7n2n_52{display:flex;flex-direction:column;gap:2px}._couponCode_c7n2n_58{font-family:var(--font-code);font-size:11px;color:var(--pro-text);letter-spacing:.04em}._couponMeta_c7n2n_65{font-family:var(--font-label);font-size:10px;color:var(--text-muted)}._collapsed_c7n2n_71{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;margin:8px auto;border-radius:var(--radius-sm);background:var(--pro-bg-soft);color:var(--pro-text);text-decoration:none;transition:filter var(--transition)}._collapsed_c7n2n_71:hover{filter:brightness(1.15);text-decoration:none}._sidebar_bqqir_2{width:var(--sidebar-width);min-width:var(--sidebar-width);height:100%;display:flex;flex-direction:column;background:var(--bg-sidebar);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border-right:1px solid var(--border);padding:0 0 16px;overflow:visible;transition:width var(--transition),min-width var(--transition)}._brand_bqqir_17{display:flex;align-items:center;gap:10px;padding:18px 16px 14px;border-bottom:1px solid var(--border);margin-bottom:8px}._brandIcon_bqqir_26{color:var(--accent);flex-shrink:0}._brandText_bqqir_31{font-family:var(--font-label);font-size:14px;font-weight:700;color:var(--text-primary);letter-spacing:.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}._brandTextPro_bqqir_42{color:var(--pro-text);text-shadow:0 0 18px rgba(212,167,44,.18)}._nav_bqqir_48{display:flex;flex-direction:column;gap:2px}._spacer_bqqir_55{flex:1;min-height:16px}._navLink_bqqir_61{display:flex;align-items:center;gap:12px;padding:10px 16px;color:var(--text-secondary);text-decoration:none;font-size:14px;font-weight:500;border-left:4px solid transparent;transition:color var(--transition),background var(--transition),border-color var(--transition)}._navLink_bqqir_61:hover{color:var(--text-primary);background:var(--accent-dim);text-decoration:none}._navLink_bqqir_61._active_bqqir_82{color:var(--accent);border-left-color:var(--accent);background:var(--accent-dim)}._navLink_bqqir_61._disabled_bqqir_89{color:var(--text-muted);pointer-events:none;cursor:default}._navLink_bqqir_61._disabled_bqqir_89:hover{background:transparent}._icon_bqqir_100{width:20px;height:20px;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:currentColor}._label_bqqir_111{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}._secondaryDivider_bqqir_118{height:1px;margin:4px 16px;background:var(--border)}._secondaryNav_bqqir_125{display:flex;flex-direction:column;gap:2px}._helpDivider_bqqir_132{height:1px;margin:8px 16px 4px;background:var(--border)}._helpGroupTitle_bqqir_139{padding:4px 16px 6px;font-family:var(--font-label);font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--text-muted)}._helpNav_bqqir_150{display:flex;flex-direction:column;gap:2px}._externalIcon_bqqir_157{margin-left:auto;display:inline-flex;align-items:center;color:var(--text-muted)}@media(max-width:768px){._sidebar_bqqir_2{width:var(--sidebar-collapsed);min-width:var(--sidebar-collapsed)}._brand_bqqir_17{justify-content:center;padding:14px 0 10px}._brandText_bqqir_31{display:none}._navLink_bqqir_61{justify-content:center;padding:10px 0;gap:0}._label_bqqir_111,._helpGroupTitle_bqqir_139,._externalIcon_bqqir_157{display:none}}._header_1ijws_2{display:flex;flex-direction:column;gap:8px;padding:12px 24px;border-bottom:1px solid var(--border);background:var(--bg-card);-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px)}._row1_1ijws_13{display:flex;align-items:center;justify-content:space-between;gap:16px;min-width:0}._projectIdentity_1ijws_21{display:flex;align-items:center;flex-wrap:wrap;gap:10px;min-width:0;flex:1}._folderIcon_1ijws_30{color:var(--accent);flex-shrink:0}._projectName_1ijws_35{margin:0;font-size:16px;font-weight:700;color:var(--text-primary);line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:clamp(120px,30vw,360px)}._placeChips_1ijws_47{display:flex;gap:6px;flex-wrap:wrap;min-width:0}._metaActions_1ijws_54{display:flex;align-items:center;gap:8px;flex-shrink:0}._placeChip_1ijws_47{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border:1px solid var(--border);border-radius:var(--radius-pill);background:var(--bg-overlay);color:var(--text-secondary);font-size:12px;font-weight:600;cursor:default}._placeChipStatic_1ijws_76,._placeChipMuted_1ijws_77{cursor:default}._placeChipActive_1ijws_81{border-color:var(--border-highlight);color:var(--text-primary);background:var(--accent-dim);box-shadow:0 0 0 1px var(--accent-glow) inset}._placeChipInactive_1ijws_88{border-color:var(--border);color:var(--text-muted);opacity:.85}._placeChipMuted_1ijws_77{border-color:var(--border);color:var(--text-muted)}._placeChipId_1ijws_99{font-family:var(--font-code);font-weight:500;font-size:11px;color:var(--text-muted);padding-left:6px;margin-left:2px;border-left:1px solid var(--border)}._placeChipActive_1ijws_81 ._placeChipId_1ijws_99{color:var(--text-secondary);border-left-color:var(--border-highlight)}._placeDot_1ijws_114{width:7px;height:7px;border-radius:var(--radius-pill);background:var(--success);box-shadow:0 0 6px var(--success)}._placeChipName_1ijws_122{white-space:nowrap;max-width:140px;overflow:hidden;text-overflow:ellipsis}._row2_1ijws_130{display:flex;align-items:center;gap:16px;min-width:0}._pathGroup_1ijws_137{display:flex;align-items:center;gap:10px;min-width:0;flex:1}._projectPath_1ijws_145{font-family:var(--font-code);font-size:12px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}._changeButton_1ijws_155{display:inline-flex;align-items:center;justify-content:center;height:32px;padding:0 12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-overlay);color:var(--text-primary);font:inherit;font-size:12px;font-weight:600;line-height:1;white-space:nowrap;cursor:pointer;transition:border-color var(--transition),background var(--transition),color var(--transition)}._changeButton_1ijws_155:hover{border-color:var(--border-highlight);background:var(--accent-dim);color:var(--accent)}._changeButton_1ijws_155:disabled{cursor:not-allowed;opacity:.52}._iconButton_1ijws_188{position:relative;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-overlay);color:var(--text-primary);text-decoration:none;transition:border-color var(--transition),background var(--transition),color var(--transition)}._iconButton_1ijws_188:hover{border-color:var(--border-highlight);background:var(--accent-dim);color:var(--accent);text-decoration:none}._notificationDot_1ijws_213{position:absolute;top:6px;right:6px;width:6px;height:6px;border-radius:var(--radius-pill);background:var(--error);box-shadow:0 0 0 2px var(--bg-overlay)}@media(max-width:768px){._header_1ijws_2{padding:10px 16px}._row1_1ijws_13,._projectIdentity_1ijws_21{gap:8px}._row2_1ijws_130{flex-direction:column;align-items:flex-start;gap:8px}._pathGroup_1ijws_137{width:100%}}._bellWrapper_tae21_2{position:relative;display:inline-flex}._unreadDot_tae21_8{position:absolute;top:4px;right:4px;width:8px;height:8px;border-radius:50%;background:var(--error);border:2px solid var(--bg-card);pointer-events:none}._backdrop_1ez3d_1{position:fixed;top:0;right:0;bottom:0;left:0;background:#0c1018b8;display:grid;place-items:center;z-index:1000;padding:24px}._modal_1ez3d_12{width:min(420px,100%);background:var(--bg-overlay);border:1px solid var(--border);border-radius:18px;box-shadow:0 24px 48px #0307126b;padding:24px}._title_1ez3d_23{margin:0 0 12px;color:var(--text-primary);font-size:20px}._message_1ez3d_30{margin:0;color:var(--text-secondary);line-height:1.6}._actions_1ez3d_37{display:flex;justify-content:flex-end;gap:12px;margin-top:24px}._cancelButton_1ez3d_44,._confirmButton_1ez3d_45{border:0;border-radius:var(--radius);padding:10px 14px;font:inherit;cursor:pointer}._cancelButton_1ez3d_44{background:var(--border);border:1px solid transparent;color:var(--text-primary)}._confirmButton_1ez3d_45{background:var(--error-bg);border:1px solid var(--error-border);color:var(--error-text)}._cancelButton_1ez3d_44:disabled,._confirmButton_1ez3d_45:disabled{cursor:not-allowed;opacity:.6}._layout_ya8ln_2{display:flex;height:100%;overflow:hidden}._mainShell_ya8ln_8{flex:1;min-width:0;display:flex;flex-direction:column;overflow:visible}._content_ya8ln_17{flex:1 1 auto;min-height:0;overflow-y:auto;padding:24px}._loading_ya8ln_25{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:14px}:root{--bg-primary: #0b0e1a;--bg-secondary: #0f1525;--bg-card: #111827;--bg-sidebar: rgba(11, 14, 26, .95);--text-primary: #e2e8f0;--text-secondary: #94a3b8;--text-muted: #475569;--accent: #3b82f6;--accent-dim: rgba(59, 130, 246, .12);--accent-glow: rgba(59, 130, 246, .25);--border: #1e293b;--border-highlight: #2563eb;--error: #ef4444;--warning: #f59e0b;--success: #22c55e;--pro-badge: #d4a72c;--pro-text: #f3d46b;--pro-bg: #3a2d12;--pro-bg-soft: rgba(212, 167, 44, .12);--pro-border: #8f6f1f;--font-code: "JetBrains Mono", "Fira Code", monospace;--font-label: "Inter", "DM Sans", -apple-system, sans-serif;--sidebar-width: 220px;--sidebar-collapsed: 56px;--radius: 10px;--radius-sm: 6px;--transition: .2s ease;--error-bg: rgba(239, 68, 68, .12);--error-border: rgba(239, 68, 68, .5);--error-text: #f4c1c1;--bg-overlay: rgba(15, 21, 37, .88);--radius-pill: 999px}@media(prefers-color-scheme:light){:root{--bg-primary: #f1f5f9;--bg-secondary: #ffffff;--bg-card: #ffffff;--bg-sidebar: rgba(255, 255, 255, .95);--text-primary: #0f172a;--text-secondary: #475569;--text-muted: #94a3b8;--accent: #2563eb;--accent-dim: rgba(37, 99, 235, .08);--accent-glow: rgba(37, 99, 235, .15);--error: #dc2626;--border: #e2e8f0;--border-highlight: #3b82f6;--pro-badge: #b07c00;--pro-text: #8a5f00;--pro-bg: #fff4d6;--pro-bg-soft: rgba(176, 124, 0, .12);--pro-border: #d9b14d;--error-bg: rgba(220, 38, 38, .08);--error-border: rgba(220, 38, 38, .3);--error-text: #b91c1c;--bg-overlay: rgba(255, 255, 255, .95)}}*,*:before,*:after{margin:0;padding:0;box-sizing:border-box}html,body{height:100%;font-family:var(--font-label);background:var(--bg-primary);color:var(--text-primary);line-height:1.5;-webkit-font-smoothing:antialiased}#root{height:100%}code,pre,.mono{font-family:var(--font-code)}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}button{font-family:inherit;cursor:pointer}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
@@ -1 +1 @@
1
- import{r as a}from"./index-FDX6EnZH.js";function c(t){const r=Math.max(0,Math.floor(t/1e3)),n=Math.floor(r/3600),o=Math.floor(r%3600/60),e=Math.floor(r%60);return`${String(n).padStart(2,"0")}:${String(o).padStart(2,"0")}:${String(e).padStart(2,"0")}`}const s=1e3;function i(t){const[r,n]=a.useState(t??null);return a.useEffect(()=>{if(t==null){n(null);return}const o=Date.now();n(t);const e=window.setInterval(()=>{n(t+(Date.now()-o))},s);return()=>{window.clearInterval(e)}},[t]),r}export{c as f,i as u};
1
+ import{r as a}from"./index-B-nqZCE3.js";function c(t){const r=Math.max(0,Math.floor(t/1e3)),n=Math.floor(r/3600),o=Math.floor(r%3600/60),e=Math.floor(r%60);return`${String(n).padStart(2,"0")}:${String(o).padStart(2,"0")}:${String(e).padStart(2,"0")}`}const s=1e3;function i(t){const[r,n]=a.useState(t??null);return a.useEffect(()=>{if(t==null){n(null);return}const o=Date.now();n(t);const e=window.setInterval(()=>{n(t+(Date.now()-o))},s);return()=>{window.clearInterval(e)}},[t]),r}export{c as f,i as u};
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" href="/dashboard/weppy-icon.png" />
7
7
  <title>WEPPY Dashboard</title>
8
- <script type="module" crossorigin src="/dashboard/assets/index-FDX6EnZH.js"></script>
9
- <link rel="stylesheet" crossorigin href="/dashboard/assets/index-XEVy0XAx.css">
8
+ <script type="module" crossorigin src="/dashboard/assets/index-B-nqZCE3.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/dashboard/assets/index-COXUWuq2.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -136,7 +136,7 @@ data: ${JSON.stringify(n)}
136
136
  `)}catch(i){g.warn("Failed to flush change log",{error:i instanceof Error?i.message:String(i),entries:e.length})}}startChangeLogFlusher(){this.flushTimer||(this.flushTimer=setInterval(()=>{this.flushChangeLog(),this.flushHistory()},_oe),this.flushTimer&&typeof this.flushTimer=="object"&&"unref"in this.flushTimer&&this.flushTimer.unref())}async stopChangeLogFlusher(){this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null),await this.flushChangeLog(),await this.flushHistory()}async updateSyncIndex(e,n){this.index.updateBothHashes(e,n)}relativePath(e){return ct.relative(this.config.getSyncRoot(),e)}extractServiceName(e){return Et(e)[0]??""}findTreeNode(e,n){let i=Et(n);if(i.length<=1)return null;let r=e.children,a;for(let o=1;o<i.length;o++){if(!r||(a=r.find(s=>s.name===i[o]),!a))return null;r=a.children}return a??null}detachTreeNode(e,n){let i=this.getParentPath(n),r=this.getLeafName(n);if(!i||!r)return null;let a=this.findTreeNode(e,i),o=a?.children??e.children,s=o.findIndex(l=>l.name===r);if(s<0)return null;let[c]=o.splice(s,1);return a?a.childCount=o.length:e.childCount=o.length,c??null}insertTreeNode(e,n,i){let r=this.findTreeNode(e,n);if(r){r.children||(r.children=[]);let o=r.children.findIndex(s=>s.name===i.name);o>=0?r.children[o]=i:r.children.push(i),r.childCount=r.children.length;return}let a=e.children.findIndex(o=>o.name===i.name);a>=0?e.children[a]=i:e.children.push(i),e.childCount=e.children.length}getLeafName(e){let n=Et(e);return n.length===0?null:n[n.length-1]??null}getParentPath(e){let n=Et(e);return n.length<=1?null:`game.${n.slice(0,-1).join(".")}`}async copyDirectory(e,n){await this.ensureDir(n);let i=await Ke.readdir(e,{withFileTypes:!0});for(let r of i){let a=ct.join(e,r.name),o=ct.join(n,r.name);r.isDirectory()?await this.copyDirectory(a,o):await Ke.copyFile(a,o)}}async tryRename(e,n){try{return this.onWriteCallback?.(e),this.onWriteCallback?.(n),await Ke.rename(e,n),!0}catch{return!1}}};function gI(t,e){if(t===e)return!0;if(t===null||e===null||typeof t!=typeof e||typeof t!="object")return!1;if(Array.isArray(t))return!Array.isArray(e)||t.length!==e.length?!1:t.every((o,s)=>gI(o,e[s]));let n=t,i=e,r=Object.keys(n),a=Object.keys(i);return r.length!==a.length?!1:r.every(o=>Object.hasOwn(i,o)&&gI(n[o],i[o]))}function woe(t,e){let n={},i=new Set([...Object.keys(t??{}),...Object.keys(e)]);for(let r of i){let a=t?.[r]??null,o=e[r]??null;gI(a,o)||(n[r]={before:a,after:o})}return n}import ze from"path";import{promises as Qa}from"fs";ce();var qy=class{index;placeRoot;constructor(e,n,i){this.index=n,this.placeRoot=i??e.getSyncRoot()}async buildReverseChanges(){let{modified:e,deleted:n}=await this.index.scanForFileModifications(),i=[];for(let a of e){if(this.isForwardOnlyPath(a.fsPath)){this.logForwardOnlySkip(a.fsPath,"buildReverseChanges:modified");continue}try{let o;a.fileType==="script"?o=await this.readModifiedScript(a.fsPath,a.instancePath):a.fileType==="value"?o=await this.readModifiedValue(a.fsPath,a.instancePath):o=await this.readModifiedProps(a.fsPath,a.instancePath),i.push(o)}catch(o){g.warn("Failed to read modified file for reverse sync",{path:a.fsPath,error:o instanceof Error?o.message:String(o)})}}let r=new Set;for(let a of n){if(this.isForwardOnlyPath(a.fsPath)){this.logForwardOnlySkip(a.fsPath,"buildReverseChanges:deleted");continue}if(r.has(a.instancePath))continue;r.add(a.instancePath);let o=ze.relative(this.placeRoot,a.fsPath);i.push({type:"instanceRemoved",instancePath:a.instancePath,fsPath:o,fileType:a.fileType,content:null})}return i}async buildChangesFromPending(e){let n=[],i=new Set,r=new Set,a=new Set;for(let o of e)if(o.type==="add"||o.type==="change"){let s=ze.dirname(o.fsPath),c=this.getFileType(o.fsPath);(c==="props"||c==="script"||c==="value")&&a.add(s)}for(let o of e)try{if(this.isForwardOnlyPath(o.fsPath)){this.logForwardOnlySkip(o.fsPath,`buildChangesFromPending:${o.type}`);continue}if(o.type==="addDir"||o.type==="unlinkDir"){let d=this.resolveInstancePathFromDirectory(o.fsPath);if(!d)continue;let f=this.getSiblingIndexForDirectory(o.fsPath),m=this.buildPendingDedupeKey(d,f);if(o.type==="unlinkDir"){if(i.has(m)||(i.add(m),this.isServiceInstance(d)))continue;let _=ze.relative(this.placeRoot,o.fsPath),S={type:"instanceRemoved",instancePath:d,fsPath:_,fileType:"directory",content:null};f&&(S.siblingIndex=f),n.push(S);continue}if(r.has(m)||this.isServiceInstance(d)||a.has(o.fsPath)||!await this.isContainerDirectory(o.fsPath))continue;let y=ze.relative(this.placeRoot,o.fsPath),v={type:"containerCreated",instancePath:d,fsPath:y,fileType:"directory",content:null,className:"Folder",parentPath:this.extractParentPath(d)};f&&(v.siblingIndex=f),r.add(m),n.push(v);continue}let s=this.resolveInstancePathFromFile(o.fsPath);if(!s)continue;let c=this.getSiblingIndexForFile(o.fsPath);if(o.type==="unlink"){let d=this.buildPendingDedupeKey(s,c);if(i.has(d)||(i.add(d),this.isServiceInstance(s)))continue;let f=ze.relative(this.placeRoot,o.fsPath),m=this.getFileType(o.fsPath);if(m!=="script"&&m!=="props"&&m!=="value")continue;let h={type:"instanceRemoved",instancePath:s,fsPath:f,fileType:m,content:null};c&&(h.siblingIndex=c),n.push(h);continue}let l=this.getFileType(o.fsPath),u=!this.index.hasEntry(o.fsPath)&&!this.index.hasInstancePath(s),p;if(u&&o.type==="add"){if(this.isServiceInstance(s))continue;if(l==="script")p=await this.readNewScript(o.fsPath,s);else if(l==="value")p=await this.readNewValue(o.fsPath,s);else if(l==="props")p=await this.readNewInstance(o.fsPath,s);else continue}else if(l==="script")p=await this.readModifiedScript(o.fsPath,s);else if(l==="value")p=await this.readModifiedValue(o.fsPath,s);else if(l==="props")p=await this.readModifiedProps(o.fsPath,s);else continue;c&&(p.siblingIndex=c),n.push(p)}catch(s){g.warn("Failed to build change from pending entry",{path:o.fsPath,type:o.type,error:s instanceof Error?s.message:String(s)})}return n}resolveInstancePathFromFile(e){return this.index.resolveInstancePathFromFsPath(e)}getSiblingIndexForFile(e){let n=this.index.getExplorerRoot();if(ze.basename(e)==="_tree.json")return;let r=ze.relative(n,e);if(r.startsWith(".."))return;let a=r.split(ze.sep).filter(p=>p.length>0);if(a.length<2)return;let o=a[a.length-2],s=ze.dirname(e),c=ze.dirname(s),l=this.index.getOriginalInstance(c,o);return l?l.siblingIndex:Wr(o).siblingIndex}isForwardOnlyPath(e){return this.index.isPathUnderClass(e,rc)}logForwardOnlySkip(e,n){g.info("Skipped reverse change for forward-only path",{source:n,path:ze.relative(this.placeRoot,e),className:this.index.getClassNameForFsPath(e)??"unknown"})}getFileType(e){return this.index.getFileTypeFromPath(e)}resolveInstancePathFromDirectory(e){let n=this.index.getExplorerRoot(),i=ze.relative(n,e);if(i.startsWith("..")||i===""||ze.isAbsolute(i))return null;let r=i.split(ze.sep).filter(s=>s.length>0);if(r.length===0)return null;let a=["game"],o=n;for(let s of r){o=ze.join(o,s);let c=ze.dirname(o);a.push(this.index.getOriginalNameForDir(c,s))}return nt(a)}getSiblingIndexForDirectory(e){let n=ze.basename(e),i=ze.dirname(e),r=this.index.getOriginalInstance(i,n);return r?r.siblingIndex:Wr(n).siblingIndex}async isContainerDirectory(e){try{let n=await Qa.readdir(e,{withFileTypes:!0});for(let i of n)if(i.isFile()&&Cy.some(r=>i.name.endsWith(r)))return!1;return!0}catch(n){if(n.code==="ENOENT")return!1;throw n}}buildPendingDedupeKey(e,n){return n?`${e}#${n}`:e}async readModifiedScript(e,n){let i=await Qa.readFile(e,"utf-8"),r=ze.relative(this.placeRoot,e);return{type:"scriptSource",instancePath:n,fsPath:r,fileType:"script",content:i}}async readModifiedProps(e,n){let i=await Qa.readFile(e,"utf-8"),r=JSON.parse(i),a=ze.relative(this.placeRoot,e);return{type:"properties",instancePath:n,fsPath:a,fileType:"props",content:r}}async readModifiedValue(e,n){let i=await Qa.readFile(e,"utf-8"),r=JSON.parse(i),a=ze.relative(this.placeRoot,e);return{type:"valueChanged",instancePath:n,fsPath:a,fileType:"value",content:r}}async readNewScript(e,n){let i=await Qa.readFile(e,"utf-8"),r=ze.relative(this.placeRoot,e),a=ze.basename(e),o="ModuleScript";a.endsWith(".server.luau")?o="Script":a.endsWith(".client.luau")&&(o="LocalScript");let s=this.extractParentPath(n);return{type:"scriptCreated",instancePath:n,fsPath:r,fileType:"script",content:i,className:o,parentPath:s}}async readNewValue(e,n){let i=await Qa.readFile(e,"utf-8"),r=JSON.parse(i),a=ze.relative(this.placeRoot,e),o=this.extractParentPath(n);return{type:"valueCreated",instancePath:n,fsPath:a,fileType:"value",content:r,className:r.className,parentPath:o}}async readNewInstance(e,n){let i=await Qa.readFile(e,"utf-8"),r=JSON.parse(i),a=ze.relative(this.placeRoot,e),o=this.extractParentPath(n);return{type:"instanceCreated",instancePath:n,fsPath:a,fileType:"props",content:r,className:r.className,parentPath:o}}extractParentPath(e){return Ut(e)}isServiceInstance(e){let n=Jr(e);return n.length!==2||n[0]!=="game"?!1:ZC.has(n[1])}};ce();import Poe from"path";ce();import Hi from"path";import{createHash as M2}from"crypto";import{promises as vI}from"fs";var koe=500,yI=class{explorerRoot;syncIndex;scanIntervalMs;previousSnapshot=new Map;pendingChanges=new Map;scanTimer=null;readyResolvers=[];isReady=!1;isPaused=!1;isScanning=!1;refreshSnapshotOnNextScan=!1;scanEpoch=0;pendingSequence=0;suppressedPaths=new Map;directionChecker=null;onForwardViolation=null;getPendingKey(e,n){return`${Hi.resolve(e)}\0${n}`}getPendingSlot(e){return e==="addDir"||e==="unlinkDir"?"dir":"file"}getPendingRank(e){return e==="change"?1:2}isStaleScan(e){return this.isPaused||e!==this.scanEpoch}resolveReadyWaiters(){for(let e of this.readyResolvers)e();this.readyResolvers=[]}queuePendingChange(e){let n=this.getPendingSlot(e.type),i=this.getPendingKey(e.fsPath,n),r=this.getPendingRank(e.type),a=this.pendingChanges.get(i);a&&a.rank>r||this.pendingChanges.set(i,{change:e,rank:r,sequence:++this.pendingSequence})}constructor(e,n,i=koe){this.explorerRoot=Hi.resolve(e),this.syncIndex=n,this.scanIntervalMs=i}setDirectionChecker(e){this.directionChecker=e}setOnForwardViolation(e){this.onForwardViolation=e}suppressPath(e,n){let i=Hi.resolve(e);if(n===void 0){this.suppressedPaths.set(i,{mode:"generic"});return}if(n==="missing"){this.suppressedPaths.set(i,{mode:"missing"});return}this.suppressedPaths.set(i,{mode:"hash",hash:n})}async start(){this.scanTimer&&await this.stop("restart"),g.info("SnapshotChangeScanner: starting",{explorerRoot:this.explorerRoot,scanIntervalMs:this.scanIntervalMs}),this.isPaused=!1,this.refreshSnapshotOnNextScan=!1,this.suppressedPaths.clear(),this.scanEpoch+=1;let e=this.scanEpoch;this.previousSnapshot=await this.syncIndex.buildSnapshotSeed(),!this.isStaleScan(e)&&(this.isReady=!0,this.resolveReadyWaiters(),this.scanTimer=setInterval(()=>{this.scanSnapshotDiffs().catch(n=>{g.warn("SnapshotChangeScanner: background scan failed",{error:n instanceof Error?n.message:String(n)})})},this.scanIntervalMs),typeof this.scanTimer=="object"&&this.scanTimer&&"unref"in this.scanTimer&&this.scanTimer.unref())}async stop(e){g.info("SnapshotChangeScanner: stopping",{reason:e??"unspecified",explorerRoot:this.explorerRoot,isReady:this.isReady,isPaused:this.isPaused,pendingChanges:this.pendingChanges.size}),this.scanTimer&&(clearInterval(this.scanTimer),this.scanTimer=null),this.scanEpoch+=1,this.clearTransientState(),this.previousSnapshot=new Map,this.refreshSnapshotOnNextScan=!1,this.isPaused=!1,this.suppressedPaths.clear(),this.resolveReadyWaiters(),this.isReady=!1,this.isScanning=!1}beginFullSyncReplacement(){this.scanEpoch+=1,this.isPaused=!0,this.isReady=!1,this.refreshSnapshotOnNextScan=!1,this.suppressedPaths.clear(),this.clearTransientState()}async endFullSyncReplacement(){if(this.clearTransientState(),this.scanTimer===null){await this.start();return}let e=++this.scanEpoch;this.isReady=!1,this.finalizeResumedBaseline(e)}isActivelyScanning(){return this.scanTimer!==null&&!this.isPaused}injectPending(e){this.isPaused||(this.queuePendingChange(e),g.debug("SnapshotChangeScanner: injected pending change",{relativePath:e.relativePath,type:e.type}))}drainPendingChanges(){let e=Array.from(this.pendingChanges.values()).sort((n,i)=>n.sequence-i.sequence).map(n=>n.change);return this.pendingChanges.clear(),e}getPendingCount(){return this.pendingChanges.size}getLastDetected(){let e=null;for(let n of this.pendingChanges.values()){let i=n.change;(!e||i.detectedAt>e)&&(e=i.detectedAt)}return e}waitUntilReady(){return this.isReady?Promise.resolve():new Promise(e=>{this.readyResolvers.push(e)})}async rescan(){let e=await this.scanSnapshotDiffs(),n=await this.scanStoredModifications();return e+n}async scanSnapshotDiffs(){if(this.isPaused||this.isScanning)return 0;let e=this.scanEpoch;this.isScanning=!0;try{let n=await this.syncIndex.buildSnapshotSeed();if(this.isStaleScan(e))return 0;if(this.refreshSnapshotOnNextScan)return this.previousSnapshot=n,this.refreshSnapshotOnNextScan=!1,this.suppressedPaths.clear(),0;let i=D2(this.previousSnapshot,n),r=await this.processCandidates(i,e);if(this.isStaleScan(e))return 0;let a=this.commitPendingChanges(r);return this.previousSnapshot=n,this.suppressedPaths.clear(),a>0&&g.info("SnapshotChangeScanner: scan found pending changes",{added:a}),a}finally{this.isScanning=!1}}async scanStoredModifications(){if(this.isPaused)return 0;let e=this.scanEpoch,n=await this.scanStoredModificationsInternal(e);if(!n||this.isStaleScan(e))return 0;for(let i of n.forwardViolations)this.onForwardViolation?.(i);return this.commitPendingChanges(n.pending)}async scanStoredModificationsInternal(e){let n=await this.syncIndex.getModifiedFiles(),i={pending:[],forwardViolations:[]};for(let r of n){if(e!==this.scanEpoch)return null;let a=r.fsPath,o=Hi.relative(this.explorerRoot,a);if(await this.isSuppressedPath(a))continue;if(this.shouldRedirectToForwardRestore(o,a)){i.forwardViolations.push(o);continue}let s;switch(r.changeType){case"modified":s="change";break;case"added":s="add";break;case"deleted":s="unlink";break;default:continue}i.pending.push({fsPath:a,relativePath:o,type:s,detectedAt:new Date().toISOString()})}return i}async processCandidates(e,n){let i=[];for(let r of e){if(this.isStaleScan(n))return[];let a=Hi.join(this.explorerRoot,r.relativePath),o=new Date().toISOString();if(await this.isSuppressedPath(a)||r.kind==="file"&&mI(r.relativePath))continue;if(this.shouldRedirectToForwardRestore(r.relativePath,a)){this.onForwardViolation?.(r.relativePath);continue}if(r.kind==="dir"){i.push({fsPath:a,relativePath:r.relativePath,type:r.changeType,detectedAt:o});continue}if(r.changeType==="unlink"){i.push({fsPath:a,relativePath:r.relativePath,type:"unlink",detectedAt:o});continue}let s=this.syncIndex.getStoredFileHash(a);try{let c=await vI.readFile(a,"utf-8"),l=M2("md5").update(c,"utf-8").digest("hex");if(s&&l===s){g.debug("SnapshotChangeScanner: echo skipped",{relativePath:r.relativePath,changeType:r.changeType});continue}i.push({fsPath:a,relativePath:r.relativePath,type:r.changeType,detectedAt:o})}catch(c){if(c.code==="ENOENT"){i.push({fsPath:a,relativePath:r.relativePath,type:"unlink",detectedAt:o});continue}g.warn("SnapshotChangeScanner: failed to read candidate file",{path:r.relativePath,changeType:r.changeType,error:c instanceof Error?c.message:String(c)})}}return i}commitPendingChanges(e){let n=0;for(let i of e){let r=this.getPendingSlot(i.type),a=this.getPendingKey(i.fsPath,r),o=this.getPendingRank(i.type),s=this.pendingChanges.get(a);s&&s.rank>o||(s||n++,this.pendingChanges.set(a,{change:i,rank:o,sequence:++this.pendingSequence}))}return n}clearTransientState(){this.pendingChanges.clear()}async finalizeResumedBaseline(e){try{let n=await this.syncIndex.buildSnapshotSeed();if(e!==this.scanEpoch)return;this.previousSnapshot=n,this.refreshSnapshotOnNextScan=!1;let i=await this.scanStoredModificationsInternal(e);if(e!==this.scanEpoch)return;if(i){for(let r of i.forwardViolations)this.onForwardViolation?.(r);this.commitPendingChanges(i.pending)}this.suppressedPaths.clear(),this.isPaused=!1,this.isReady=!0,this.resolveReadyWaiters()}catch(n){if(e!==this.scanEpoch)return;g.warn("SnapshotChangeScanner: resume baseline refresh failed",{error:n instanceof Error?n.message:String(n)}),this.refreshSnapshotOnNextScan=!0,this.isPaused=!1,this.isReady=!0,this.resolveReadyWaiters()}}shouldRedirectToForwardRestore(e,n){return this.syncIndex.isPathUnderClass(n,rc)?!0:this.directionChecker?this.directionChecker(e)==="forward":!1}async isSuppressedPath(e){let n=Hi.resolve(e);for(let[i,r]of this.suppressedPaths){if(n===i||n.startsWith(`${i}${Hi.sep}`))return r.mode==="generic"?!0:r.mode==="missing"?!await Coe(n):await Ioe(n)===r.hash;if(i.startsWith(`${n}${Hi.sep}`))return r.mode==="generic"}return!1}};async function Coe(t){try{return await vI.access(t),!0}catch{return!1}}async function Ioe(t){try{let e=await vI.readFile(t,"utf-8");return M2("md5").update(e,"utf-8").digest("hex")}catch{return null}}var L2=yI;var Ye="snapshot-scanner",By=class extends L2{placeId;detectorExplorerRoot;lifecycleGeneration=0;constructor(e,n,i={}){super(e,n,i.scanIntervalMs),this.placeId=i.placeId??null,this.detectorExplorerRoot=Poe.resolve(e)}async start(){let e=++this.lifecycleGeneration;g.info("Reverse detector bootstrap scheduled",{placeId:this.placeId,mode:Ye,explorerRoot:this.detectorExplorerRoot}),await super.start(),e===this.lifecycleGeneration&&g.info("Reverse detector bootstrap completed",{placeId:this.placeId,mode:Ye})}async stop(e){this.lifecycleGeneration+=1,g.info("Reverse detector stopping",{placeId:this.placeId,mode:Ye,reason:e??"unspecified"}),await super.stop(e)}beginFullSyncReplacement(){g.info("Reverse detector paused for full sync replacement",{placeId:this.placeId,mode:Ye}),super.beginFullSyncReplacement()}async endFullSyncReplacement(){g.info("Reverse detector baseline refresh scheduled",{placeId:this.placeId,mode:Ye}),await super.endFullSyncReplacement()}async rescan(){g.debug("Reverse detector manual rescan requested",{placeId:this.placeId,mode:Ye});let e=await super.rescan();return g.info("Reverse detector manual rescan completed",{placeId:this.placeId,mode:Ye,added:e,pendingCount:this.getPendingCount()}),e}};import yc from"path";import{promises as U2}from"fs";function eo(t,e,n,i,r=!1){if(i&&i>=1){let s=Ut(e);if(s){let c=t.getEffectiveDir(s,i);if(c){let l=$t(e);if(n){let u=t.getResolvedName(e,n);if(u)l=u;else if(r)throw new Error(`Missing collision mapping for ${e}#${n}`)}return bI(t,c,l)}if(r)throw new Error(`Missing parent collision mapping for ${s}#${i}`)}}let a=t.getEffectiveDir(e,n??1);if(a){let s=t.getExplorerRoot(),c=yc.relative(s,a).split(yc.sep).filter(l=>l.length>0);return nt(["game",...c])}let o=e;if(n){let s=t.getResolvedName(e,n);if(s)o=Tp(o,s);else if(r)throw new Error(`Missing collision mapping for ${e}#${n}`)}return o}function Eoe(t,e){let n=Jr(t),i=Jr(e);if(n.length<=i.length)return!1;for(let r=0;r<i.length;r++)if(n[r]!==i[r])return!1;return!0}function F2(t,e){e=$oe(t,e);let n=new Map;for(let r=0;r<e.length;r++){let a=e[r];if(a?.type==="instanceRemoved")try{let o=eo(t.index,a.path,a.siblingIndex,a.parentSiblingIndex,!0);n.set(r,o)}catch{continue}}if(n.size<=1)return e;let i=new Set;for(let[r,a]of n)for(let[o,s]of n)if(r!==o&&Eoe(a,s)){i.add(r);break}return i.size===0?e:e.filter((r,a)=>!i.has(a))}function $oe(t,e){let n=new Map;for(let a=0;a<e.length;a++){let o=e[a];if(o?.type!=="instanceRemoved"||o.siblingIndex===void 0)continue;let s=n.get(o.path);s||(s=[],n.set(o.path,s)),s.push(a)}let i=!1;for(let a of n.values()){if(a.length<2)continue;let o=new Set;for(let s of a){let c=e[s].siblingIndex;if(o.has(c)){i=!0;break}o.add(c)}if(i)break}if(!i)return e;let r=[...e];for(let[a,o]of n){if(o.length<2)continue;let s=to(t.index,a,void 0,!1),c=new Set(t.index.getRegisteredSiblingIndices(a,s));if(c.size===0)continue;let l=new Set;for(let u of o){let p=e[u];if(p.type!=="instanceRemoved")continue;let d=p.siblingIndex;if(!l.has(d)&&c.has(d))l.add(d);else{let f;for(let m of c)if(!l.has(m)){f=m;break}f!==void 0&&(l.add(f),r[u]={...p,siblingIndex:f})}}}return r}function to(t,e,n,i=!1){if(n&&n>=1){let r=Ut(e);if(r){let a=t.getEffectiveDir(r,n);if(a)return a;if(i)throw new Error(`Missing parent collision mapping for ${r}#${n}`)}}return t.resolveParentDir(e)}async function Vi(t,e,n,i,r){let a=yc.join(e,n),o=yc.join(e,i);try{(await U2.stat(a)).isDirectory()&&(r&&(r.suppressPath(a),r.suppressPath(o)),await U2.rename(a,o),t.renameHashKeysUnder(a,o),t.renameReverseNameMappingsUnder(a,o),t.renameInstanceDirMappingsUnder(a,o))}catch{return}}function bI(t,e,n){let i=yc.relative(t.getExplorerRoot(),e).split(yc.sep).filter(r=>r.length>0);return nt(["game",...i,n])}async function Zy(t,e){let n=t.index.planCollisionReindexAfterBatchRemovals(e.instancePath,[...e.removedSiblingIndices],e.parentDir);for(let i of n.renames)await Vi(t.index,e.parentDir,i.from,i.to,t.fileWatcher),await vc(t.writer,t.index,e.parentDir,i.from,i.to);t.index.applyCollisionReindexAfterBatchRemovals(e.instancePath,e.parentDir,n)}async function vc(t,e,n,i,r){let a=bI(e,n,i),o=bI(e,n,r);await t.renameInTree(a,o,r)}import Hy from"path";var q2=5*60*1e3,B2=100;function xI(t){return t!==null&&typeof t=="object"&&!Array.isArray(t)}function Rp(t){return typeof t=="string"&&t.length>0}function Vy(t,e){let n=Hy.resolve(t),i=Hy.resolve(e),r=Hy.relative(n,i);return r===""||!r.startsWith("..")&&!Hy.isAbsolute(r)}function Z2(t){if(!xI(t))return{success:!1,error:"Body must be an object"};let e=t.phase;if(e!=="start"&&e!=="chunk"&&e!=="complete")return{success:!1,error:`Invalid phase: ${String(e)}`};if(!Rp(t.clientId))return{success:!1,error:"clientId is required"};switch(e){case"start":return typeof t.placeId!="number"?{success:!1,error:"placeId must be a number"}:Rp(t.placeName)?typeof t.totalServices!="number"?{success:!1,error:"totalServices must be a number"}:typeof t.totalInstances!="number"?{success:!1,error:"totalInstances must be a number"}:t.previousPlaceId!==void 0&&typeof t.previousPlaceId!="number"?{success:!1,error:"previousPlaceId must be a number"}:{success:!0,data:t}:{success:!1,error:"placeName is required"};case"chunk":return Rp(t.serviceName)?Rp(t.serviceClassName)?Array.isArray(t.instances)?typeof t.chunkIndex!="number"?{success:!1,error:"chunkIndex must be a number"}:typeof t.totalChunks!="number"?{success:!1,error:"totalChunks must be a number"}:{success:!0,data:t}:{success:!1,error:"instances must be an array"}:{success:!1,error:"serviceClassName is required"}:{success:!1,error:"serviceName is required"};case"complete":return typeof t.instanceCount!="number"?{success:!1,error:"instanceCount must be a number"}:typeof t.scriptCount!="number"?{success:!1,error:"scriptCount must be a number"}:{success:!0,data:t}}}function H2(t){if(!xI(t))return{success:!1,error:"Body must be an object"};if(!Rp(t.clientId))return{success:!1,error:"clientId is required"};if(t.placeId!==void 0&&typeof t.placeId!="number")return{success:!1,error:"placeId must be a number"};if(typeof t.timestamp!="number")return{success:!1,error:"timestamp must be a number"};if(!Array.isArray(t.changes)||t.changes.length===0)return{success:!1,error:"changes must be a non-empty array"};let e=new Set(["propertyChanged","instanceAdded","instanceRemoved","instanceRenamed","instanceMoved","scriptSourceChanged"]);for(let n=0;n<t.changes.length;n++){let i=t.changes[n];if(!xI(i)||!e.has(i.type))return{success:!1,error:`changes[${n}]: invalid type '${String(i?.type)}'`}}return{success:!0,data:t}}import er from"path";import{promises as no}from"fs";ce();var Wy=class{constructor(e){this.ctx=e}async processChangeForPlace(e,n,i){let r=xL(n.type);if(e.directions[r]==="reverse")return g.info("Reverse enforcement: rejecting Studio change on reverse-only path",{placeId:e.placeId,changeType:n.type,dirKey:r,path:"path"in n?n.path:void 0}),(n.type==="scriptSourceChanged"||n.type==="propertyChanged")&&await this.injectLocalAsReverse(e,n),null;switch(n.type){case"propertyChanged":{let o=eo(e.index,n.path,n.siblingIndex,n.parentSiblingIndex,!0),s=await this.checkPropertyConflictForPlace(e,o,n);if(s)return s;let c=n;if(c.attributes!==void 0||c.tags!==void 0){let l=e.index.resolvePropsPath(o),u=await this.buildPropertyChangedPropsData(l,o,n);await e.writer.writeProps(o,u)}else c.properties&&typeof c.properties=="object"&&!Array.isArray(c.properties)?await e.writer.updateProperties(o,c.properties):n.property!==void 0?await e.writer.updateProperty(o,n.property,n.value):g.warn("propertyChanged: missing both property and properties",{path:o});return null}case"instanceAdded":{let o=nc.has(n.className),s=n.path,c=n.name;{let p=to(e.index,n.path,n.parentSiblingIndex,!0),{resolved:d,retroactiveRename:f}=e.index.registerCollision(n.path,n.siblingIndex,p);f&&(await Vi(e.index,p,f.from,f.to,e.fileWatcher),await vc(e.writer,e.index,p,f.from,f.to));let m=e.index.sanitizeName(n.name);if(d!==m||p!==e.index.resolveParentDir(n.path)){let h=e.index.getExplorerRoot(),y=er.relative(h,p).split(er.sep).filter(v=>v.length>0);s=nt(["game",...y,d]),c=d}}await e.writer.writeInstance({path:s,name:n.name,className:n.className,properties:n.properties,attributes:n.attributes,tags:n.tags,scriptSource:n.scriptSource,hasChildren:!1,childCount:0,siblingIndex:n.siblingIndex}),this.setOriginalClassMapping(e,n.path,n.className,n.siblingIndex);let l={name:c,className:n.className,childCount:0,...c!==n.name?{originalName:n.name}:{}},u=Ut(s)||n.parentPath;return await e.writer.addToTree(u,l),e.instanceCount++,o&&n.scriptSource!==void 0&&e.scriptCount++,null}case"instanceRemoved":{let o=eo(e.index,n.path,n.siblingIndex,n.parentSiblingIndex,!0),s=to(e.index,n.path,n.parentSiblingIndex,!0);g.info("Processing instanceRemoved",{path:o}),await e.writer.removeInstance(o),this.removeOriginalClassMapping(e,n.path,o);let c=$t(o),l=Ut(o);if(l&&c&&await e.writer.removeFromTree(l,c),n.siblingIndex!==void 0){let p=`${s}\0${n.path}`,d=i.get(p);d||(d={instancePath:n.path,parentDir:s,removedSiblingIndices:new Set},i.set(p,d)),d.removedSiblingIndices.add(n.siblingIndex)}let u=Ut(o);if(u)try{let p=e.index.resolveChildrenDir(u);er.dirname(p)!==e.index.getExplorerRoot()&&(await no.readdir(p)).length===0&&await no.rmdir(p)}catch{return null}return null}case"instanceRenamed":{let o=eo(e.index,n.oldPath,n.siblingIndex,n.parentSiblingIndex,!0),s=Ut(n.oldPath)===Ut(n.newPath),c=s?to(e.index,n.oldPath,n.parentSiblingIndex,!0):null,l=to(e.index,n.newPath,n.parentSiblingIndex,!0),{resolved:u,retroactiveRename:p}=e.index.registerCollision(n.newPath,n.siblingIndex,l);p&&(await Vi(e.index,l,p.from,p.to,e.fileWatcher),await vc(e.writer,e.index,l,p.from,p.to));let d=u!==e.index.sanitizeName(n.newName)?Tp(n.newPath,u):n.newPath;if(await e.writer.renameInstance(o,d,n.newName),this.renameOriginalClassMapping(e,n.oldPath,n.newPath),await e.writer.renameInTree(o,d,u,n.newName),s&&c&&n.siblingIndex!==void 0){let f=e.index.planCollisionReindexAfterRemoval(n.oldPath,n.siblingIndex,c);for(let m of f.renames)await Vi(e.index,c,m.from,m.to,e.fileWatcher),await vc(e.writer,e.index,c,m.from,m.to);e.index.applyCollisionReindexAfterRemoval(n.oldPath,c,f)}return null}case"instanceMoved":{let o=eo(e.index,n.oldPath,n.siblingIndex,n.parentSiblingIndex,!0),s=to(e.index,n.newPath,n.parentSiblingIndex,!0),{resolved:c,retroactiveRename:l}=e.index.registerCollision(n.newPath,n.siblingIndex,s);l&&(await Vi(e.index,s,l.from,l.to,e.fileWatcher),await vc(e.writer,e.index,s,l.from,l.to));let u=$t(n.newPath)||"",p=c!==e.index.sanitizeName(u)?Tp(n.newPath,c):n.newPath;return await e.writer.moveInstance(o,p),this.renameOriginalClassMapping(e,n.oldPath,n.newPath),await e.writer.moveInTree(o,p),null}case"scriptSourceChanged":{let o=eo(e.index,n.path,n.siblingIndex,n.parentSiblingIndex,!0),s=await this.checkScriptConflictForPlace(e,o,n.source);if(s)return s;let c=await this.resolveScriptClassForPlace(e,o);return await this.cleanupStaleScriptFilesForPlace(e,o,c),await e.writer.writeScript(o,c,n.source,!1),null}}}async injectLocalAsReverse(e,n){if(e.fileWatcher)try{if(n.type==="scriptSourceChanged"){let i=await this.resolveScriptClassForPlace(e,n.path),r=e.index.resolveScriptPath(n.path,i,!1),a=er.join(this.ctx.config.getPlaceRoot(e.placeId),"explorer"),o=er.relative(a,r);e.fileWatcher.injectPending({fsPath:r,relativePath:o,type:"change",detectedAt:new Date().toISOString()})}else if(n.type==="propertyChanged"){let i=e.index.resolvePropsPath(n.path),r=er.join(this.ctx.config.getPlaceRoot(e.placeId),"explorer"),a=er.relative(r,i);e.fileWatcher.injectPending({fsPath:i,relativePath:a,type:"change",detectedAt:new Date().toISOString()})}}catch(i){g.debug("injectLocalAsReverse: could not inject",{changeType:n.type,error:i instanceof Error?i.message:String(i)})}}async resolveScriptClassForPlace(e,n){try{let i=e.index.resolvePropsPath(n),r=await no.readFile(i,"utf-8"),a=JSON.parse(r);if(a.className&&nc.has(a.className))return a.className}catch{return"ModuleScript"}return"ModuleScript"}async cleanupStaleScriptFilesForPlace(e,n,i){let r=wy[i],a=er.join(this.ctx.config.getPlaceRoot(e.placeId),"explorer");for(let[o,s]of Object.entries(wy)){if(s===r)continue;let c=e.index.resolveScriptPath(n,o,!1);try{await no.unlink(c),e.index.removeHash(c),g.info("Cleaned up stale script file",{placeId:e.placeId,removed:er.relative(a,c),currentClassName:i})}catch(l){l.code!=="ENOENT"&&g.warn("Failed to clean up stale script file",{path:c,error:l instanceof Error?l.message:String(l)})}}}async checkPropertyConflictForPlace(e,n,i){try{let r=e.index.resolvePropsPath(n),a=e.index.getFileHash(r);if(!a)return null;let o;try{o=await no.readFile(r,"utf-8")}catch{return null}return e.index.computeHash(o)===a?null:{fsPath:er.relative(this.ctx.config.getPlaceRoot(e.placeId),r),instancePath:n,fileType:"props",studioContent:await this.buildStudioPropsConflictContent(r,n,i),fileContent:o}}catch{return null}}async checkScriptConflictForPlace(e,n,i){try{let r=await this.resolveScriptClassForPlace(e,n),a=e.index.resolveScriptPath(n,r,!1),o=e.index.getFileHash(a);if(!o)return null;let s;try{s=await no.readFile(a,"utf-8")}catch{return null}return e.index.computeHash(s)===o?null:{fsPath:er.relative(this.ctx.config.getPlaceRoot(e.placeId),a),instancePath:n,fileType:"script",studioContent:i,fileContent:s}}catch{return null}}async applyDeferredCollisionBatchReindexForPlace(e,n){await Zy(e,n)}async buildStudioPropsConflictContent(e,n,i){let r=await this.buildPropertyChangedPropsData(e,n,i);return JSON.stringify(r,null,2)}async buildPropertyChangedPropsData(e,n,i){let r=null;try{let s=await no.readFile(e,"utf-8");r=JSON.parse(s)}catch{r=null}let a=i.properties?{...i.properties}:{...r?.properties??{}};i.property&&(a[i.property]=i.value);let o={name:r?.name??$t(n)??"",className:i.className??r?.className??"Folder",properties:a};return i.attributes!==void 0?Object.keys(i.attributes).length>0&&(o.attributes={...i.attributes}):r?.attributes&&(o.attributes={...r.attributes}),i.tags!==void 0?i.tags.length>0&&(o.tags=[...i.tags]):r?.tags&&(o.tags=[...r.tags]),o}setOriginalClassMapping(e,n,i,r){e.index.setClassName(n,i,r)}removeOriginalClassMapping(e,n,i){n!==i&&e.index.removeClassMappingsUnderInstancePath(n)}renameOriginalClassMapping(e,n,i){n!==i&&e.index.renameClassMappingKeys(n,i)}};import V2 from"path";import{promises as Gy}from"fs";ce();var Jy=class{constructor(e){this.ctx=e}async handlePreCheck(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({hasPreviousSync:!1,changes:[],summary:{modified:0,deleted:0,added:0,total:0}});return}let r=this.ctx.config.getPlaceRoot(i),a=V2.join(r,".sync-index.json"),o=!1;try{await Gy.access(a),o=!0}catch{o=!1}if(!o){n.status(200).json({hasPreviousSync:!1,changes:[],summary:{modified:0,deleted:0,added:0,total:0}});return}let s=V2.join(r,"explorer"),c=new Zi(r,s);await c.loadFromDisk();let l=await c.getModifiedFiles(),u=0,p=0,d=0;for(let m of l)switch(m.changeType){case"modified":u++;break;case"deleted":p++;break;case"added":d++;break}let f=l.map(m=>({relPath:m.relPath,instancePath:m.instancePath,changeType:m.changeType,fileType:m.fileType}));n.status(200).json({hasPreviousSync:!0,changes:f,summary:{modified:u,deleted:p,added:d,total:f.length}})}async handleSyncDirections(e,n){let i=e.body,r=i.placeId??this.ctx.getDefaultRuntimePlaceId();if(r==null){n.status(400).json({error:"Missing placeId"});return}let a=this.ctx.places.get(r);if(!a){n.status(404).json({error:"Place not found",message:`No sync context for place ${r}`});return}if(i.directions&&typeof i.directions=="object"){let o=c=>c==="forward"||c==="reverse"||c==="bidirectional",s=i.directions;o(s.scripts)&&(a.directions.scripts=s.scripts),o(s.values)&&(a.directions.values=s.values),o(s.containers)&&(a.directions.containers=s.containers),o(s.data)&&(a.directions.data=s.data),o(s.services)&&(a.directions.services=s.services),g.info("Sync directions updated",{placeId:r,directions:a.directions})}if(i.applyModes&&typeof i.applyModes=="object"){let o=c=>c==="auto"||c==="manual",s=i.applyModes;o(s.scripts)&&(a.applyModes.scripts=s.scripts),o(s.values)&&(a.applyModes.values=s.values),o(s.containers)&&(a.applyModes.containers=s.containers),o(s.data)&&(a.applyModes.data=s.data),o(s.services)&&(a.applyModes.services=s.services)}this.ctx.touchRuntimePlace(r),n.status(200).json({status:"updated",directions:a.directions,applyModes:a.applyModes})}async handleForwardRestoreList(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({paths:[]});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}let a=r.forwardRestoreQueue.splice(0);this.ctx.touchRuntimePlace(i),g.info("Forward restore list drained",{placeId:i,count:a.length}),n.status(200).json({paths:a})}async handleSyncHistory(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({entries:[],total:0,hasMore:!1});return}let r=en(e.query.limit,50,1,200),a=en(e.query.offset,0,0),o=e.query.direction,s=e.query.type,c=this.ctx.places.get(i);c&&(await c.writer.flushHistory(),this.ctx.touchRuntimePlace(i));let l=this.ctx.config.getHistoryPath(i),p=(await Dy(l,1e4)).reverse().filter(h=>!(o&&h.direction!==o||s&&h.type!==s)),d=p.length,m={entries:p.slice(a,a+r),total:d,hasMore:a+r<d};n.status(200).json(m)}getStatusSummary(){if(this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0)return{active:!0,placeId:this.ctx.activeFullSyncPlaceId};for(let[e,n]of this.ctx.places.entries())if(n.state==="syncing")return{active:!0,placeId:e};return{active:!1}}getDirectionForCategory(e){let n;if(this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0&&(n=this.ctx.places.get(this.ctx.activeFullSyncPlaceId)),!n){let r=this.ctx.getDefaultRuntimePlaceId();r!=null&&(n=this.ctx.places.get(r))}if(!n)return"forward";let i=e;return n.directions[i]??"forward"}getStatusDirect(e){let n=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(n==null)return{state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.ctx.config.getSyncRoot(),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0,applyModes:{...Ga},reverseDetectorActive:!1,reverseDetectorMode:Ye,fileWatcherActive:!1};let i=this.ctx.places.get(n);if(!i)return{state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:this.ctx.config.getPlaceRoot(n),activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0,applyModes:{...Ga},reverseDetectorActive:!1,reverseDetectorMode:Ye,fileWatcherActive:!1};let r=i.fileWatcher?.getPendingCount()??0,a=i.fileWatcher?.isActivelyScanning()??!1,o=i.fileWatcher!==null;return{state:i.state,instanceCount:i.instanceCount,scriptCount:i.scriptCount,lastFullSync:i.lastFullSync,lastIncrementalSync:i.lastIncrementalSync,syncRoot:this.ctx.config.getPlaceRoot(n),activeClientId:i.activeClientId,reverseSyncAvailable:r>0,modifiedFileCount:r,applyModes:{...i.applyModes},reverseDetectorActive:a,reverseDetectorMode:Ye,fileWatcherActive:o}}async getHistoryDirect(e,n){let i=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(i==null||!Number.isFinite(i))return{entries:[],total:0,hasMore:!1};let r=this.ctx.places.get(i);r&&await r.writer.flushHistory();let a=Math.min(Math.max(n?.limit??50,1),200),o=Math.max(n?.offset??0,0),s=this.ctx.config.getHistoryPath(i),l=(await Dy(s,1e4)).reverse(),u=l.length;return{entries:l.slice(o,o+a),total:u,hasMore:o+a<u}}getDirectionsDirect(e){let n=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(n==null)return{...Wa};let i=this.ctx.places.get(n);return i?{...i.directions}:{...Wa}}getProgressDirect(e){let n=e!=null?parseInt(e,10):this.ctx.getDefaultRuntimePlaceId();if(n==null)return{state:"idle",isSyncing:!1};let i=this.ctx.places.get(n);if(!i)return{state:"idle",isSyncing:!1};if(!i.syncProgress)return{state:i.state,isSyncing:!1,lastSync:{instanceCount:i.instanceCount,scriptCount:i.scriptCount,completedAt:i.lastFullSync}};let r=i.syncProgress,a=Date.now()-r.syncStartTime,o=r.totalInstances>0?Math.min(100,Math.round(r.processedInstances/r.totalInstances*100)):0,s;if(r.processedInstances>0&&o<100){let l=a/r.processedInstances,u=r.totalInstances-r.processedInstances;s=Math.round(l*u)}let c=a>0?Math.round(r.bytesReceived/a*1e3):0;return{state:i.state,isSyncing:!0,progressPercent:o,currentService:r.currentService,currentChunk:{index:r.currentChunkIndex,total:r.currentTotalChunks},instances:{processed:r.processedInstances,total:r.totalInstances},services:{processed:r.processedServices,total:r.totalServices},elapsedMs:a,estimatedRemainingMs:s,bytesReceived:r.bytesReceived,bytesPerSecond:c}}async readSyncedFile(e,n){let i=parseInt(e,10),r=this.ctx.places.get(i);if(!r)throw new Error(`Place ${e} is not loaded in the sync cache. Start sync for this place before reading synced files.`);let a=r.index.resolvePropsPath(n);try{return{content:await Gy.readFile(a,"utf-8"),path:a}}catch(s){if(s.code!=="ENOENT")throw s}for(let s of nc){let c=r.index.resolveScriptPath(n,s,!1);try{return{content:await Gy.readFile(c,"utf-8"),path:c}}catch{continue}}let o=r.index.resolveValuePath(n);try{return{content:await Gy.readFile(o,"utf-8"),path:o}}catch(s){if(s.code!=="ENOENT")throw s}throw new Error(`No synced file found for instance: ${n}. Ensure the instance has been synced to disk and that the canonical instance path is correct.`)}async writeSyncedFile(e,n,i){let r=parseInt(e,10),a=this.ctx.places.get(r);if(!a)throw new Error(`Place ${e} is not loaded in the sync cache. Start sync for this place before writing synced files.`);await a.writer.writeScript(n,"Script",i,!1)}async executeViaDisk(e,n){let i=this.ctx.getDefaultRuntimePlaceId();if(i==null)throw new Error("No active sync place for disk execution. Start sync for the current place before using reverse-sync disk execution.");let r=this.ctx.places.get(i);if(!r)throw new Error(`Place ${i} is not loaded in the sync cache. Restart sync before retrying disk execution.`);switch(e){case"manage_scripts_set_source":{let a=n.scriptType||n.className||"Script";return await r.writer.writeScript(n.path,a,n.source,!1),{success:!0,path:n.path}}case"manage_properties_set":return await r.writer.writeProps(n.path,{className:n.className||"Instance",name:$t(n.path),properties:{[n.property]:n.value}}),{success:!0,path:n.path};default:throw new Error(`Disk execution is not supported for command: ${e}. This command must run through the plugin route.`)}}};import Pe from"path";import{createHash as Toe,randomUUID as Roe}from"crypto";import{promises as pn}from"fs";ce();function Ooe(t){return Toe("md5").update(t,"utf-8").digest("hex")}var Ky=class{constructor(e){this.ctx=e}preserveLocalFilesMap=new Map;pendingServiceTrees=new Map;async handleInitStart(e,n,i){if(n.previousPlaceId!==void 0&&n.previousPlaceId!==e&&(g.info("Place promotion detected",{from:n.previousPlaceId,to:e}),await this.ctx.config.promotePlaceRoot(n.previousPlaceId,e,n.placeName),this.ctx.places.get(n.previousPlaceId)&&this.ctx.places.delete(n.previousPlaceId),this.ctx.activeFullSyncPlaceId===n.previousPlaceId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(n.previousPlaceId)),this.ctx.activeFullSyncPlaceId!==null&&this.ctx.activeFullSyncPlaceId!==void 0&&this.ctx.activeFullSyncPlaceId!==e){i.status(409).json({error:"Conflict",message:`Place ${this.ctx.activeFullSyncPlaceId} is currently syncing. Only one place can sync at a time.`});return}let r=await this.ctx.getOrCreatePlaceContext(e,n.placeName);if(r.activeClientId&&r.activeClientId!==n.clientId&&r.activeFullSyncSessionId!==null){i.status(409).json({error:"Conflict",message:`Another client (${r.activeClientId}) is currently syncing this place`});return}if(this.ctx.activeFullSyncPlaceId=e,this.ctx.touchRuntimePlace(e),r.activeClientId=n.clientId,r.placeName=n.placeName,n.directions&&typeof n.directions=="object"){let p=n.directions,d=f=>f==="forward"||f==="reverse"||f==="bidirectional";d(p.scripts)&&(r.directions.scripts=p.scripts),d(p.values)&&(r.directions.values=p.values),d(p.containers)&&(r.directions.containers=p.containers),d(p.data)&&(r.directions.data=p.data),d(p.services)&&(r.directions.services=p.services),g.info("Sync directions received",{placeId:e,directions:r.directions})}else r.directions={...Wa};if(n.applyModes&&typeof n.applyModes=="object"){let p=n.applyModes,d=f=>f==="auto"||f==="manual";d(p.scripts)&&(r.applyModes.scripts=p.scripts),d(p.values)&&(r.applyModes.values=p.values),d(p.containers)&&(r.applyModes.containers=p.containers),d(p.data)&&(r.applyModes.data=p.data),d(p.services)&&(r.applyModes.services=p.services)}else r.applyModes={...Ga};r.forwardRestoreQueue=[];let a=Roe();r.activeFullSyncSessionId=a,this.pendingServiceTrees.set(a,new Map),r.instanceCount=0,r.scriptCount=0;let o=this.ctx.config.getPlaceRoot(e),s=Pe.join(o,`explorer_tmp_${a}`);await pn.mkdir(s,{recursive:!0}),r.tmpIndex=new Zi(o,s),r.tmpWriter=new gc(this.ctx.config,r.tmpIndex,e),r.collisionDirMap=new Map;let c=n.preserveLocalFiles;Array.isArray(c)&&c.length>0&&(this.setPreserveLocalFiles(a,c),g.info("PreserveLocalFiles set for sync",{syncId:a,fileCount:c.length}));let l={version:1,placeId:n.placeId,placeName:n.placeName,lastFullSync:null,lastIncrementalSync:null,instanceCount:0,scriptCount:0,syncMode:"mirror"},u=Pe.join(o,".sync-meta.json");await pn.mkdir(Pe.dirname(u),{recursive:!0}),await this.ctx.atomicWriteFile(u,JSON.stringify(l,null,2)+`
137
137
  `),this.startTTLTimerForPlace(r,a),r.state="initializing",r.index.resetNameCounters(),r.syncProgress={syncStartTime:Date.now(),totalInstances:n.totalInstances,totalServices:n.totalServices,processedInstances:0,processedServices:0,currentService:null,currentChunkIndex:0,currentTotalChunks:0,bytesReceived:0,processedChunks:0},r.writer.appendChangeLog(`FULL_SYNC_START clientId=${n.clientId} placeId=${n.placeId}`),r.writer.setBootstrapMode(!0),r.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncStart",direction:"forward",source:"studio",path:`place_${n.placeId}`,details:`services:${n.totalServices} instances:${n.totalInstances}`}),g.info("Full sync started",{syncId:a,clientId:n.clientId,placeId:n.placeId,placeName:n.placeName,totalServices:n.totalServices,totalInstances:n.totalInstances}),i.status(200).json({status:"started",syncId:r.activeFullSyncSessionId})}async handleInitChunk(e,n,i){let r=this.ctx.places.get(e);if(!r||!r.activeFullSyncSessionId||!r.tmpIndex||!r.tmpWriter){i.status(400).json({error:"No active sync session",message:"Call sync/init with phase=start first"});return}if(r.activeClientId&&n.clientId&&r.activeClientId!==n.clientId){i.status(409).json({error:"Conflict",message:`This sync session belongs to client ${r.activeClientId}`});return}let a=r.activeFullSyncSessionId,o=this.getOrCreatePendingServiceTree(a,n),s=0,c=r.collisionDirMap;for(let u of n.instances){if(this.ctx.config.isForbiddenPath(u.path))continue;let p=r.tmpIndex.resolveParentDir(u.path),d=c.get(p)??p,{resolved:f,retroactiveRename:m}=r.tmpIndex.registerCollision(u.path,u.siblingIndex??void 0,d);m&&(await Vi(r.tmpIndex,d,m.from,m.to),this.rewritePendingEffectivePaths(o,r.tmpIndex.getExplorerRoot(),Pe.join(d,m.from),Pe.join(d,m.to)));let h=r.tmpIndex.resolveChildrenDir(u.path),y=Pe.join(d,f);y!==h&&c.set(h,y);let v=r.tmpIndex.sanitizeName(u.name),_=u;if(d!==p||f!==v){let C=r.tmpIndex.getExplorerRoot(),I=Pe.relative(C,d).split(Pe.sep).filter(q=>q.length>0),M=nt(["game",...I,f]);_={...u,path:M}}let S=await r.tmpWriter.writeInstance(_);r.tmpIndex.setClassName(u.path,u.className,u.siblingIndex),s++,(S.propsWritten||S.valueWritten)&&r.instanceCount++,S.scriptWritten&&r.scriptCount++,o.instances.push({effectivePath:_.path,originalPath:u.path,className:u.className})}let l=this.isLastChunk(o,n.chunkIndex,n.totalChunks);if(l){let u=this.buildServiceTree(r,o);await r.tmpWriter.writeTree(n.serviceName,u);let p=this.pendingServiceTrees.get(a);p?.delete(n.serviceName),p&&p.size===0&&this.pendingServiceTrees.delete(a)}r.syncProgress&&(r.syncProgress.processedInstances+=s,r.syncProgress.currentService=n.serviceName,r.syncProgress.currentChunkIndex=n.chunkIndex,r.syncProgress.currentTotalChunks=n.totalChunks,r.syncProgress.processedChunks++,r.syncProgress.bytesReceived+=JSON.stringify(n).length,l&&r.syncProgress.processedServices++),g.debug("Sync chunk processed",{placeId:e,serviceName:n.serviceName,chunkIndex:n.chunkIndex,totalChunks:n.totalChunks,processed:s}),i.status(200).json({processed:s,service:n.serviceName})}async handleInitComplete(e,n,i){let r=this.ctx.places.get(e);if(!r||!r.activeFullSyncSessionId){i.status(400).json({error:"No active sync session",message:"Call sync/init with phase=start first"});return}if(r.activeClientId&&n.clientId&&r.activeClientId!==n.clientId){i.status(409).json({error:"Conflict",message:`This sync session belongs to client ${r.activeClientId}`});return}let a=r.activeFullSyncSessionId,o=this.ctx.config.getPlaceRoot(e),s=Pe.join(o,"explorer"),c=Pe.join(o,`explorer_tmp_${a}`),l=r.fileWatcher,u=this.getAndClearPreserveLocalFiles(a),p=new Map,d=[];if(u.length>0){for(let f of u){let m=Pe.resolve(o,f);try{let h=await pn.readFile(m,"utf-8");p.set(f,h)}catch{d.push(f)}}g.info("Backed up local files for preservation",{placeId:e,requested:u.length,backed:p.size,deleted:d.length})}l&&l.beginFullSyncReplacement();try{try{await pn.rm(s,{recursive:!0,force:!0})}catch{}await pn.rename(c,s),r.instanceCount=n.instanceCount,r.scriptCount=n.scriptCount,r.lastFullSync=new Date().toISOString();let f={version:1,placeId:r.placeId,placeName:r.placeName,lastFullSync:r.lastFullSync,lastIncrementalSync:r.lastIncrementalSync,instanceCount:r.instanceCount,scriptCount:r.scriptCount,syncMode:"mirror"},m=Pe.join(o,".sync-meta.json");if(await this.ctx.atomicWriteFile(m,JSON.stringify(f,null,2)+`
138
138
  `),this.clearTTLTimerForPlace(r,a),r.index.clearAllHashes(),r.index.clearClassMappings(),r.tmpIndex){let h=r.tmpIndex.getExplorerRoot(),y=r.index.getExplorerRoot();for(let[v,_]of r.tmpIndex.getAllHashes()){let S=Pe.relative(h,v),C=Pe.resolve(y,S);r.index.updateHashByValue(C,_)}for(let[v,_]of r.tmpIndex.getAllFileHashes()){let S=Pe.relative(h,v),C=Pe.resolve(y,S);r.index.updateFileHashByValue(C,_)}r.index.resetNameCounters(),r.index.mergeNameMappingsFrom(r.tmpIndex)}if(r.tmpIndex=null,r.tmpWriter&&(r.tmpWriter.stopChangeLogFlusher(),r.tmpWriter=null),this.pendingServiceTrees.delete(a),r.collisionDirMap=null,await r.index.saveToDisk(),r.state="syncing",r.activeFullSyncSessionId=null,r.syncProgress=null,this.ctx.touchRuntimePlace(e),this.ctx.activeFullSyncPlaceId=null,p.size>0){for(let[h,y]of p){let v=Pe.resolve(o,h);try{await pn.mkdir(Pe.dirname(v),{recursive:!0}),await pn.writeFile(v,y,"utf-8"),l?.suppressPath(v,Ooe(y))}catch(_){g.warn("Failed to restore preserved file",{path:h,error:_ instanceof Error?_.message:String(_)})}}g.info("Restored preserved local files",{placeId:e,count:p.size})}if(d.length>0){let h=0;for(let y of d){let v=Pe.resolve(o,y);try{await pn.unlink(v),r.index.removeHash(v),l?.suppressPath(v,"missing"),h++}catch(_){_.code!=="ENOENT"&&g.warn("Failed to delete preserved-as-deleted file",{path:y,error:_ instanceof Error?_.message:String(_)})}}h>0&&(await r.index.saveToDisk(),g.info("Deleted locally-removed files from new sync",{placeId:e,count:h}))}try{let h=this.ctx.config.getSyncRoot();await Py(h,o)}catch(h){g.warn("Failed to generate place sourcemap after full sync",{placeId:e,error:h instanceof Error?h.message:String(h)})}try{let h=this.ctx.config.getSyncRoot(),y={version:1,lastActivePlaceId:r.placeId,lastActivePlaceName:r.placeName??null,lastActiveAt:new Date().toISOString()},v=Pe.join(h,".project-meta.json");await this.ctx.atomicWriteFile(v,JSON.stringify(y,null,2)+`
139
- `),await ic(h)}catch(h){g.warn("Failed to refresh root representative sourcemap after full sync",{placeId:e,error:h instanceof Error?h.message:String(h)})}this.ctx.startFileWatcherForPlace(r).catch(h=>{g.warn("Failed to start reverse detector after full sync",{placeId:e,error:h instanceof Error?h.message:String(h)}),r.fileWatcher=null}),r.writer.setBootstrapMode(!1),r.writer.appendChangeLog(`FULL_SYNC_COMPLETE instances=${r.instanceCount} scripts=${r.scriptCount}`),r.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncComplete",direction:"forward",source:"studio",path:`place_${e}`,details:`instances:${r.instanceCount} scripts:${r.scriptCount}`}),g.info("Full sync completed",{placeId:e,instanceCount:r.instanceCount,scriptCount:r.scriptCount}),i.status(200).json({status:"completed",instanceCount:r.instanceCount,scriptCount:r.scriptCount,syncRoot:this.ctx.config.getPlaceRoot(e)})}catch(f){throw l&&l.endFullSyncReplacement(),f}}setPreserveLocalFiles(e,n){this.preserveLocalFilesMap.set(e,n)}getAndClearPreserveLocalFiles(e){let n=this.preserveLocalFilesMap.get(e)||[];return this.preserveLocalFilesMap.delete(e),n}clearPreserveLocalFiles(e){this.preserveLocalFilesMap.delete(e)}clearPendingServiceTrees(e){this.pendingServiceTrees.delete(e)}startTTLTimerForPlace(e,n){let i=setTimeout(async()=>{g.warn("Incomplete sync TTL expired, cleaning up",{placeId:e.placeId,syncId:n});let r=this.ctx.config.getPlaceRoot(e.placeId),a=Pe.join(r,`explorer_tmp_${n}`);try{await pn.rm(a,{recursive:!0,force:!0})}catch(o){g.error("Failed to clean up expired temp dir",o instanceof Error?o:new Error(String(o)))}e.incompleteSyncTimer=null,e.activeFullSyncSessionId===n&&(e.activeFullSyncSessionId=null,e.activeClientId=null,e.state="idle",e.tmpIndex=null,this.pendingServiceTrees.delete(n),e.collisionDirMap=null,e.tmpWriter&&(e.tmpWriter.stopChangeLogFlusher(),e.tmpWriter=null),this.ctx.activeFullSyncPlaceId===e.placeId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(e.placeId))},q2);i&&typeof i=="object"&&"unref"in i&&i.unref(),e.incompleteSyncTimer=i}getOrCreatePendingServiceTree(e,n){let i=this.pendingServiceTrees.get(e);i||(i=new Map,this.pendingServiceTrees.set(e,i));let r=i.get(n.serviceName);if(r)return r;let a={serviceName:n.tree?.name??n.serviceName,serviceClassName:n.tree?.className??n.serviceClassName,zeroBasedChunkIndex:n.chunkIndex===0,instances:[]};return i.set(n.serviceName,a),a}isLastChunk(e,n,i){return i<=1?!0:e.zeroBasedChunkIndex?n>=i-1:n>=i}buildServiceTree(e,n){let i={name:n.serviceName,className:n.serviceClassName,childCount:0,children:[],syncedAt:new Date().toISOString()};for(let r of n.instances){let a=this.resolveEffectiveSegments(e,r.effectivePath),o=Et(r.originalPath);this.upsertTreeNode(i,a,o,r.className)}return this.recomputeTreeChildCounts(i),i.syncedAt=new Date().toISOString(),i}resolveEffectiveSegments(e,n){return e.tmpIndex?Et(n).map(i=>e.tmpIndex.sanitizeName(i)):Et(n)}rewritePendingEffectivePaths(e,n,i,r){let a=Pe.relative(n,i).split(Pe.sep).filter(l=>l.length>0),o=Pe.relative(n,r).split(Pe.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=nt(["game",...a]),c=nt(["game",...o]);for(let l of e.instances){if(l.effectivePath===s){l.effectivePath=c;continue}(l.effectivePath.startsWith(`${s}.`)||l.effectivePath.startsWith(`${s}[`))&&(l.effectivePath=`${c}${l.effectivePath.slice(s.length)}`)}}upsertTreeNode(e,n,i,r){if(n.length<=1)return;let a=e.children;for(let o=1;o<n.length;o++){let s=n[o],c=i[o],l=o===n.length-1,u=a.find(p=>p.name===s);u?l&&(u.className=r,c!==void 0&&c!==s&&(u.originalName=c)):(u={name:s,className:l?r:"Folder",childCount:0,children:[]},c!==void 0&&c!==s&&(u.originalName=c),a.push(u)),u.children||(u.children=[]),a=u.children}}recomputeTreeChildCounts(e){let n=i=>{let r=i.children??[];i.children=r;for(let a of r)n(a);i.childCount=r.length};for(let i of e.children)n(i);e.childCount=e.children.length}clearTTLTimerForPlace(e,n){e.incompleteSyncTimer&&e.activeFullSyncSessionId===n&&(clearTimeout(e.incompleteSyncTimer),e.incompleteSyncTimer=null)}async cleanupStaleTempDirs(){let e=this.ctx.config.getSyncRoot();try{let n=await pn.readdir(e,{withFileTypes:!0});for(let i of n)if(i.isDirectory()){if(i.name.startsWith("explorer_tmp_")){let r=Pe.join(e,i.name);g.warn("Removing stale temp directory from crashed sync",{dir:i.name});try{await pn.rm(r,{recursive:!0,force:!0})}catch(a){g.error(`Failed to remove stale temp dir: ${i.name}`,a instanceof Error?a:new Error(String(a)))}}if(i.name.startsWith("place_")){let r=Pe.join(e,i.name);try{let a=await pn.readdir(r,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=Pe.join(r,o.name);g.warn("Removing stale temp directory from crashed sync",{dir:`${i.name}/${o.name}`});try{await pn.rm(s,{recursive:!0,force:!0})}catch(c){g.error(`Failed to remove stale temp dir: ${i.name}/${o.name}`,c instanceof Error?c:new Error(String(c)))}}}catch{continue}}}}catch(n){if(n.code==="ENOENT")return;g.warn("Failed to scan for stale temp dirs",{error:n instanceof Error?n.message:String(n)})}}};import tr from"path";import{promises as Xy}from"fs";ce();var Yy=class{constructor(e){this.ctx=e}async handleReversePending(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({pending:0,hasConflicts:!1,lastDetected:null});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}let a={pending:r.fileWatcher?.getPendingCount()??0,hasConflicts:!1,lastDetected:r.fileWatcher?.getLastDetected()??null,forwardRestoreNeeded:r.forwardRestoreQueue.length};this.ctx.touchRuntimePlace(i),n.status(200).json(a)}async handleReverseSyncChanges(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({changes:[],count:0});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}if(r.state!=="syncing"){n.status(400).json({error:"Not syncing",message:"Reverse sync is only available when sync is active"});return}let a=r.fileWatcher?.drainPendingChanges()??[],o=a.length>0?await r.reader.buildChangesFromPending(a):[];this.ctx.touchRuntimePlace(i),n.status(200).json({changes:o,count:o.length})}async handleReverseSyncResult(e,n){let i=e.body,r=i.placeId??this.ctx.getDefaultRuntimePlaceId();if(r==null){n.status(400).json({error:"Validation error",message:"placeId is required (in body or via active sync session)"});return}let a=i.appliedFiles??i.appliedPaths;if(!a||!Array.isArray(a)){n.status(400).json({error:"Validation error",message:"appliedFiles (or appliedPaths) must be an array of relative file paths"});return}let o=this.ctx.places.get(r);if(!o){n.status(404).json({error:"Place not found",message:`No sync context for place ${r}`});return}this.ctx.touchRuntimePlace(r);let s=this.ctx.config.getPlaceRoot(r),c=0,l=[];for(let u of a){let p=tr.resolve(s,u);if(!Vy(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await Xy.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=$t(m),y=Ut(m);y&&h&&await o.writer.removeFromTree(y,h)}c++}else f==="EISDIR"?c++:l.push({path:u,error:d instanceof Error?d.message:String(d)})}}c>0&&await o.index.saveToDisk(),c>0&&o.writer.appendHistory({timestamp:new Date().toISOString(),type:"reverseApply",direction:"reverse",source:"local",path:`place_${r}`,details:`applied:${c} failed:${l.length}`}),n.status(200).json({updated:c,failed:l.length,errors:l})}async handleResolveConflict(e,n){let i=e.body;if(!i.fsPath||!i.resolution){n.status(400).json({error:"Validation error",message:"fsPath and resolution are required"});return}let{fsPath:r,resolution:a}=i,o=this.ctx.config.getSyncRoot();if(!Vy(o,tr.resolve(o,r))){n.status(403).json({error:"Forbidden",message:"Path is outside the sync root"});return}if(a==="skip"){n.status(200).json({status:"skipped",fsPath:r});return}let s;if(i.placeId&&(s=this.ctx.places.get(i.placeId)),!s){let d=r.match(/^place_(\d+)(?:_[^/]+)?\//);if(d){let f=parseInt(d[1],10);s=this.ctx.places.get(f)}else s=Array.from(this.ctx.places.values())[0]}if(!s){n.status(404).json({error:"No active place context",message:"No sync session is active. Start a sync first."});return}let c=this.ctx.config.getPlaceRoot(s.placeId),l=tr.resolve(c,r);if(!Vy(c,l)){n.status(403).json({error:"Forbidden",message:"Path is outside the place root"});return}let u=s.index,p=s.reader;if(a==="apply-studio"){if(typeof i.studioContent!="string"){n.status(400).json({error:"Validation error",message:"studioContent is required for apply-studio resolution"});return}await Xy.mkdir(tr.dirname(l),{recursive:!0});let d=i.studioContent,f=u.computeHash(d);s.fileWatcher?.suppressPath(l,f),await Xy.writeFile(l,d,"utf-8"),u.updateHashByValue(l,f),u.updateFileHashByValue(l,f),await u.saveToDisk(),n.status(200).json({status:"resolved",resolution:"apply-studio",fsPath:r});return}if(a==="apply-file"){let d=await Xy.readFile(l,"utf-8"),f=u.computeHash(d);u.resolveFile(l,"apply-file",f),await u.saveToDisk();let m=p.getFileType(l),h=p.resolveInstancePathFromFile(l);n.status(200).json({status:"resolved",resolution:"apply-file",fsPath:r,instancePath:h,fileType:m,content:d});return}n.status(400).json({error:"Invalid resolution",message:`Unknown resolution: ${a}`})}async handleReverseRescan(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({added:0});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}if(!r.fileWatcher){n.status(200).json({added:0});return}let a=await r.fileWatcher.rescan();this.ctx.touchRuntimePlace(i),g.info("Reverse rescan completed",{placeId:i,added:a}),n.status(200).json({added:a})}resolveInstancePathForAppliedPath(e,n){let i=e.resolveInstancePathFromFsPath(n);if(i)return i;let r=e.getExplorerRoot(),a=tr.relative(r,n);if(a.startsWith("..")||a===""||tr.isAbsolute(a))return null;let o=a.split(tr.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=tr.basename(n),c=tr.dirname(n),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(Cy.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=r;for(let f of o){d=tr.join(d,f);let m=tr.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return nt(p)}};ce();function W2(t){if(!t||typeof t!="object")return;let e=t;if(Array.isArray(e.instances))for(let n of e.instances)Array.isArray(n.properties)&&n.properties.length===0&&(n.properties={}),Array.isArray(n.attributes)&&n.attributes.length===0&&(n.attributes={});if(Array.isArray(e.changes))for(let n of e.changes)Array.isArray(n.properties)&&n.properties.length===0&&(n.properties={}),Array.isArray(n.attributes)&&n.attributes.length===0&&(n.attributes={})}function Doe(t){return t.type==="instanceAdded"||t.type==="instanceRemoved"||t.type==="instanceRenamed"||t.type==="instanceMoved"||t.type==="scriptSourceChanged"}var Ir=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;reverseDetectorFactory;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;currentConnectedPlaceId=null;currentConnectedPlaceName=null;constructor(e,n={}){this.config=new Sy(e,n),this.reverseDetectorFactory=n.reverseDetectorFactory??(i=>new By(i.explorerRoot,i.syncIndex,{placeId:i.placeId})),this.apiHandler=new Jy(this),this.changeProcessor=new Wy(this),this.initHandler=new Ky(this),this.reverseHandler=new Yy(this),this.places=new Fy({max:3,dispose:(i,r)=>{g.info("Disposing place context (LRU eviction)",{placeId:r}),this.activeFullSyncPlaceId===r&&(this.activeFullSyncPlaceId=null),i.fileWatcher&&(g.info("Disposing place context: stopping reverse detector",{placeId:r,reason:"lru-dispose",mode:Ye}),i.fileWatcher.stop("lru-dispose").catch(a=>{g.error("Error stopping reverse detector during dispose",a)}),i.fileWatcher=null),i.writer.stopChangeLogFlusher().catch(a=>{g.error("LRU dispose \uC911 changelog flush \uC2E4\uD328",a)}),i.incompleteSyncTimer&&(clearTimeout(i.incompleteSyncTimer),i.incompleteSyncTimer=null),i.index.clearAllMaps(),i.index.saveToDisk().catch(a=>{g.error("Error saving index during dispose",a)}),i.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(i.activeFullSyncSessionId),i.tmpWriter&&(i.tmpWriter.stopChangeLogFlusher(),i.tmpWriter=null),i.tmpIndex=null,i.collisionDirMap=null}})}getSyncRoot(){return this.config.getSyncRoot()}async getOrCreatePlaceContext(e,n){let i=this.places.get(e);if(i&&n){let r=this.config.getPlaceRoot(e),a=await this.config.resolvePlaceRoot(e,n);a!==r&&(g.info("Place root migrated, recreating context",{placeId:e,from:r,to:a}),this.places.delete(e),i=void 0)}if(!i){let r=await this.config.resolvePlaceRoot(e,n),a=_I.join(r,"explorer");await bc.mkdir(r,{recursive:!0}),await bc.mkdir(a,{recursive:!0});let o=new Zi(r,a);await o.loadFromDisk();let s=new gc(this.config,o,e),c=new qy(this.config,o,r);s.startChangeLogFlusher(),i={placeId:e,placeName:"",index:o,writer:s,reader:c,fileWatcher:null,state:"idle",activeClientId:null,activeFullSyncSessionId:null,instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,tmpIndex:null,tmpWriter:null,incompleteSyncTimer:null,changesSinceLastSave:0,directions:{...Wa},applyModes:{...Ga},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null},this.places.set(e,i),g.info("Created new place context",{placeId:e,placeRoot:r})}return i}async handleSyncInit(e,n){try{W2(e.body);let i=Z2(e.body);if(!i.success){n.status(400).json({error:"Validation error",message:i.error});return}let r=i.data,a=r.phase==="start"?r.placeId??null:r.phase==="chunk"||r.phase==="complete"?this.activeFullSyncPlaceId:null;if(a==null){n.status(400).json({error:"Missing placeId",message:"placeId is required in start phase or must be set by previous start"});return}switch(r.phase){case"start":this.setCurrentConnectedPlace(a,r.placeName),await this.initHandler.handleInitStart(a,r,n);break;case"chunk":await this.initHandler.handleInitChunk(a,r,n);break;case"complete":await this.initHandler.handleInitComplete(a,r,n);break}}catch(i){this.sendError(n,i,"handleSyncInit")}}async handleSyncUpdate(e,n){try{W2(e.body);let i=H2(e.body);if(!i.success){n.status(400).json({error:"Validation error",message:i.error});return}let r=i.data,a=r.placeId??this.getDefaultRuntimePlaceId();if(a==null){n.status(400).json({error:"Missing placeId",message:"placeId is required in body or must have an active sync session"});return}let o=await this.getOrCreatePlaceContext(a);if(this.setCurrentConnectedPlace(a,o.placeName||this.currentConnectedPlaceName),o.activeFullSyncSessionId!==null||o.state==="initializing"){n.status(409).json({error:"Conflict",message:`Full sync in progress for place ${a}`});return}o.activeClientId=r.clientId,this.touchRuntimePlace(a);let s=F2(o,r.changes);g.info("Sync update received",{placeId:a,clientId:r.clientId,changeCount:s.length,receivedCount:r.changes.length,types:s.map(m=>m.type)});let c=[],l=[],u=0,p=!1,d=new Map;for(let m of s)try{let h=await this.changeProcessor.processChangeForPlace(o,m,d);h?l.push(h):(u++,Doe(m)&&(p=!0))}catch(h){let y="path"in m?m.path:"oldPath"in m?m.oldPath:"unknown";c.push({path:y,error:h instanceof Error?h.message:String(h)})}for(let m of d.values())try{await Zy(o,m)}catch(h){c.push({path:m.instancePath,error:h instanceof Error?h.message:String(h)})}if(p)try{let m=this.config.getSyncRoot(),h=this.config.getPlaceRoot(a);await Py(m,h),await ic(m)}catch(m){g.warn("Failed to refresh sourcemaps after incremental sync update",{placeId:a,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=B2&&(await o.index.saveToDisk(),o.changesSinceLastSave=0),o.lastIncrementalSync=new Date().toISOString();let f={processed:u,failed:c.length,errors:c,syncedAt:o.lastIncrementalSync};l.length>0&&(f.conflicts=l),n.status(200).json(f)}catch(i){this.sendError(n,i,"handleSyncUpdate")}}getDefaultRuntimePlaceId(){return this.currentConnectedPlaceId}getDefaultRuntimePlaceName(){if(this.currentConnectedPlaceName)return this.currentConnectedPlaceName;if(this.currentConnectedPlaceId!==null&&this.currentConnectedPlaceId!==void 0){let e=this.places.get(this.currentConnectedPlaceId);if(e?.placeName)return e.placeName}return null}setCurrentConnectedPlace(e,n){if(this.currentConnectedPlaceId=e,e==null){this.currentConnectedPlaceName=null;return}if(typeof n=="string"&&n.length>0){this.currentConnectedPlaceName=n;let r=this.places.get(e);r&&(r.placeName=n);return}let i=this.places.get(e);i?.placeName&&(this.currentConnectedPlaceName=i.placeName)}clearCurrentConnectedPlaceIfMatch(e){this.currentConnectedPlaceId===e&&(this.currentConnectedPlaceId=null,this.currentConnectedPlaceName=null)}touchRuntimePlace(e){this.activeRuntimeSyncPlaceId=e}clearRuntimePlaceIfMatch(e){this.activeRuntimeSyncPlaceId===e&&(this.activeRuntimeSyncPlaceId=null)}resolveQueryPlaceId(e,n="runtime"){let i=e.query.placeId;if(i){let r=parseInt(i,10);if(!isNaN(r))return r}return n==="full"?this.activeFullSyncPlaceId:this.getDefaultRuntimePlaceId()}async handleSyncStatus(e,n){try{let i=u=>({state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:u,activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0,reverseDetectorActive:!1,reverseDetectorMode:Ye,fileWatcherActive:!1}),r=this.resolveQueryPlaceId(e);if(r==null){let u=i(this.config.getSyncRoot());n.status(200).json(u);return}let a=this.places.get(r);if(!a){let u=i(this.config.getPlaceRoot(r));n.status(200).json(u);return}let o=a.fileWatcher?.getPendingCount()??0,s=a.fileWatcher?.isActivelyScanning()??!1,c=a.fileWatcher!==null;this.touchRuntimePlace(r);let l={state:a.state,instanceCount:a.instanceCount,scriptCount:a.scriptCount,lastFullSync:a.lastFullSync,lastIncrementalSync:a.lastIncrementalSync,syncRoot:this.config.getPlaceRoot(r),activeClientId:a.activeClientId,reverseSyncAvailable:o>0,modifiedFileCount:o,applyModes:a.applyModes,directions:a.directions,reverseDetectorActive:s,reverseDetectorMode:Ye,fileWatcherActive:c,forwardOnlyClasses:[...rc]};n.status(200).json(l)}catch(i){this.sendError(n,i,"handleSyncStatus")}}async handleSyncStop(e,n){try{let i=e.body,r=i.placeId??this.getDefaultRuntimePlaceId();if(g.info("handleSyncStop requested",{placeId:r,clientId:i.clientId??null,reason:i.reason??"requested"}),r==null){n.status(200).json({status:"idle",state:"idle",placeId:null,message:"No active sync place"});return}let a=this.places.get(r);if(!a){g.info("handleSyncStop no place context",{placeId:r,clientId:i.clientId??null}),n.status(200).json({status:"idle",state:"idle",placeId:r,message:`No sync context for place ${r}`});return}if(i.clientId&&a.activeClientId&&i.clientId!==a.activeClientId&&(a.activeFullSyncSessionId!==null||a.state==="syncing"||a.state==="initializing")){g.warn("handleSyncStop rejected due to active client mismatch",{placeId:r,requestedClientId:i.clientId,activeClientId:a.activeClientId,state:a.state,activeFullSyncSessionId:a.activeFullSyncSessionId}),n.status(409).json({error:"Conflict",message:`This sync session belongs to client ${a.activeClientId}`});return}let o=a.activeFullSyncSessionId,s=a.state,c=a.fileWatcher!==null;if(o&&(this.initHandler.clearPreserveLocalFiles(o),this.initHandler.clearPendingServiceTrees(o)),a.fileWatcher&&(g.info("handleSyncStop suspending reverse detector",{placeId:r,state:a.state,activeClientId:a.activeClientId,mode:Ye}),a.fileWatcher.beginFullSyncReplacement()),a.incompleteSyncTimer&&(clearTimeout(a.incompleteSyncTimer),a.incompleteSyncTimer=null),o){let l=this.config.getPlaceRoot(r),u=_I.join(l,`explorer_tmp_${o}`);await bc.rm(u,{recursive:!0,force:!0}).catch(()=>{})}a.tmpWriter&&(a.tmpWriter.stopChangeLogFlusher(),a.tmpWriter=null),a.tmpIndex=null,a.collisionDirMap=null,a.activeFullSyncSessionId=null,a.syncProgress=null,a.state="idle",a.activeClientId=null,a.instanceCount=0,a.scriptCount=0,a.forwardRestoreQueue=[],this.activeFullSyncPlaceId===r&&(this.activeFullSyncPlaceId=null),this.clearRuntimePlaceIfMatch(r),g.info("handleSyncStop responding idle",{placeId:r,previousState:s,activeFullSyncSessionId:o,hadWatcher:c}),g.info("Sync stopped",{placeId:r,reason:i.reason??"requested"}),n.status(200).json({status:"stopped",state:"idle",placeId:r})}catch(i){this.sendError(n,i,"handleSyncStop")}}async initialize(){try{await this.config.loadFromMeta(),await this.initHandler.cleanupStaleTempDirs(),g.info("SyncController initialized")}catch(e){g.error("SyncController initialization failed",e instanceof Error?e:new Error(String(e)))}}async shutdown(){try{g.info("SyncController shutdown: clearing place contexts",{placeCount:this.places.size}),this.places.clear(),g.info("SyncController shut down")}catch(e){g.error("SyncController shutdown error",e instanceof Error?e:new Error(String(e)))}}async atomicWriteFile(e,n){let i=e+".tmp."+Noe().slice(0,8);try{await bc.writeFile(i,n,"utf-8"),await bc.rename(i,e)}catch(r){throw await bc.unlink(i).catch(()=>{}),r}}async handlePreCheck(e,n){try{await this.apiHandler.handlePreCheck(e,n)}catch(i){this.sendError(n,i,"handlePreCheck")}}async handleSyncDirections(e,n){try{await this.apiHandler.handleSyncDirections(e,n)}catch(i){this.sendError(n,i,"handleSyncDirections")}}async handleForwardRestoreList(e,n){try{await this.apiHandler.handleForwardRestoreList(e,n)}catch(i){this.sendError(n,i,"handleForwardRestoreList")}}async handleReversePending(e,n){try{await this.reverseHandler.handleReversePending(e,n)}catch(i){this.sendError(n,i,"handleReversePending")}}async handleReverseSyncChanges(e,n){try{await this.reverseHandler.handleReverseSyncChanges(e,n)}catch(i){this.sendError(n,i,"handleReverseSyncChanges")}}async handleReverseSyncResult(e,n){try{await this.reverseHandler.handleReverseSyncResult(e,n)}catch(i){this.sendError(n,i,"handleReverseSyncResult")}}async handleResolveConflict(e,n){try{await this.reverseHandler.handleResolveConflict(e,n)}catch(i){this.sendError(n,i,"handleResolveConflict")}}async handleReverseRescan(e,n){try{await this.reverseHandler.handleReverseRescan(e,n)}catch(i){this.sendError(n,i,"handleReverseRescan")}}async handleSyncHistory(e,n){try{await this.apiHandler.handleSyncHistory(e,n)}catch(i){this.sendError(n,i,"handleSyncHistory")}}async startFileWatcherForPlace(e){if(e.state!=="syncing"){g.debug("Skipping reverse detector start - place not syncing",{placeId:e.placeId,state:e.state});return}if(e.fileWatcher){if(e.fileWatcher.isActivelyScanning()){g.info("startFileWatcherForPlace: reusing active reverse detector",{placeId:e.placeId,mode:Ye});return}g.info("startFileWatcherForPlace: resuming paused reverse detector",{placeId:e.placeId,mode:Ye}),e.fileWatcher.endFullSyncReplacement();return}let n=this.createReverseChangeDetector(e);this.configureReverseChangeDetector(e,n),e.fileWatcher=n,n.start().then(()=>{e.fileWatcher===n&&g.info("Reverse detector started for reverse sync",{placeId:e.placeId,mode:Ye})}).catch(i=>{g.warn("Failed to start reverse detector",{placeId:e.placeId,mode:Ye,error:i instanceof Error?i.message:String(i)}),e.fileWatcher===n&&(e.fileWatcher=null)})}createReverseChangeDetector(e){return this.reverseDetectorFactory({explorerRoot:_I.join(this.config.getPlaceRoot(e.placeId),"explorer"),syncIndex:e.index,placeId:e.placeId})}configureReverseChangeDetector(e,n){e.writer.setOnWriteCallback(i=>{n.suppressPath(i)}),n.setDirectionChecker(i=>{let r=_L(i);return e.directions[r]}),n.setOnForwardViolation(i=>{e.forwardRestoreQueue.includes(i)||(e.forwardRestoreQueue.push(i),g.info("Forward violation queued for restore",{placeId:e.placeId,relativePath:i,queueSize:e.forwardRestoreQueue.length}))})}getStatusSummary(){return this.apiHandler.getStatusSummary()}getDirectionForCategory(e){return this.apiHandler.getDirectionForCategory(e)}getStatusDirect(e){return this.apiHandler.getStatusDirect(e)}async getHistoryDirect(e,n){return this.apiHandler.getHistoryDirect(e,n)}getDirectionsDirect(e){return this.apiHandler.getDirectionsDirect(e)}getProgressDirect(e){return this.apiHandler.getProgressDirect(e)}async readSyncedFile(e,n){return this.apiHandler.readSyncedFile(e,n)}async writeSyncedFile(e,n,i){await this.apiHandler.writeSyncedFile(e,n,i)}async executeViaDisk(e,n){return this.apiHandler.executeViaDisk(e,n)}sendError(e,n,i){let r=n instanceof Error?n.message:String(n);if(r.includes("Path traversal detected")){e.status(403).json({error:"Forbidden",message:r});return}let a=n.code;if(a==="ENOSPC"||a==="EPERM"||a==="EACCES"){e.status(500).json({error:"Disk error",message:r});return}g.error(`SyncController.${i} failed`,n instanceof Error?n:new Error(r)),e.status(500).json({error:"Internal error",message:`${i}: ${r}`})}};import{randomUUID as joe}from"crypto";function G2(t,e){let n=joe(),i=n.replace(/-/g,"").substring(0,8).toUpperCase(),r=`${i.substring(0,4)}-${i.substring(4,8)}`;return{config:t,app:e,instanceId:n,sessionId:r,startTime:Date.now(),baseUrl:`http://${t.httpHost}:${t.httpPort}`,commandQueue:new Map,pendingCommands:new Map,globalPendingCommands:[],totalCommandsProcessed:0,pluginClients:new Map,pluginCommandSessionsByClientId:new Map,pluginCommandClientIdsByProcessToken:new Map,mcpInstances:new Map,cachedSelectionMap:new Map,isClientMode:!1,clientModeHealthTimer:null,clientModeConsecutiveHealthFailures:0,clientModeUpstreamReachable:!0,clientModeUpstreamContextCaptureEnabled:!0,clientModeLastHealthSuccessAt:null,clientModeLastHealthFailureAt:null,clientModeLastHealthError:null,onUpstreamPermanentlyDown:null,clientModeIdleTimeoutMs:t.clientModeIdleTimeoutMs??36e5,clientModeIdleWatchdogTimer:null,shutdownFn:null,historyManager:null,analyticsManager:null,executionContextManager:null,licenseState:null,syncController:null,internalCommandExecutor:null,dashboardFolderPicker:null,activeSyncOwnerInstanceId:null,activeProjectRoot:null,pendingDashboardSyncRootPin:null,playtestControlCommand:null,dashboardSyncRootSwitchInFlight:!1,aiClientName:"",pluginVersion:"",syncedSessionToken:null,serverLastCommandAt:null}}var J2=Rt(tc(),1);ce();function K2(t){let e=J2.default.json({limit:"5mb"});t.app.use((n,i,r)=>{if(n.path.startsWith("/sync/")){r();return}e(n,i,r)}),t.app.use((n,i,r)=>{i.setHeader("Access-Control-Allow-Origin","http://localhost:3002"),i.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),i.setHeader("Access-Control-Allow-Headers","Content-Type"),r()}),t.app.use((n,i,r)=>{g.debug(`${n.method} ${n.path}`,{ip:n.ip}),r()})}_p();import{randomUUID as f6}from"crypto";ce();function Aoe(){let t=process.env.WEPPY_ROBLOX_MCP_VERSION?.trim();return t||null}var Qe=Aoe()??"2.6.1";jp();function tv(t){let e=new Set;t.aiClientName&&e.add(t.aiClientName);for(let n of t.mcpInstances.values())n.aiClientName&&e.add(n.aiClientName);return Array.from(e)}function n6(t){let n=Date.now();for(let[i,r]of t.mcpInstances)r.lastSeen&&n-r.lastSeen>15e3&&(t.mcpInstances.delete(i),r.sessionId&&t.executionContextManager?.endSession(r.sessionId),g.debug("Removed stale MCP instance",{instanceId:i,lastSeen:r.lastSeen}))}function r6(t,e,n){let i=e.query.clientId;if(i&&t.pluginClients.has(i)){let r=t.pluginClients.get(i);r.lastSeen=Date.now()}try{let r=e.body;if(!r||!Array.isArray(r.selection)||typeof r.count!="number"){g.warn("Invalid selection update request",{body:r}),n.status(400).json({error:"Invalid request body"});return}let a=i||"unknown",o=Date.now();t.cachedSelectionMap.set(a,{selection:r.selection,count:r.count,timestamp:o,clientId:a}),g.debug("Selection cache updated",{count:r.count,clientId:a,timestamp:o}),n.json({status:"ok",timestamp:o})}catch(r){g.error("Error handling selection update",r),n.status(500).json({error:"Internal server error"})}}function i6(t,e,n){let i=parseInt(e.query.maxAge)||3e4,r=Ap(t,i);r?n.json({cached:!0,...r}):n.json({cached:!1,message:"No cached selection available"})}function Ap(t,e=3e4,n){if(t.cachedSelectionMap.size===0)return null;let i;if(n)i=t.cachedSelectionMap.get(n);else for(let a of t.cachedSelectionMap.values())(!i||a.timestamp>i.timestamp)&&(i=a);if(!i)return null;let r=Date.now()-i.timestamp;return e===0||r<=e?i:null}function $I(t,e){let n=t.pluginClients.get(e.clientId),i=Date.now(),r={clientId:e.clientId,projectName:e.projectName,placeName:e.placeName,placeId:e.placeId,pluginVersion:e.pluginVersion,connectedAt:n?.connectedAt||i,lastSeen:i,commandsProcessed:n?.commandsProcessed||0,connectionType:n?.connectionType||"polling",inFlightRequestId:n?.inFlightRequestId,...n?.commandSessionState?{commandSessionState:n.commandSessionState}:{},...n?.processToken?{processToken:n.processToken}:{}};return t.pluginClients.set(e.clientId,r),t.syncController&&typeof t.syncController.setCurrentConnectedPlace=="function"&&typeof e.placeId=="number"&&Number.isFinite(e.placeId)&&t.syncController.setCurrentConnectedPlace(e.placeId,e.placeName??null),t.pendingCommands.has(e.clientId)||t.pendingCommands.set(e.clientId,[]),e.pluginVersion&&(t.pluginVersion=e.pluginVersion),typeof r.placeId=="number"&&Number.isFinite(r.placeId)&&tI(t,r.placeId,r.placeName??null),t.analyticsManager&&(e.pluginVersion&&t.analyticsManager.setPluginVersion(e.pluginVersion),t.analyticsManager.trackPluginConnected()),g.info("Plugin client registered",{clientId:e.clientId,projectName:e.projectName,placeName:e.placeName,isReconnect:!!n}),{clientInfo:r,sessionId:t.sessionId,mcpVersion:Qe,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:tv(t)}}function TI(t,e){let n=t.pluginClients.get(e),i=t.pluginClients.has(e);if(t.pluginClients.delete(e),t.pendingCommands.delete(e),i&&t.syncController&&typeof t.syncController.clearCurrentConnectedPlaceIfMatch=="function"&&typeof n?.placeId=="number"&&Number.isFinite(n.placeId)){let r=[...t.pluginClients.values()].filter(a=>typeof a.placeId=="number"&&Number.isFinite(a.placeId)).sort((a,o)=>o.lastSeen-a.lastSeen)[0];r?t.syncController.setCurrentConnectedPlace?.(r.placeId??null,r.placeName??null):t.syncController.clearCurrentConnectedPlaceIfMatch(n.placeId)}return i&&(tt(t,"connection",{clientId:e,status:"disconnected"}),lc(t,{timestamp:new Date().toISOString(),type:"plugin",status:"disconnected",clientId:e,message:`Plugin disconnected \u2014 ${e}`})),g.info("Plugin client unregistered",{clientId:e,existed:i}),i}function nv(t,e,n){try{let i=e.body;if(!i.clientId){n.status(400).json({error:"Missing clientId"});return}let r=$I(t,{clientId:i.clientId,projectName:i.projectName,placeName:i.placeName,placeId:i.placeId,pluginVersion:i.pluginVersion});n.json({status:"ok",clientId:i.clientId,serverInstanceId:t.instanceId,sessionId:r.sessionId,mcpVersion:r.mcpVersion,connectedAt:r.clientInfo.connectedAt,aiClientNames:r.aiClientNames,serverStartTime:t.startTime,mcpInstanceCount:r.mcpInstanceCount})}catch(i){g.error("Error registering plugin client",i),n.status(500).json({error:"Internal server error"})}}function rv(t,e,n){let i=e.body?.clientId;if(!i){n.status(400).json({error:"Missing clientId"});return}let r=TI(t,i);n.json({status:"ok",existed:r})}function a6(t,e,n){try{let i=e.body;if(!i.instanceId){n.status(400).json({error:"Missing instanceId"});return}let r=Date.now(),a={instanceId:i.instanceId,...typeof i.sessionId=="string"?{sessionId:i.sessionId}:{},pid:i.pid,connectedAt:r,isServer:!1,lastSeen:r};i.aiClientName&&(a.aiClientName=i.aiClientName),i.cwd&&(a.cwd=i.cwd),"projectRoot"in i&&(a.projectRoot=i.projectRoot),t.mcpInstances.set(i.instanceId,a),tt(t,"mcp_status",{aiClientName:a.aiClientName??"Unknown",instanceId:i.instanceId,status:"registered"}),lc(t,{timestamp:new Date().toISOString(),type:"mcp",status:"registered",instanceId:i.instanceId,message:`MCP registered \u2014 ${a.aiClientName??i.instanceId}`,...a.aiClientName?{aiClientName:a.aiClientName}:{}}),g.info("MCP instance registered (client mode)",{instanceId:i.instanceId,pid:i.pid,cwd:i.cwd}),n.json({status:"ok",instanceId:i.instanceId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Qe,mcpInstanceCount:t.mcpInstances.size+1})}catch(i){g.error("Error registering MCP instance",i),n.status(500).json({error:"Internal server error"})}}function o6(t,e,n){let i=e.body?.instanceId;if(!i){n.status(400).json({error:"Missing instanceId"});return}let r=t.mcpInstances.get(i),a=!!r;t.mcpInstances.delete(i),r?.sessionId&&t.executionContextManager?.endSession(r.sessionId),a&&(tt(t,"mcp_status",{aiClientName:r?.aiClientName??"Unknown",instanceId:i,status:"unregistered"}),lc(t,{timestamp:new Date().toISOString(),type:"mcp",status:"unregistered",instanceId:i,message:`MCP unregistered \u2014 ${r?.aiClientName??i}`,...r?.aiClientName?{aiClientName:r.aiClientName}:{}})),g.info("MCP instance unregistered",{instanceId:i,existed:a}),n.json({status:"ok",existed:a})}function s6(t,e,n){let{instanceId:i,aiClientName:r}=e.body;if(i&&r){let a=t.mcpInstances.get(i);a&&(a.aiClientName=r)}n.json({status:"ok"})}function c6(t){let e=3e4,n=Date.now(),i=Xn({appDataDir:t.config.appDataDir});for(let[r,a]of t.pluginClients)n-a.lastSeen>e&&(t.pluginClients.delete(r),t.pendingCommands.delete(r));return n6(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Qe,uptime:n-t.startTime,serverStartTime:t.startTime,serverExecutable:process.execPath,serverHost:t.config.httpHost,serverPort:t.config.httpPort,serverPid:process.pid,mcpInstances:[{instanceId:t.instanceId,pid:process.pid,connectedAt:t.startTime,isServer:!0,cwd:process.cwd(),projectRoot:i,lastSeen:n,...t.aiClientName?{aiClientName:t.aiClientName}:{}},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function iv(t,e){e.json(c6(t))}function av(t,e,n){let i=e.query.instanceId;i&&t.mcpInstances.has(i)&&(t.mcpInstances.get(i).lastSeen=Date.now()),n6(t);let r=Ja(t),s={...{status:"online",connectedClients:r.length,queuedCommands:n2(t),uptime:Date.now()-t.startTime,version:Qe,enableContextCapture:t.executionContextManager?.isEnabled()??t.config.enableContextCapture??!0,isClientMode:t.isClientMode,pid:process.pid,sessionId:t.sessionId},instanceId:t.instanceId,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:tv(t),pluginVersion:t.pluginVersion||void 0,dashboardSseClients:t.dashboardSseClients?.size??0,websocketClients:r.length,pluginClients:r.map(c=>({clientId:c.clientId,projectName:c.projectName,placeName:c.placeName,pluginVersion:c.pluginVersion,connectionType:c.connectionType,commandSessionState:c.commandSessionState,lastSeen:c.lastSeen})),...t.isClientMode?{upstream:{reachable:t.clientModeUpstreamReachable,consecutiveFailures:t.clientModeConsecutiveHealthFailures,lastSuccessAt:t.clientModeLastHealthSuccessAt,lastFailureAt:t.clientModeLastHealthFailureAt,lastError:t.clientModeLastHealthError,baseUrl:t.baseUrl}}:{}};n.json(s)}function l6(t,e,n,i){let r=e.ip||e.socket.remoteAddress||"";if(!(r==="127.0.0.1"||r==="::1"||r==="::ffff:127.0.0.1"||r==="localhost")){g.warn("Shutdown request rejected from non-localhost",{ip:r}),n.status(403).json({error:"Forbidden: localhost only"});return}g.info("Shutdown request received, initiating graceful shutdown",{requestedBy:r,uptime:Date.now()-t.startTime}),n.json({status:"shutting_down",message:"Server will shutdown gracefully",pid:process.pid}),setTimeout(async()=>{try{await i(),g.info("Graceful shutdown completed"),process.exit(0)}catch(o){g.error("Error during graceful shutdown",o),process.exit(1)}},100)}async function ov(t){if(t.isClientMode)try{let e=await fetch(`${t.baseUrl}/connection-info`);if(e.ok)return await e.json()}catch(e){g.warn("Failed to fetch connection info from server",{error:e})}return c6(t)}ce();Op();var Sc={query_instances:{discriminator:"action",validActions:new Set(["get","children","find_child","find_descendant","wait_for_child","class_info","search_name","search_class","search_property","search_tag","file_tree","project_structure","descendants","ancestors"]),paramAliases:{query_instances_search_name:{query:"pattern"},query_instances_search_property:{root:"rootPath"},query_instances_search_tag:{root:"rootPath"},query_instances_project_structure:{root:"rootPath"}}},mutate_instances:{discriminator:"action",validActions:new Set(["create","create_with_props","delete","clone","move","rename","pivot","create_tree","mass_create","mass_delete","mass_duplicate","smart_duplicate"]),paramAliases:{mutate_instances_clone:{path:"sourcePath"}}},manage_properties:{discriminator:"action",validActions:new Set(["get","set","get_all","set_multiple","get_attr","set_attr","get_all_attrs","delete_attr","add_tag","remove_tag","check_tag","get_tags","get_tagged","set_calculated","set_relative","mass_set","mass_get","modify_children"]),paramAliases:{manage_properties_get_tagged:{tagName:"tag",root:"rootPath"},manage_properties_set_relative:{amount:"value"}}},manage_scripts:{discriminator:"action",validActions:new Set(["get_source","set_source","create","delete","edit_replace","edit_insert","edit_delete","search","replace","get_dependencies"]),paramAliases:{manage_scripts_edit_replace:{newLines:"newContent"},manage_scripts_edit_insert:{lines:"content"},manage_scripts_replace:{pattern:"searchPattern"}}},manage_lighting:{discriminator:"action",validActions:new Set(["lighting","atmosphere","sky","terrain_props","time"])},manage_selection:{discriminator:"action",validActions:new Set(["get","set","clear","cached","context","details","add","remove","watch"])},manage_camera:{discriminator:"action",validActions:new Set(["info","focus_path","focus_position","suggest","screenshot"]),paramAliases:{manage_camera_suggest:{path:"targetPath"}}},manage_tween:{discriminator:"action",validActions:new Set(["create","play","pause","cancel"])},manage_audio:{discriminator:"action",validActions:new Set(["play","stop","pause","resume","set_listener"])},manage_animation:{discriminator:"action",validActions:new Set(["load","play","stop","get_tracks"])},manage_physics:{discriminator:"action",validActions:new Set(["register_group","set_collidable","get_groups"])},manage_effects:{discriminator:"action",validActions:new Set(["emit","clear","toggle"])},manage_terrain:{discriminator:"action",validActions:new Set(["fill_block","fill_ball","fill_cylinder","fill_wedge","clear_region","clear_bounds","replace_material","colors_get","colors_set","read_voxel","read_voxels","write_voxels","generate","smooth"])},spatial_query:{discriminator:"action",validActions:new Set(["raycast","find_ground","check_placement","multi_raycast","scan_area","find_flat","find_spawn","analyze_walkable","spatial_map","find_space","bounds","snap_grid","collision"]),paramAliases:{spatial_query_spatial_map:{path:"rootPath"}}},manage_assets:{discriminator:"action",validActions:new Set(["insert","info","search","search_insert","insert_free","insert_package","export"]),paramAliases:{manage_assets_search:{maxResults:"limit"}}},manage_sync:{discriminator:"action",validActions:new Set(["status_current_place","history","directions","read_file","write_file","progress"])},workspace_state:{discriminator:"action",validActions:new Set(["sync","snapshot","changes","viewport","clear_history","metadata","scripts","selection_info","clear_cache"])},manage_logs:{discriminator:"action",validActions:new Set(["get","clear","errors"]),paramAliases:{manage_logs_get:{level:"type"}}},system_info:{discriminator:"action",validActions:new Set(["ping","connection","usage","place_info","services","studio_settings","play","stop","pause","resume","play_status","run_test"])}};var Loe={manage_selection_cached:"internal",manage_sync_status_current_place:"internal",manage_sync_history:"internal",manage_sync_directions:"internal",manage_sync_read_file:"internal",manage_sync_write_file:"internal",manage_sync_progress:"internal",system_info_connection:"internal",system_info_run_test:"internal"};function sv(t){return Loe[t]||"plugin"}var u6=new Set(["batch_execute","execute_luau","extended_call_method","extended_call_methods","extended_get_properties","extended_get_property","extended_set_properties","extended_set_property","manage_animation_get_tracks","manage_animation_load","manage_animation_play","manage_animation_stop","manage_assets_export","manage_assets_info","manage_assets_insert","manage_assets_insert_free","manage_assets_insert_package","manage_assets_search","manage_assets_search_insert","manage_audio_pause","manage_audio_play","manage_audio_resume","manage_audio_set_listener","manage_audio_stop","manage_camera_screenshot","manage_effects_clear","manage_effects_emit","manage_effects_toggle","manage_lighting_atmosphere","manage_lighting_lighting","manage_lighting_sky","manage_lighting_terrain_props","manage_lighting_time","manage_physics_get_groups","manage_physics_register_group","manage_physics_set_collidable","manage_properties_mass_get","manage_properties_mass_set","manage_properties_modify_children","manage_properties_set_calculated","manage_properties_set_relative","manage_scripts_replace","manage_selection_add","manage_selection_context","manage_selection_details","manage_selection_remove","manage_selection_watch","manage_sync_directions","manage_sync_history","manage_sync_progress","manage_sync_read_file","manage_sync_status_current_place","manage_sync_write_file","manage_terrain_clear_bounds","manage_terrain_clear_region","manage_terrain_colors_get","manage_terrain_colors_set","manage_terrain_fill_ball","manage_terrain_fill_block","manage_terrain_fill_cylinder","manage_terrain_fill_wedge","manage_terrain_generate","manage_terrain_read_voxel","manage_terrain_read_voxels","manage_terrain_replace_material","manage_terrain_smooth","manage_terrain_write_voxels","manage_tween_cancel","manage_tween_create","manage_tween_pause","manage_tween_play","mutate_instances_create_tree","mutate_instances_mass_create","mutate_instances_mass_delete","mutate_instances_mass_duplicate","mutate_instances_smart_duplicate","query_instances_ancestors","query_instances_descendants","query_instances_file_tree","query_instances_project_structure","query_instances_search_property","query_instances_search_tag","spatial_query_analyze_walkable","spatial_query_bounds","spatial_query_check_placement","spatial_query_collision","spatial_query_find_flat","spatial_query_find_ground","spatial_query_find_space","spatial_query_find_spawn","spatial_query_multi_raycast","spatial_query_raycast","spatial_query_scan_area","spatial_query_snap_grid","spatial_query_spatial_map","system_info_pause","system_info_place_info","system_info_play","system_info_play_status","system_info_resume","system_info_run_test","system_info_services","system_info_stop","system_info_studio_settings","workspace_state_changes","workspace_state_clear_cache","workspace_state_clear_history","workspace_state_metadata","workspace_state_scripts","workspace_state_selection_info","workspace_state_snapshot","workspace_state_sync","workspace_state_viewport"]),RI=new Set(["system_info_connection","system_info_ping","system_info_usage"]);function rr(t){return u6.has(t)?"pro":"basic"}function Uoe(t){if(t===null||typeof t!="object")return;let e=t.proFallback;if(e===null||typeof e!="object")return;let n=e;if(typeof n.executedCommand!="string")return;let i=Array.isArray(n.alternatives)?n.alternatives.filter(r=>typeof r=="string"):void 0;return{executedCommand:n.executedCommand,...typeof n.requestedCommand=="string"?{requestedCommand:n.requestedCommand}:{},...i?{alternatives:i}:{}}}var Foe=/^Pro action '[^']+' is blocked in Basic mode\. Basic fallback '([^']+)' failed: (.*)$/s,qoe=/^Pro action '[^']+' is blocked in Basic mode\.(?! Basic fallback ')/,Boe=/instance not found|not found in|parent not found/,Zoe=/is required|missing required/,Hoe=/unknown action|unknown command/,Voe=/must be|invalid|cannot/;function Woe(t){let e=Foe.exec(t);return e?{fallbackCommand:e[1],reason:e[2]}:null}function Goe(t){return qoe.test(t)}function Joe(t){let e=t.toLowerCase();return Boe.test(e)?"not_found":Zoe.test(e)?"missing_param":Hoe.test(e)?"unknown_action":Voe.test(e)?"validation":"other"}function wc(t){if(t.resultSuccess){let n=Uoe(t.resultData);return n?{kind:"success_fallback",fallbackCommand:n.executedCommand,...n.alternatives?{alternatives:n.alternatives}:{}}:{kind:"success_ok"}}if(t.tier!=="pro"||!t.resultError)return{kind:"error"};let e=Woe(t.resultError);return e?{kind:"blocked_fallback_failed",fallbackCommand:e.fallbackCommand,blockedDetail:Joe(e.reason)}:Goe(t.resultError)?{kind:"blocked_unsupported"}:{kind:"error"}}var OI=100,Koe=12e4,Xoe=Object.entries(Sc).reduce((t,[e,n])=>{for(let i of n.validActions)t[`${e}_${i}`]=e;return t},{});function p6(t){return{toolName:Xoe[t]||t,commandName:t}}function d6(t,e){if(typeof t.contextId=="string"||!e||typeof e!="object")return t;let n=e.contextId;return typeof n=="string"?{...t,contextId:n}:t}function cv(t,e){if(t!=="manage_sync_status_current_place")return e;let{placeId:n,...i}=e;return i}async function NI(t,e,n,i,r,a){if(!t.historyManager)return;let o=wc({resultSuccess:r.success,resultData:r.data,resultError:r.error,tier:rr(n)});if(r.success){let s=o.kind==="success_fallback"&&r.data&&typeof r.data=="object"?{...r.data,outcomeStatus:"fallback",requestedCommand:n,executedCommand:o.fallbackCommand,...o.alternatives?{alternatives:o.alternatives}:{}}:r.data;await t.historyManager.recordSuccess(e,cv(n,i),s,a,n);return}if(o.kind==="blocked_unsupported"){await t.historyManager.recordBlockedOutcome(e,cv(n,i),r.error||"Pro action blocked in Basic mode","blocked_unsupported",{executionTimeMs:a,command:n});return}if(o.kind==="blocked_fallback_failed"){await t.historyManager.recordBlockedOutcome(e,cv(n,i),r.error||"Pro action blocked in Basic mode","blocked_fallback_failed",{executionTimeMs:a,command:n,...o.fallbackCommand?{fallbackCommand:o.fallbackCommand}:{},...o.blockedDetail?{blockedDetail:o.blockedDetail}:{}});return}await t.historyManager.recordFailure(e,cv(n,i),r.error||"Unknown error",a,void 0,n)}function Yoe(t,e,n){if(!e)return;let i=t.pluginClients.get(e);i?.inFlightRequestId===n&&(i.inFlightRequestId=void 0)}function Qoe(t,e,n){if(g.debug("Broadcasting command",{command:e.data.command,requestId:e.data.requestId,targetClientId:n||"all",activeWebsocketClients:Ja(t).length}),n){let i=t.pendingCommands.get(n)||[];i.length>=OI&&i.shift(),i.push(e),t.pendingCommands.set(n,i)}else{let i=Ja(t).sort((o,s)=>s.lastSeen-o.lastSeen),r=e.data.params?.placeId,a;if(r!==void 0&&i.length>1&&(a=i.find(o=>o.placeId===r),a&&g.debug("Routed command by placeId",{clientId:a.clientId,placeId:r})),a||(a=i[0],i.length>1&&g.warn("Multiple plugin clients connected, routing to most recent",{targetClientId:a?.clientId,targetPlaceId:a?.placeId,totalClients:i.length,command:e.data.command})),a){let o=t.pendingCommands.get(a.clientId)||[];o.length>=OI&&o.shift(),o.push(e),t.pendingCommands.set(a.clientId,o),g.debug("Routed command to client",{clientId:a.clientId,projectName:a.projectName,placeId:a.placeId})}else t.globalPendingCommands.length>=OI&&t.globalPendingCommands.shift(),t.globalPendingCommands.push(e)}KC(t,n)}async function m6(t,e,n){let i=Date.now(),r="",a={};try{let o=e.body;r=o.command??"",a=o.params||{};let s=o.requestId;if(o.instanceId||(t.serverLastCommandAt=Date.now()),o.instanceId){let m=t.mcpInstances.get(o.instanceId);m&&(m.lastCommandAt=Date.now(),m.lastSeen=m.lastCommandAt)}let c=p6(r),l=typeof o.timeout=="number"&&Number.isFinite(o.timeout)&&o.timeout>0?{timeout:o.timeout}:void 0,u=o.instanceId?t.mcpInstances.get(o.instanceId)?.sessionId??t.sessionId:t.sessionId;if(!r){n.status(400).json({error:"Missing command"});return}if(g.debug("Received execute request",{command:r,requestId:s}),r==="manage_camera_play_screenshot"){let m="manage_camera.play_screenshot is not supported. Screenshot capture is available only in Edit mode via manage_camera.screenshot.",h=Date.now()-i;t.historyManager&&await t.historyManager.recordFailure("manage_camera",a,m,h,"UNSUPPORTED_COMMAND",r),n.json({requestId:s,success:!1,error:m});return}if(r==="get_cached_selection"){let m=a.maxAge,h=Ap(t,m!==void 0?m:3e4),y=Date.now()-i;if(!h){let S={requestId:s,success:!0,data:{cached:!1,message:"No cached selection data available. The plugin may not be connected or no selection changes have occurred yet."}};t.historyManager&&await t.historyManager.recordSuccess(c.toolName,a,S.data,y,c.commandName),n.json(S);return}let v=Date.now()-h.timestamp,_={cached:!0,selection:h.selection,count:h.count,timestamp:h.timestamp,age:v};t.historyManager&&await t.historyManager.recordSuccess(c.toolName,a,_,y,c.commandName),n.json({requestId:s,success:!0,data:_});return}if(r==="get_connection_info"){let m=await ov(t),h=Date.now()-i;t.historyManager&&await t.historyManager.recordSuccess(c.toolName,a,m,h,c.commandName),n.json({requestId:s,success:!0,data:m});return}if(sv(r)==="internal"){if(!t.internalCommandExecutor){n.status(503).json({requestId:s,success:!1,error:`Internal command executor is not initialized for command: ${r}`});return}let m={...a,__sessionId:u},h=await t.internalCommandExecutor(r,m),y=Date.now()-i,v=d6(m,h.data);t.historyManager&&await NI(t,c.toolName,c.commandName,v,{success:h.success,data:h.data,error:h.error||"Internal command failed"},y),n.json({requestId:s,...h});return}let p=await kc(t,r,a,s,l),d=Date.now()-i,f={...d6(a,p.data),__sessionId:u};t.historyManager&&await NI(t,c.toolName,c.commandName,f,p,d),n.json(p)}catch(o){let s=Date.now()-i,c=nr(o);if(t.historyManager&&r){let l=p6(r);await t.historyManager.recordFailure(l.toolName,a,c,s,"EXCEPTION",l.commandName)}g.error("Execute request failed",o),n.status(500).json({success:!1,error:c})}}async function h6(t,e,n){let i=Date.now(),r=e.body,a=r.action;if(!a||typeof a!="string"){n.status(400).json({success:!1,error:"Missing required field: action"});return}if(!new Set(["get","set","get_all","set_multiple","get_attr","set_attr","get_all_attrs","delete_attr","add_tag","remove_tag","check_tag","get_tags","get_tagged"]).has(a)){n.status(400).json({success:!1,error:`Unsupported action: ${a}`});return}let s=`manage_properties_${a}`,c=f6(),{action:l,...u}=r;try{t.serverLastCommandAt=Date.now();let p=await kc(t,s,u,c),d=Date.now()-i;t.historyManager&&await NI(t,"manage_properties",s,u,p,d),n.json(p)}catch(p){let d=Date.now()-i,f=nr(p);t.historyManager&&await t.historyManager.recordFailure("manage_properties",u,f,d,"EXCEPTION",s),g.error("/api/properties \uC694\uCCAD \uCC98\uB9AC \uC2E4\uD328",p),n.status(500).json({success:!1,error:f})}}async function kc(t,e,n,i,r){g.debug("Executing command locally",{command:e,requestId:i});let a=t.config.requestTimeout,o=r?.timeout??Math.max(t.config.requestTimeout,Koe),s=new Promise((c,l)=>{let u={requestId:i,command:e,params:n,timestamp:Date.now(),queueTimeoutMs:a,ackedTimeoutMs:o,state:un.QUEUED,resolve:c,reject:l,timeoutId:null};t.commandQueue.set(i,u),u.timeoutId=setTimeout(()=>{t.commandQueue.delete(u.requestId),Yoe(t,u.targetClientId,u.requestId),u.reject(new Error(`Command timeout after ${a}ms: ${u.command}`))},a)});return Qoe(t,{event:"command",id:f6(),data:{command:e,requestId:i,params:n}}),s}ce();function lv(t,e){let n={};e.provider!==void 0&&(n.provider=e.provider),e.tier!==void 0&&(n.tier=e.tier),e.status!==void 0&&(n.status=e.status),e.canUsePro!==void 0&&(n.canUsePro=e.canUsePro),e.cleared===!0&&(n.cleared=!0),tt(t,"license",n)}function ir(t,e,n,i){t.status(e).json({success:!1,error:{code:n,message:i}})}function ese(t){if(typeof t!="string")return;let e=t.trim().toLowerCase();return e.length>0?e:void 0}function uv(t,e){if(e==="plugin")return t;let n={...t};return delete n.sessionToken,n}function zp(t,e,n,i){let r=ese(i??e.query.provider);return r?t.licenseState?.supportsProvider(r)===!1?(ir(n,400,"LICENSE_PROVIDER_UNSUPPORTED",`provider "${r}" is not supported`),null):r:(ir(n,400,"LICENSE_PROVIDER_REQUIRED","provider is required"),null)}function Mp(t,e){return t.licenseState?!0:(ir(e,503,"LICENSE_NOT_INITIALIZED","License system is not initialized"),!1)}function tse(t){if(!t||typeof t!="object")return!1;let e=t;return(e.canUsePro===!0||e.canUsePro===!1)&&typeof e.status=="string"&&typeof e.checkedAt=="number"}function Lp(t,e,n,i="plugin"){if(!Mp(t,n))return;let r=e.body,a=typeof r?.licenseKey=="string"?r.licenseKey:"";if(!a.trim()){ir(n,400,"LICENSE_KEY_REQUIRED","licenseKey is required");return}let o=zp(t,e,n,r?.provider);if(!o)return;let s=typeof r?.pluginClientId=="string"?r.pluginClientId:typeof e.query.clientId=="string"?e.query.clientId:void 0,c=typeof r?.deviceId=="string"?r.deviceId:void 0,l={licenseKey:a,provider:o,clientType:i};s&&(l.pluginClientId=s),c&&(l.deviceId=c),t.licenseState.activateGateway(l).then(u=>{t.analyticsManager?.setTier(u.canUsePro?"pro":"basic"),lv(t,{provider:o,tier:u.canUsePro?"pro":"basic",status:u.status,canUsePro:u.canUsePro}),n.json(uv(u,i))}).catch(u=>{g.warn("License activate failed",{error:u instanceof Error?u.message:"unknown_error"}),ir(n,502,"LICENSE_ACTIVATE_FAILED",u instanceof Error?u.message:"License activation failed")})}function Up(t,e,n,i="plugin"){if(!Mp(t,n))return;let r=zp(t,e,n);if(r)try{let a=t.licenseState.getStatus(r);t.analyticsManager?.setTier(a.canUsePro?"pro":"basic"),n.json(uv(a,i))}catch(a){g.warn("License status check failed",{error:a instanceof Error?a.message:"unknown_error"}),ir(n,502,"LICENSE_STATUS_FAILED",a instanceof Error?a.message:"License status check failed")}}function Fp(t,e,n,i="plugin"){if(!Mp(t,n))return;let r=e.body,a=zp(t,e,n,r?.provider);if(!a)return;let o={provider:a,clientType:i};i==="plugin"&&typeof r?.sessionToken=="string"&&r.sessionToken.trim()&&(o.sessionToken=r.sessionToken.trim()),typeof r?.pluginClientId=="string"&&(o.pluginClientId=r.pluginClientId),typeof r?.deviceId=="string"&&(o.deviceId=r.deviceId),t.licenseState.refreshGateway(o).then(s=>{t.analyticsManager?.setTier(s.canUsePro?"pro":"basic"),lv(t,{provider:a,tier:s.canUsePro?"pro":"basic",status:s.status,canUsePro:s.canUsePro}),n.json(uv(s,i))}).catch(s=>{g.warn("License refresh failed",{error:s instanceof Error?s.message:"unknown_error"}),ir(n,502,"LICENSE_REFRESH_FAILED",s instanceof Error?s.message:"License refresh failed")})}function qp(t,e,n,i="plugin"){if(!Mp(t,n))return;let r=e.body,a=zp(t,e,n,r?.provider);a&&t.licenseState.resetGateway({provider:a,clientType:i}).then(o=>{t.analyticsManager?.setTier("basic"),lv(t,{provider:a,tier:"basic",status:o.status,canUsePro:o.canUsePro,cleared:!0}),n.json(uv(o,i))}).catch(o=>{g.warn("License reset failed",{error:o instanceof Error?o.message:"unknown_error"}),ir(n,502,"LICENSE_RESET_FAILED",o instanceof Error?o.message:"License reset failed")})}function DI(t,e,n){if(!Mp(t,n))return;let i=e.body,r=zp(t,e,n,i?.provider);if(!r)return;if(i?.clientType!=="plugin"){ir(n,400,"LICENSE_CLIENT_TYPE_INVALID","clientType must be plugin");return}let a=typeof i?.sessionToken=="string"&&i.sessionToken.trim()?i.sessionToken.trim():void 0,o=tse(i?.snapshot)?i.snapshot:void 0;if(!o&&!a){ir(n,400,"LICENSE_BOOTSTRAP_REQUIRED","snapshot or sessionToken is required");return}let s={provider:r,clientType:"plugin"};o&&(s.snapshot=o),a&&(s.sessionToken=a),t.licenseState.bootstrap(s).then(c=>{t.analyticsManager?.setTier(c.canUsePro?"pro":"basic"),lv(t,{provider:r,tier:c.canUsePro?"pro":"basic",status:c.status,canUsePro:c.canUsePro}),n.json({ok:!0})}).catch(c=>{g.warn("License bootstrap failed",{error:c instanceof Error?c.message:"unknown_error"}),ir(n,502,"LICENSE_BOOTSTRAP_FAILED",c instanceof Error?c.message:"License bootstrap failed")})}function g6(t){t.app.post("/license/bootstrap",(e,n)=>DI(t,e,n)),t.app.post("/license/activate",(e,n)=>Lp(t,e,n,"plugin")),t.app.post("/license/refresh",(e,n)=>Fp(t,e,n,"plugin")),t.app.post("/license/reset",(e,n)=>qp(t,e,n,"plugin")),t.app.get("/license/status",(e,n)=>Up(t,e,n,"plugin"))}Pt();ce();import{promises as pv}from"fs";import jI from"path";function nse(t){let e=t.config.appDataDir??tn();return jI.join(e,"observability","logs")}async function Cc(t,e,n){let i=e.query.clientId;if(i&&t.pluginClients.has(i)){let r=t.pluginClients.get(i);r.lastSeen=Date.now()}try{let r=e.body;if(!r||!Array.isArray(r.logs)){g.warn("Invalid logs request",{body:r}),n.status(400).json({error:"Invalid request body"});return}let a=nse(t);await pv.mkdir(a,{recursive:!0});let c=`plugin-${new Date().toISOString().split("T")[0]}.log`,l=jI.join(a,c),u=jI.join(a,"current.log"),p=r.logs.map(d=>`[${new Date(d.timestamp*1e3).toISOString().replace("T"," ").substring(0,19)}] [${d.level}] ${d.message}`).join(`
139
+ `),await ic(h)}catch(h){g.warn("Failed to refresh root representative sourcemap after full sync",{placeId:e,error:h instanceof Error?h.message:String(h)})}this.ctx.startFileWatcherForPlace(r).catch(h=>{g.warn("Failed to start reverse detector after full sync",{placeId:e,error:h instanceof Error?h.message:String(h)}),r.fileWatcher=null}),r.writer.setBootstrapMode(!1),r.writer.appendChangeLog(`FULL_SYNC_COMPLETE instances=${r.instanceCount} scripts=${r.scriptCount}`),r.writer.appendHistory({timestamp:new Date().toISOString(),type:"fullSyncComplete",direction:"forward",source:"studio",path:`place_${e}`,details:`instances:${r.instanceCount} scripts:${r.scriptCount}`}),g.info("Full sync completed",{placeId:e,instanceCount:r.instanceCount,scriptCount:r.scriptCount}),i.status(200).json({status:"completed",instanceCount:r.instanceCount,scriptCount:r.scriptCount,syncRoot:this.ctx.config.getPlaceRoot(e)})}catch(f){throw l&&l.endFullSyncReplacement(),f}}setPreserveLocalFiles(e,n){this.preserveLocalFilesMap.set(e,n)}getAndClearPreserveLocalFiles(e){let n=this.preserveLocalFilesMap.get(e)||[];return this.preserveLocalFilesMap.delete(e),n}clearPreserveLocalFiles(e){this.preserveLocalFilesMap.delete(e)}clearPendingServiceTrees(e){this.pendingServiceTrees.delete(e)}startTTLTimerForPlace(e,n){let i=setTimeout(async()=>{g.warn("Incomplete sync TTL expired, cleaning up",{placeId:e.placeId,syncId:n});let r=this.ctx.config.getPlaceRoot(e.placeId),a=Pe.join(r,`explorer_tmp_${n}`);try{await pn.rm(a,{recursive:!0,force:!0})}catch(o){g.error("Failed to clean up expired temp dir",o instanceof Error?o:new Error(String(o)))}e.incompleteSyncTimer=null,e.activeFullSyncSessionId===n&&(e.activeFullSyncSessionId=null,e.activeClientId=null,e.state="idle",e.tmpIndex=null,this.pendingServiceTrees.delete(n),e.collisionDirMap=null,e.tmpWriter&&(e.tmpWriter.stopChangeLogFlusher(),e.tmpWriter=null),this.ctx.activeFullSyncPlaceId===e.placeId&&(this.ctx.activeFullSyncPlaceId=null),this.ctx.clearRuntimePlaceIfMatch(e.placeId))},q2);i&&typeof i=="object"&&"unref"in i&&i.unref(),e.incompleteSyncTimer=i}getOrCreatePendingServiceTree(e,n){let i=this.pendingServiceTrees.get(e);i||(i=new Map,this.pendingServiceTrees.set(e,i));let r=i.get(n.serviceName);if(r)return r;let a={serviceName:n.tree?.name??n.serviceName,serviceClassName:n.tree?.className??n.serviceClassName,zeroBasedChunkIndex:n.chunkIndex===0,instances:[]};return i.set(n.serviceName,a),a}isLastChunk(e,n,i){return i<=1?!0:e.zeroBasedChunkIndex?n>=i-1:n>=i}buildServiceTree(e,n){let i={name:n.serviceName,className:n.serviceClassName,childCount:0,children:[],syncedAt:new Date().toISOString()};for(let r of n.instances){let a=this.resolveEffectiveSegments(e,r.effectivePath),o=Et(r.originalPath);this.upsertTreeNode(i,a,o,r.className)}return this.recomputeTreeChildCounts(i),i.syncedAt=new Date().toISOString(),i}resolveEffectiveSegments(e,n){return e.tmpIndex?Et(n).map(i=>e.tmpIndex.sanitizeName(i)):Et(n)}rewritePendingEffectivePaths(e,n,i,r){let a=Pe.relative(n,i).split(Pe.sep).filter(l=>l.length>0),o=Pe.relative(n,r).split(Pe.sep).filter(l=>l.length>0);if(a.length===0||o.length===0)return;let s=nt(["game",...a]),c=nt(["game",...o]);for(let l of e.instances){if(l.effectivePath===s){l.effectivePath=c;continue}(l.effectivePath.startsWith(`${s}.`)||l.effectivePath.startsWith(`${s}[`))&&(l.effectivePath=`${c}${l.effectivePath.slice(s.length)}`)}}upsertTreeNode(e,n,i,r){if(n.length<=1)return;let a=e.children;for(let o=1;o<n.length;o++){let s=n[o],c=i[o],l=o===n.length-1,u=a.find(p=>p.name===s);u?l&&(u.className=r,c!==void 0&&c!==s&&(u.originalName=c)):(u={name:s,className:l?r:"Folder",childCount:0,children:[]},c!==void 0&&c!==s&&(u.originalName=c),a.push(u)),u.children||(u.children=[]),a=u.children}}recomputeTreeChildCounts(e){let n=i=>{let r=i.children??[];i.children=r;for(let a of r)n(a);i.childCount=r.length};for(let i of e.children)n(i);e.childCount=e.children.length}clearTTLTimerForPlace(e,n){e.incompleteSyncTimer&&e.activeFullSyncSessionId===n&&(clearTimeout(e.incompleteSyncTimer),e.incompleteSyncTimer=null)}async cleanupStaleTempDirs(){let e=this.ctx.config.getSyncRoot();try{let n=await pn.readdir(e,{withFileTypes:!0});for(let i of n)if(i.isDirectory()){if(i.name.startsWith("explorer_tmp_")){let r=Pe.join(e,i.name);g.warn("Removing stale temp directory from crashed sync",{dir:i.name});try{await pn.rm(r,{recursive:!0,force:!0})}catch(a){g.error(`Failed to remove stale temp dir: ${i.name}`,a instanceof Error?a:new Error(String(a)))}}if(i.name.startsWith("place_")){let r=Pe.join(e,i.name);try{let a=await pn.readdir(r,{withFileTypes:!0});for(let o of a)if(o.isDirectory()&&o.name.startsWith("explorer_tmp_")){let s=Pe.join(r,o.name);g.warn("Removing stale temp directory from crashed sync",{dir:`${i.name}/${o.name}`});try{await pn.rm(s,{recursive:!0,force:!0})}catch(c){g.error(`Failed to remove stale temp dir: ${i.name}/${o.name}`,c instanceof Error?c:new Error(String(c)))}}}catch{continue}}}}catch(n){if(n.code==="ENOENT")return;g.warn("Failed to scan for stale temp dirs",{error:n instanceof Error?n.message:String(n)})}}};import tr from"path";import{promises as Xy}from"fs";ce();var Yy=class{constructor(e){this.ctx=e}async handleReversePending(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({pending:0,hasConflicts:!1,lastDetected:null});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}let a={pending:r.fileWatcher?.getPendingCount()??0,hasConflicts:!1,lastDetected:r.fileWatcher?.getLastDetected()??null,forwardRestoreNeeded:r.forwardRestoreQueue.length};this.ctx.touchRuntimePlace(i),n.status(200).json(a)}async handleReverseSyncChanges(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({changes:[],count:0});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}if(r.state!=="syncing"){n.status(400).json({error:"Not syncing",message:"Reverse sync is only available when sync is active"});return}let a=r.fileWatcher?.drainPendingChanges()??[],o=a.length>0?await r.reader.buildChangesFromPending(a):[];this.ctx.touchRuntimePlace(i),n.status(200).json({changes:o,count:o.length})}async handleReverseSyncResult(e,n){let i=e.body,r=i.placeId??this.ctx.getDefaultRuntimePlaceId();if(r==null){n.status(400).json({error:"Validation error",message:"placeId is required (in body or via active sync session)"});return}let a=i.appliedFiles??i.appliedPaths;if(!a||!Array.isArray(a)){n.status(400).json({error:"Validation error",message:"appliedFiles (or appliedPaths) must be an array of relative file paths"});return}let o=this.ctx.places.get(r);if(!o){n.status(404).json({error:"Place not found",message:`No sync context for place ${r}`});return}this.ctx.touchRuntimePlace(r);let s=this.ctx.config.getPlaceRoot(r),c=0,l=[];for(let u of a){let p=tr.resolve(s,u);if(!Vy(s,p)){l.push({path:u,error:"Path is outside the place root"});continue}try{let d=await Xy.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=$t(m),y=Ut(m);y&&h&&await o.writer.removeFromTree(y,h)}c++}else f==="EISDIR"?c++:l.push({path:u,error:d instanceof Error?d.message:String(d)})}}c>0&&await o.index.saveToDisk(),c>0&&o.writer.appendHistory({timestamp:new Date().toISOString(),type:"reverseApply",direction:"reverse",source:"local",path:`place_${r}`,details:`applied:${c} failed:${l.length}`}),n.status(200).json({updated:c,failed:l.length,errors:l})}async handleResolveConflict(e,n){let i=e.body;if(!i.fsPath||!i.resolution){n.status(400).json({error:"Validation error",message:"fsPath and resolution are required"});return}let{fsPath:r,resolution:a}=i,o=this.ctx.config.getSyncRoot();if(!Vy(o,tr.resolve(o,r))){n.status(403).json({error:"Forbidden",message:"Path is outside the sync root"});return}if(a==="skip"){n.status(200).json({status:"skipped",fsPath:r});return}let s;if(i.placeId&&(s=this.ctx.places.get(i.placeId)),!s){let d=r.match(/^place_(\d+)(?:_[^/]+)?\//);if(d){let f=parseInt(d[1],10);s=this.ctx.places.get(f)}else s=Array.from(this.ctx.places.values())[0]}if(!s){n.status(404).json({error:"No active place context",message:"No sync session is active. Start a sync first."});return}let c=this.ctx.config.getPlaceRoot(s.placeId),l=tr.resolve(c,r);if(!Vy(c,l)){n.status(403).json({error:"Forbidden",message:"Path is outside the place root"});return}let u=s.index,p=s.reader;if(a==="apply-studio"){if(typeof i.studioContent!="string"){n.status(400).json({error:"Validation error",message:"studioContent is required for apply-studio resolution"});return}await Xy.mkdir(tr.dirname(l),{recursive:!0});let d=i.studioContent,f=u.computeHash(d);s.fileWatcher?.suppressPath(l,f),await Xy.writeFile(l,d,"utf-8"),u.updateHashByValue(l,f),u.updateFileHashByValue(l,f),await u.saveToDisk(),n.status(200).json({status:"resolved",resolution:"apply-studio",fsPath:r});return}if(a==="apply-file"){let d=await Xy.readFile(l,"utf-8"),f=u.computeHash(d);u.resolveFile(l,"apply-file",f),await u.saveToDisk();let m=p.getFileType(l),h=p.resolveInstancePathFromFile(l);n.status(200).json({status:"resolved",resolution:"apply-file",fsPath:r,instancePath:h,fileType:m,content:d});return}n.status(400).json({error:"Invalid resolution",message:`Unknown resolution: ${a}`})}async handleReverseRescan(e,n){let i=this.ctx.resolveQueryPlaceId(e);if(i==null){n.status(200).json({added:0});return}let r=this.ctx.places.get(i);if(!r){n.status(404).json({error:"Place not found",message:`No sync context for place ${i}`});return}if(!r.fileWatcher){n.status(200).json({added:0});return}let a=await r.fileWatcher.rescan();this.ctx.touchRuntimePlace(i),g.info("Reverse rescan completed",{placeId:i,added:a}),n.status(200).json({added:a})}resolveInstancePathForAppliedPath(e,n){let i=e.resolveInstancePathFromFsPath(n);if(i)return i;let r=e.getExplorerRoot(),a=tr.relative(r,n);if(a.startsWith("..")||a===""||tr.isAbsolute(a))return null;let o=a.split(tr.sep).filter(f=>f.length>0);if(o.length<2)return null;let s=tr.basename(n),c=tr.dirname(n),l=e.getOriginalInstance(c,s);if(l)return l.instancePath;let u=s.toLowerCase();if(Cy.some(f=>u.endsWith(f))||u==="_tree.json")return null;let p=["game"],d=r;for(let f of o){d=tr.join(d,f);let m=tr.dirname(d);p.push(e.getOriginalNameForDir(m,f))}return nt(p)}};ce();function W2(t){if(!t||typeof t!="object")return;let e=t;if(Array.isArray(e.instances))for(let n of e.instances)Array.isArray(n.properties)&&n.properties.length===0&&(n.properties={}),Array.isArray(n.attributes)&&n.attributes.length===0&&(n.attributes={});if(Array.isArray(e.changes))for(let n of e.changes)Array.isArray(n.properties)&&n.properties.length===0&&(n.properties={}),Array.isArray(n.attributes)&&n.attributes.length===0&&(n.attributes={})}function Doe(t){return t.type==="instanceAdded"||t.type==="instanceRemoved"||t.type==="instanceRenamed"||t.type==="instanceMoved"||t.type==="scriptSourceChanged"}var Ir=class{config;places;apiHandler;changeProcessor;initHandler;reverseHandler;reverseDetectorFactory;activeFullSyncPlaceId=null;activeRuntimeSyncPlaceId=null;currentConnectedPlaceId=null;currentConnectedPlaceName=null;constructor(e,n={}){this.config=new Sy(e,n),this.reverseDetectorFactory=n.reverseDetectorFactory??(i=>new By(i.explorerRoot,i.syncIndex,{placeId:i.placeId})),this.apiHandler=new Jy(this),this.changeProcessor=new Wy(this),this.initHandler=new Ky(this),this.reverseHandler=new Yy(this),this.places=new Fy({max:3,dispose:(i,r)=>{g.info("Disposing place context (LRU eviction)",{placeId:r}),this.activeFullSyncPlaceId===r&&(this.activeFullSyncPlaceId=null),i.fileWatcher&&(g.info("Disposing place context: stopping reverse detector",{placeId:r,reason:"lru-dispose",mode:Ye}),i.fileWatcher.stop("lru-dispose").catch(a=>{g.error("Error stopping reverse detector during dispose",a)}),i.fileWatcher=null),i.writer.stopChangeLogFlusher().catch(a=>{g.error("LRU dispose \uC911 changelog flush \uC2E4\uD328",a)}),i.incompleteSyncTimer&&(clearTimeout(i.incompleteSyncTimer),i.incompleteSyncTimer=null),i.index.clearAllMaps(),i.index.saveToDisk().catch(a=>{g.error("Error saving index during dispose",a)}),i.activeFullSyncSessionId&&this.initHandler.clearPendingServiceTrees(i.activeFullSyncSessionId),i.tmpWriter&&(i.tmpWriter.stopChangeLogFlusher(),i.tmpWriter=null),i.tmpIndex=null,i.collisionDirMap=null}})}getSyncRoot(){return this.config.getSyncRoot()}async getOrCreatePlaceContext(e,n){let i=this.places.get(e);if(i&&n){let r=this.config.getPlaceRoot(e),a=await this.config.resolvePlaceRoot(e,n);a!==r&&(g.info("Place root migrated, recreating context",{placeId:e,from:r,to:a}),this.places.delete(e),i=void 0)}if(!i){let r=await this.config.resolvePlaceRoot(e,n),a=_I.join(r,"explorer");await bc.mkdir(r,{recursive:!0}),await bc.mkdir(a,{recursive:!0});let o=new Zi(r,a);await o.loadFromDisk();let s=new gc(this.config,o,e),c=new qy(this.config,o,r);s.startChangeLogFlusher(),i={placeId:e,placeName:"",index:o,writer:s,reader:c,fileWatcher:null,state:"idle",activeClientId:null,activeFullSyncSessionId:null,instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,tmpIndex:null,tmpWriter:null,incompleteSyncTimer:null,changesSinceLastSave:0,directions:{...Wa},applyModes:{...Ga},forwardRestoreQueue:[],syncProgress:null,collisionDirMap:null},this.places.set(e,i),g.info("Created new place context",{placeId:e,placeRoot:r})}return i}async handleSyncInit(e,n){try{W2(e.body);let i=Z2(e.body);if(!i.success){n.status(400).json({error:"Validation error",message:i.error});return}let r=i.data,a=r.phase==="start"?r.placeId??null:r.phase==="chunk"||r.phase==="complete"?this.activeFullSyncPlaceId:null;if(a==null){n.status(400).json({error:"Missing placeId",message:"placeId is required in start phase or must be set by previous start"});return}switch(r.phase){case"start":this.setCurrentConnectedPlace(a,r.placeName),await this.initHandler.handleInitStart(a,r,n);break;case"chunk":await this.initHandler.handleInitChunk(a,r,n);break;case"complete":await this.initHandler.handleInitComplete(a,r,n);break}}catch(i){this.sendError(n,i,"handleSyncInit")}}async handleSyncUpdate(e,n){try{W2(e.body);let i=H2(e.body);if(!i.success){n.status(400).json({error:"Validation error",message:i.error});return}let r=i.data,a=r.placeId??this.getDefaultRuntimePlaceId();if(a==null){n.status(400).json({error:"Missing placeId",message:"placeId is required in body or must have an active sync session"});return}let o=await this.getOrCreatePlaceContext(a);if(this.setCurrentConnectedPlace(a,o.placeName||this.currentConnectedPlaceName),o.activeFullSyncSessionId!==null||o.state==="initializing"){n.status(409).json({error:"Conflict",message:`Full sync in progress for place ${a}`});return}o.activeClientId=r.clientId,this.touchRuntimePlace(a);let s=F2(o,r.changes);g.info("Sync update received",{placeId:a,clientId:r.clientId,changeCount:s.length,receivedCount:r.changes.length,types:s.map(m=>m.type)});let c=[],l=[],u=0,p=!1,d=new Map;for(let m of s)try{let h=await this.changeProcessor.processChangeForPlace(o,m,d);h?l.push(h):(u++,Doe(m)&&(p=!0))}catch(h){let y="path"in m?m.path:"oldPath"in m?m.oldPath:"unknown";c.push({path:y,error:h instanceof Error?h.message:String(h)})}for(let m of d.values())try{await Zy(o,m)}catch(h){c.push({path:m.instancePath,error:h instanceof Error?h.message:String(h)})}if(p)try{let m=this.config.getSyncRoot(),h=this.config.getPlaceRoot(a);await Py(m,h),await ic(m)}catch(m){g.warn("Failed to refresh sourcemaps after incremental sync update",{placeId:a,error:m instanceof Error?m.message:String(m)})}o.changesSinceLastSave+=u,o.changesSinceLastSave>=B2&&(await o.index.saveToDisk(),o.changesSinceLastSave=0),o.lastIncrementalSync=new Date().toISOString();let f={processed:u,failed:c.length,errors:c,syncedAt:o.lastIncrementalSync};l.length>0&&(f.conflicts=l),n.status(200).json(f)}catch(i){this.sendError(n,i,"handleSyncUpdate")}}getDefaultRuntimePlaceId(){return this.currentConnectedPlaceId}getDefaultRuntimePlaceName(){if(this.currentConnectedPlaceName)return this.currentConnectedPlaceName;if(this.currentConnectedPlaceId!==null&&this.currentConnectedPlaceId!==void 0){let e=this.places.get(this.currentConnectedPlaceId);if(e?.placeName)return e.placeName}return null}setCurrentConnectedPlace(e,n){if(this.currentConnectedPlaceId=e,e==null){this.currentConnectedPlaceName=null;return}if(typeof n=="string"&&n.length>0){this.currentConnectedPlaceName=n;let r=this.places.get(e);r&&(r.placeName=n);return}let i=this.places.get(e);i?.placeName&&(this.currentConnectedPlaceName=i.placeName)}clearCurrentConnectedPlaceIfMatch(e){this.currentConnectedPlaceId===e&&(this.currentConnectedPlaceId=null,this.currentConnectedPlaceName=null)}touchRuntimePlace(e){this.activeRuntimeSyncPlaceId=e}clearRuntimePlaceIfMatch(e){this.activeRuntimeSyncPlaceId===e&&(this.activeRuntimeSyncPlaceId=null)}resolveQueryPlaceId(e,n="runtime"){let i=e.query.placeId;if(i){let r=parseInt(i,10);if(!isNaN(r))return r}return n==="full"?this.activeFullSyncPlaceId:this.getDefaultRuntimePlaceId()}async handleSyncStatus(e,n){try{let i=u=>({state:"idle",instanceCount:0,scriptCount:0,lastFullSync:null,lastIncrementalSync:null,syncRoot:u,activeClientId:null,reverseSyncAvailable:!1,modifiedFileCount:0,reverseDetectorActive:!1,reverseDetectorMode:Ye,fileWatcherActive:!1}),r=this.resolveQueryPlaceId(e);if(r==null){let u=i(this.config.getSyncRoot());n.status(200).json(u);return}let a=this.places.get(r);if(!a){let u=i(this.config.getPlaceRoot(r));n.status(200).json(u);return}let o=a.fileWatcher?.getPendingCount()??0,s=a.fileWatcher?.isActivelyScanning()??!1,c=a.fileWatcher!==null;this.touchRuntimePlace(r);let l={state:a.state,instanceCount:a.instanceCount,scriptCount:a.scriptCount,lastFullSync:a.lastFullSync,lastIncrementalSync:a.lastIncrementalSync,syncRoot:this.config.getPlaceRoot(r),activeClientId:a.activeClientId,reverseSyncAvailable:o>0,modifiedFileCount:o,applyModes:a.applyModes,directions:a.directions,reverseDetectorActive:s,reverseDetectorMode:Ye,fileWatcherActive:c,forwardOnlyClasses:[...rc]};n.status(200).json(l)}catch(i){this.sendError(n,i,"handleSyncStatus")}}async handleSyncStop(e,n){try{let i=e.body,r=i.placeId??this.getDefaultRuntimePlaceId();if(g.info("handleSyncStop requested",{placeId:r,clientId:i.clientId??null,reason:i.reason??"requested"}),r==null){n.status(200).json({status:"idle",state:"idle",placeId:null,message:"No active sync place"});return}let a=this.places.get(r);if(!a){g.info("handleSyncStop no place context",{placeId:r,clientId:i.clientId??null}),n.status(200).json({status:"idle",state:"idle",placeId:r,message:`No sync context for place ${r}`});return}if(i.clientId&&a.activeClientId&&i.clientId!==a.activeClientId&&(a.activeFullSyncSessionId!==null||a.state==="syncing"||a.state==="initializing")){g.warn("handleSyncStop rejected due to active client mismatch",{placeId:r,requestedClientId:i.clientId,activeClientId:a.activeClientId,state:a.state,activeFullSyncSessionId:a.activeFullSyncSessionId}),n.status(409).json({error:"Conflict",message:`This sync session belongs to client ${a.activeClientId}`});return}let o=a.activeFullSyncSessionId,s=a.state,c=a.fileWatcher!==null;if(o&&(this.initHandler.clearPreserveLocalFiles(o),this.initHandler.clearPendingServiceTrees(o)),a.fileWatcher&&(g.info("handleSyncStop suspending reverse detector",{placeId:r,state:a.state,activeClientId:a.activeClientId,mode:Ye}),a.fileWatcher.beginFullSyncReplacement()),a.incompleteSyncTimer&&(clearTimeout(a.incompleteSyncTimer),a.incompleteSyncTimer=null),o){let l=this.config.getPlaceRoot(r),u=_I.join(l,`explorer_tmp_${o}`);await bc.rm(u,{recursive:!0,force:!0}).catch(()=>{})}a.tmpWriter&&(a.tmpWriter.stopChangeLogFlusher(),a.tmpWriter=null),a.tmpIndex=null,a.collisionDirMap=null,a.activeFullSyncSessionId=null,a.syncProgress=null,a.state="idle",a.activeClientId=null,a.instanceCount=0,a.scriptCount=0,a.forwardRestoreQueue=[],this.activeFullSyncPlaceId===r&&(this.activeFullSyncPlaceId=null),this.clearRuntimePlaceIfMatch(r),g.info("handleSyncStop responding idle",{placeId:r,previousState:s,activeFullSyncSessionId:o,hadWatcher:c}),g.info("Sync stopped",{placeId:r,reason:i.reason??"requested"}),n.status(200).json({status:"stopped",state:"idle",placeId:r})}catch(i){this.sendError(n,i,"handleSyncStop")}}async initialize(){try{await this.config.loadFromMeta(),await this.initHandler.cleanupStaleTempDirs(),g.info("SyncController initialized")}catch(e){g.error("SyncController initialization failed",e instanceof Error?e:new Error(String(e)))}}async shutdown(){try{g.info("SyncController shutdown: clearing place contexts",{placeCount:this.places.size}),this.places.clear(),g.info("SyncController shut down")}catch(e){g.error("SyncController shutdown error",e instanceof Error?e:new Error(String(e)))}}async atomicWriteFile(e,n){let i=e+".tmp."+Noe().slice(0,8);try{await bc.writeFile(i,n,"utf-8"),await bc.rename(i,e)}catch(r){throw await bc.unlink(i).catch(()=>{}),r}}async handlePreCheck(e,n){try{await this.apiHandler.handlePreCheck(e,n)}catch(i){this.sendError(n,i,"handlePreCheck")}}async handleSyncDirections(e,n){try{await this.apiHandler.handleSyncDirections(e,n)}catch(i){this.sendError(n,i,"handleSyncDirections")}}async handleForwardRestoreList(e,n){try{await this.apiHandler.handleForwardRestoreList(e,n)}catch(i){this.sendError(n,i,"handleForwardRestoreList")}}async handleReversePending(e,n){try{await this.reverseHandler.handleReversePending(e,n)}catch(i){this.sendError(n,i,"handleReversePending")}}async handleReverseSyncChanges(e,n){try{await this.reverseHandler.handleReverseSyncChanges(e,n)}catch(i){this.sendError(n,i,"handleReverseSyncChanges")}}async handleReverseSyncResult(e,n){try{await this.reverseHandler.handleReverseSyncResult(e,n)}catch(i){this.sendError(n,i,"handleReverseSyncResult")}}async handleResolveConflict(e,n){try{await this.reverseHandler.handleResolveConflict(e,n)}catch(i){this.sendError(n,i,"handleResolveConflict")}}async handleReverseRescan(e,n){try{await this.reverseHandler.handleReverseRescan(e,n)}catch(i){this.sendError(n,i,"handleReverseRescan")}}async handleSyncHistory(e,n){try{await this.apiHandler.handleSyncHistory(e,n)}catch(i){this.sendError(n,i,"handleSyncHistory")}}async startFileWatcherForPlace(e){if(e.state!=="syncing"){g.debug("Skipping reverse detector start - place not syncing",{placeId:e.placeId,state:e.state});return}if(e.fileWatcher){if(e.fileWatcher.isActivelyScanning()){g.info("startFileWatcherForPlace: reusing active reverse detector",{placeId:e.placeId,mode:Ye});return}g.info("startFileWatcherForPlace: resuming paused reverse detector",{placeId:e.placeId,mode:Ye}),e.fileWatcher.endFullSyncReplacement();return}let n=this.createReverseChangeDetector(e);this.configureReverseChangeDetector(e,n),e.fileWatcher=n,n.start().then(()=>{e.fileWatcher===n&&g.info("Reverse detector started for reverse sync",{placeId:e.placeId,mode:Ye})}).catch(i=>{g.warn("Failed to start reverse detector",{placeId:e.placeId,mode:Ye,error:i instanceof Error?i.message:String(i)}),e.fileWatcher===n&&(e.fileWatcher=null)})}createReverseChangeDetector(e){return this.reverseDetectorFactory({explorerRoot:_I.join(this.config.getPlaceRoot(e.placeId),"explorer"),syncIndex:e.index,placeId:e.placeId})}configureReverseChangeDetector(e,n){e.writer.setOnWriteCallback(i=>{n.suppressPath(i)}),n.setDirectionChecker(i=>{let r=_L(i);return e.directions[r]}),n.setOnForwardViolation(i=>{e.forwardRestoreQueue.includes(i)||(e.forwardRestoreQueue.push(i),g.info("Forward violation queued for restore",{placeId:e.placeId,relativePath:i,queueSize:e.forwardRestoreQueue.length}))})}getStatusSummary(){return this.apiHandler.getStatusSummary()}getDirectionForCategory(e){return this.apiHandler.getDirectionForCategory(e)}getStatusDirect(e){return this.apiHandler.getStatusDirect(e)}async getHistoryDirect(e,n){return this.apiHandler.getHistoryDirect(e,n)}getDirectionsDirect(e){return this.apiHandler.getDirectionsDirect(e)}getProgressDirect(e){return this.apiHandler.getProgressDirect(e)}async readSyncedFile(e,n){return this.apiHandler.readSyncedFile(e,n)}async writeSyncedFile(e,n,i){await this.apiHandler.writeSyncedFile(e,n,i)}async executeViaDisk(e,n){return this.apiHandler.executeViaDisk(e,n)}sendError(e,n,i){let r=n instanceof Error?n.message:String(n);if(r.includes("Path traversal detected")){e.status(403).json({error:"Forbidden",message:r});return}let a=n.code;if(a==="ENOSPC"||a==="EPERM"||a==="EACCES"){e.status(500).json({error:"Disk error",message:r});return}g.error(`SyncController.${i} failed`,n instanceof Error?n:new Error(r)),e.status(500).json({error:"Internal error",message:`${i}: ${r}`})}};import{randomUUID as joe}from"crypto";function G2(t,e){let n=joe(),i=n.replace(/-/g,"").substring(0,8).toUpperCase(),r=`${i.substring(0,4)}-${i.substring(4,8)}`;return{config:t,app:e,instanceId:n,sessionId:r,startTime:Date.now(),baseUrl:`http://${t.httpHost}:${t.httpPort}`,commandQueue:new Map,pendingCommands:new Map,globalPendingCommands:[],totalCommandsProcessed:0,pluginClients:new Map,pluginCommandSessionsByClientId:new Map,pluginCommandClientIdsByProcessToken:new Map,mcpInstances:new Map,cachedSelectionMap:new Map,isClientMode:!1,clientModeHealthTimer:null,clientModeConsecutiveHealthFailures:0,clientModeUpstreamReachable:!0,clientModeUpstreamContextCaptureEnabled:!0,clientModeLastHealthSuccessAt:null,clientModeLastHealthFailureAt:null,clientModeLastHealthError:null,onUpstreamPermanentlyDown:null,clientModeIdleTimeoutMs:t.clientModeIdleTimeoutMs??36e5,clientModeIdleWatchdogTimer:null,shutdownFn:null,historyManager:null,analyticsManager:null,executionContextManager:null,licenseState:null,syncController:null,internalCommandExecutor:null,dashboardFolderPicker:null,activeSyncOwnerInstanceId:null,activeProjectRoot:null,pendingDashboardSyncRootPin:null,playtestControlCommand:null,dashboardSyncRootSwitchInFlight:!1,aiClientName:"",pluginVersion:"",syncedSessionToken:null,serverLastCommandAt:null}}var J2=Rt(tc(),1);ce();function K2(t){let e=J2.default.json({limit:"5mb"});t.app.use((n,i,r)=>{if(n.path.startsWith("/sync/")){r();return}e(n,i,r)}),t.app.use((n,i,r)=>{i.setHeader("Access-Control-Allow-Origin","http://localhost:3002"),i.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),i.setHeader("Access-Control-Allow-Headers","Content-Type"),r()}),t.app.use((n,i,r)=>{g.debug(`${n.method} ${n.path}`,{ip:n.ip}),r()})}_p();import{randomUUID as f6}from"crypto";ce();function Aoe(){let t=process.env.WEPPY_ROBLOX_MCP_VERSION?.trim();return t||null}var Qe=Aoe()??"2.6.2";jp();function tv(t){let e=new Set;t.aiClientName&&e.add(t.aiClientName);for(let n of t.mcpInstances.values())n.aiClientName&&e.add(n.aiClientName);return Array.from(e)}function n6(t){let n=Date.now();for(let[i,r]of t.mcpInstances)r.lastSeen&&n-r.lastSeen>15e3&&(t.mcpInstances.delete(i),r.sessionId&&t.executionContextManager?.endSession(r.sessionId),g.debug("Removed stale MCP instance",{instanceId:i,lastSeen:r.lastSeen}))}function r6(t,e,n){let i=e.query.clientId;if(i&&t.pluginClients.has(i)){let r=t.pluginClients.get(i);r.lastSeen=Date.now()}try{let r=e.body;if(!r||!Array.isArray(r.selection)||typeof r.count!="number"){g.warn("Invalid selection update request",{body:r}),n.status(400).json({error:"Invalid request body"});return}let a=i||"unknown",o=Date.now();t.cachedSelectionMap.set(a,{selection:r.selection,count:r.count,timestamp:o,clientId:a}),g.debug("Selection cache updated",{count:r.count,clientId:a,timestamp:o}),n.json({status:"ok",timestamp:o})}catch(r){g.error("Error handling selection update",r),n.status(500).json({error:"Internal server error"})}}function i6(t,e,n){let i=parseInt(e.query.maxAge)||3e4,r=Ap(t,i);r?n.json({cached:!0,...r}):n.json({cached:!1,message:"No cached selection available"})}function Ap(t,e=3e4,n){if(t.cachedSelectionMap.size===0)return null;let i;if(n)i=t.cachedSelectionMap.get(n);else for(let a of t.cachedSelectionMap.values())(!i||a.timestamp>i.timestamp)&&(i=a);if(!i)return null;let r=Date.now()-i.timestamp;return e===0||r<=e?i:null}function $I(t,e){let n=t.pluginClients.get(e.clientId),i=Date.now(),r={clientId:e.clientId,projectName:e.projectName,placeName:e.placeName,placeId:e.placeId,pluginVersion:e.pluginVersion,connectedAt:n?.connectedAt||i,lastSeen:i,commandsProcessed:n?.commandsProcessed||0,connectionType:n?.connectionType||"polling",inFlightRequestId:n?.inFlightRequestId,...n?.commandSessionState?{commandSessionState:n.commandSessionState}:{},...n?.processToken?{processToken:n.processToken}:{}};return t.pluginClients.set(e.clientId,r),t.syncController&&typeof t.syncController.setCurrentConnectedPlace=="function"&&typeof e.placeId=="number"&&Number.isFinite(e.placeId)&&t.syncController.setCurrentConnectedPlace(e.placeId,e.placeName??null),t.pendingCommands.has(e.clientId)||t.pendingCommands.set(e.clientId,[]),e.pluginVersion&&(t.pluginVersion=e.pluginVersion),typeof r.placeId=="number"&&Number.isFinite(r.placeId)&&tI(t,r.placeId,r.placeName??null),t.analyticsManager&&(e.pluginVersion&&t.analyticsManager.setPluginVersion(e.pluginVersion),t.analyticsManager.trackPluginConnected()),g.info("Plugin client registered",{clientId:e.clientId,projectName:e.projectName,placeName:e.placeName,isReconnect:!!n}),{clientInfo:r,sessionId:t.sessionId,mcpVersion:Qe,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:tv(t)}}function TI(t,e){let n=t.pluginClients.get(e),i=t.pluginClients.has(e);if(t.pluginClients.delete(e),t.pendingCommands.delete(e),i&&t.syncController&&typeof t.syncController.clearCurrentConnectedPlaceIfMatch=="function"&&typeof n?.placeId=="number"&&Number.isFinite(n.placeId)){let r=[...t.pluginClients.values()].filter(a=>typeof a.placeId=="number"&&Number.isFinite(a.placeId)).sort((a,o)=>o.lastSeen-a.lastSeen)[0];r?t.syncController.setCurrentConnectedPlace?.(r.placeId??null,r.placeName??null):t.syncController.clearCurrentConnectedPlaceIfMatch(n.placeId)}return i&&(tt(t,"connection",{clientId:e,status:"disconnected"}),lc(t,{timestamp:new Date().toISOString(),type:"plugin",status:"disconnected",clientId:e,message:`Plugin disconnected \u2014 ${e}`})),g.info("Plugin client unregistered",{clientId:e,existed:i}),i}function nv(t,e,n){try{let i=e.body;if(!i.clientId){n.status(400).json({error:"Missing clientId"});return}let r=$I(t,{clientId:i.clientId,projectName:i.projectName,placeName:i.placeName,placeId:i.placeId,pluginVersion:i.pluginVersion});n.json({status:"ok",clientId:i.clientId,serverInstanceId:t.instanceId,sessionId:r.sessionId,mcpVersion:r.mcpVersion,connectedAt:r.clientInfo.connectedAt,aiClientNames:r.aiClientNames,serverStartTime:t.startTime,mcpInstanceCount:r.mcpInstanceCount})}catch(i){g.error("Error registering plugin client",i),n.status(500).json({error:"Internal server error"})}}function rv(t,e,n){let i=e.body?.clientId;if(!i){n.status(400).json({error:"Missing clientId"});return}let r=TI(t,i);n.json({status:"ok",existed:r})}function a6(t,e,n){try{let i=e.body;if(!i.instanceId){n.status(400).json({error:"Missing instanceId"});return}let r=Date.now(),a={instanceId:i.instanceId,...typeof i.sessionId=="string"?{sessionId:i.sessionId}:{},pid:i.pid,connectedAt:r,isServer:!1,lastSeen:r};i.aiClientName&&(a.aiClientName=i.aiClientName),i.cwd&&(a.cwd=i.cwd),"projectRoot"in i&&(a.projectRoot=i.projectRoot),t.mcpInstances.set(i.instanceId,a),tt(t,"mcp_status",{aiClientName:a.aiClientName??"Unknown",instanceId:i.instanceId,status:"registered"}),lc(t,{timestamp:new Date().toISOString(),type:"mcp",status:"registered",instanceId:i.instanceId,message:`MCP registered \u2014 ${a.aiClientName??i.instanceId}`,...a.aiClientName?{aiClientName:a.aiClientName}:{}}),g.info("MCP instance registered (client mode)",{instanceId:i.instanceId,pid:i.pid,cwd:i.cwd}),n.json({status:"ok",instanceId:i.instanceId,serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Qe,mcpInstanceCount:t.mcpInstances.size+1})}catch(i){g.error("Error registering MCP instance",i),n.status(500).json({error:"Internal server error"})}}function o6(t,e,n){let i=e.body?.instanceId;if(!i){n.status(400).json({error:"Missing instanceId"});return}let r=t.mcpInstances.get(i),a=!!r;t.mcpInstances.delete(i),r?.sessionId&&t.executionContextManager?.endSession(r.sessionId),a&&(tt(t,"mcp_status",{aiClientName:r?.aiClientName??"Unknown",instanceId:i,status:"unregistered"}),lc(t,{timestamp:new Date().toISOString(),type:"mcp",status:"unregistered",instanceId:i,message:`MCP unregistered \u2014 ${r?.aiClientName??i}`,...r?.aiClientName?{aiClientName:r.aiClientName}:{}})),g.info("MCP instance unregistered",{instanceId:i,existed:a}),n.json({status:"ok",existed:a})}function s6(t,e,n){let{instanceId:i,aiClientName:r}=e.body;if(i&&r){let a=t.mcpInstances.get(i);a&&(a.aiClientName=r)}n.json({status:"ok"})}function c6(t){let e=3e4,n=Date.now(),i=Xn({appDataDir:t.config.appDataDir});for(let[r,a]of t.pluginClients)n-a.lastSeen>e&&(t.pluginClients.delete(r),t.pendingCommands.delete(r));return n6(t),{serverInstanceId:t.instanceId,sessionId:t.sessionId,mcpVersion:Qe,uptime:n-t.startTime,serverStartTime:t.startTime,serverExecutable:process.execPath,serverHost:t.config.httpHost,serverPort:t.config.httpPort,serverPid:process.pid,mcpInstances:[{instanceId:t.instanceId,pid:process.pid,connectedAt:t.startTime,isServer:!0,cwd:process.cwd(),projectRoot:i,lastSeen:n,...t.aiClientName?{aiClientName:t.aiClientName}:{}},...Array.from(t.mcpInstances.values())],mcpInstanceCount:t.mcpInstances.size+1}}function iv(t,e){e.json(c6(t))}function av(t,e,n){let i=e.query.instanceId;i&&t.mcpInstances.has(i)&&(t.mcpInstances.get(i).lastSeen=Date.now()),n6(t);let r=Ja(t),s={...{status:"online",connectedClients:r.length,queuedCommands:n2(t),uptime:Date.now()-t.startTime,version:Qe,enableContextCapture:t.executionContextManager?.isEnabled()??t.config.enableContextCapture??!0,isClientMode:t.isClientMode,pid:process.pid,sessionId:t.sessionId},instanceId:t.instanceId,mcpInstanceCount:t.mcpInstances.size+1,aiClientNames:tv(t),pluginVersion:t.pluginVersion||void 0,dashboardSseClients:t.dashboardSseClients?.size??0,websocketClients:r.length,pluginClients:r.map(c=>({clientId:c.clientId,projectName:c.projectName,placeName:c.placeName,pluginVersion:c.pluginVersion,connectionType:c.connectionType,commandSessionState:c.commandSessionState,lastSeen:c.lastSeen})),...t.isClientMode?{upstream:{reachable:t.clientModeUpstreamReachable,consecutiveFailures:t.clientModeConsecutiveHealthFailures,lastSuccessAt:t.clientModeLastHealthSuccessAt,lastFailureAt:t.clientModeLastHealthFailureAt,lastError:t.clientModeLastHealthError,baseUrl:t.baseUrl}}:{}};n.json(s)}function l6(t,e,n,i){let r=e.ip||e.socket.remoteAddress||"";if(!(r==="127.0.0.1"||r==="::1"||r==="::ffff:127.0.0.1"||r==="localhost")){g.warn("Shutdown request rejected from non-localhost",{ip:r}),n.status(403).json({error:"Forbidden: localhost only"});return}g.info("Shutdown request received, initiating graceful shutdown",{requestedBy:r,uptime:Date.now()-t.startTime}),n.json({status:"shutting_down",message:"Server will shutdown gracefully",pid:process.pid}),setTimeout(async()=>{try{await i(),g.info("Graceful shutdown completed"),process.exit(0)}catch(o){g.error("Error during graceful shutdown",o),process.exit(1)}},100)}async function ov(t){if(t.isClientMode)try{let e=await fetch(`${t.baseUrl}/connection-info`);if(e.ok)return await e.json()}catch(e){g.warn("Failed to fetch connection info from server",{error:e})}return c6(t)}ce();Op();var Sc={query_instances:{discriminator:"action",validActions:new Set(["get","children","find_child","find_descendant","wait_for_child","class_info","search_name","search_class","search_property","search_tag","file_tree","project_structure","descendants","ancestors"]),paramAliases:{query_instances_search_name:{query:"pattern"},query_instances_search_property:{root:"rootPath"},query_instances_search_tag:{root:"rootPath"},query_instances_project_structure:{root:"rootPath"}}},mutate_instances:{discriminator:"action",validActions:new Set(["create","create_with_props","delete","clone","move","rename","pivot","create_tree","mass_create","mass_delete","mass_duplicate","smart_duplicate"]),paramAliases:{mutate_instances_clone:{path:"sourcePath"}}},manage_properties:{discriminator:"action",validActions:new Set(["get","set","get_all","set_multiple","get_attr","set_attr","get_all_attrs","delete_attr","add_tag","remove_tag","check_tag","get_tags","get_tagged","set_calculated","set_relative","mass_set","mass_get","modify_children"]),paramAliases:{manage_properties_get_tagged:{tagName:"tag",root:"rootPath"},manage_properties_set_relative:{amount:"value"}}},manage_scripts:{discriminator:"action",validActions:new Set(["get_source","set_source","create","delete","edit_replace","edit_insert","edit_delete","search","replace","get_dependencies"]),paramAliases:{manage_scripts_edit_replace:{newLines:"newContent"},manage_scripts_edit_insert:{lines:"content"},manage_scripts_replace:{pattern:"searchPattern"}}},manage_lighting:{discriminator:"action",validActions:new Set(["lighting","atmosphere","sky","terrain_props","time"])},manage_selection:{discriminator:"action",validActions:new Set(["get","set","clear","cached","context","details","add","remove","watch"])},manage_camera:{discriminator:"action",validActions:new Set(["info","focus_path","focus_position","suggest","screenshot"]),paramAliases:{manage_camera_suggest:{path:"targetPath"}}},manage_tween:{discriminator:"action",validActions:new Set(["create","play","pause","cancel"])},manage_audio:{discriminator:"action",validActions:new Set(["play","stop","pause","resume","set_listener"])},manage_animation:{discriminator:"action",validActions:new Set(["load","play","stop","get_tracks"])},manage_physics:{discriminator:"action",validActions:new Set(["register_group","set_collidable","get_groups"])},manage_effects:{discriminator:"action",validActions:new Set(["emit","clear","toggle"])},manage_terrain:{discriminator:"action",validActions:new Set(["fill_block","fill_ball","fill_cylinder","fill_wedge","clear_region","clear_bounds","replace_material","colors_get","colors_set","read_voxel","read_voxels","write_voxels","generate","smooth"])},spatial_query:{discriminator:"action",validActions:new Set(["raycast","find_ground","check_placement","multi_raycast","scan_area","find_flat","find_spawn","analyze_walkable","spatial_map","find_space","bounds","snap_grid","collision"]),paramAliases:{spatial_query_spatial_map:{path:"rootPath"}}},manage_assets:{discriminator:"action",validActions:new Set(["insert","info","search","search_insert","insert_free","insert_package","export"]),paramAliases:{manage_assets_search:{maxResults:"limit"}}},manage_sync:{discriminator:"action",validActions:new Set(["status_current_place","history","directions","read_file","write_file","progress"])},workspace_state:{discriminator:"action",validActions:new Set(["sync","snapshot","changes","viewport","clear_history","metadata","scripts","selection_info","clear_cache"])},manage_logs:{discriminator:"action",validActions:new Set(["get","clear","errors"]),paramAliases:{manage_logs_get:{level:"type"}}},system_info:{discriminator:"action",validActions:new Set(["ping","connection","usage","place_info","services","studio_settings","play","stop","pause","resume","play_status","run_test"])}};var Loe={manage_selection_cached:"internal",manage_sync_status_current_place:"internal",manage_sync_history:"internal",manage_sync_directions:"internal",manage_sync_read_file:"internal",manage_sync_write_file:"internal",manage_sync_progress:"internal",system_info_connection:"internal",system_info_run_test:"internal"};function sv(t){return Loe[t]||"plugin"}var u6=new Set(["batch_execute","execute_luau","extended_call_method","extended_call_methods","extended_get_properties","extended_get_property","extended_set_properties","extended_set_property","manage_animation_get_tracks","manage_animation_load","manage_animation_play","manage_animation_stop","manage_assets_export","manage_assets_info","manage_assets_insert","manage_assets_insert_free","manage_assets_insert_package","manage_assets_search","manage_assets_search_insert","manage_audio_pause","manage_audio_play","manage_audio_resume","manage_audio_set_listener","manage_audio_stop","manage_camera_screenshot","manage_effects_clear","manage_effects_emit","manage_effects_toggle","manage_lighting_atmosphere","manage_lighting_lighting","manage_lighting_sky","manage_lighting_terrain_props","manage_lighting_time","manage_physics_get_groups","manage_physics_register_group","manage_physics_set_collidable","manage_properties_mass_get","manage_properties_mass_set","manage_properties_modify_children","manage_properties_set_calculated","manage_properties_set_relative","manage_scripts_replace","manage_selection_add","manage_selection_context","manage_selection_details","manage_selection_remove","manage_selection_watch","manage_sync_directions","manage_sync_history","manage_sync_progress","manage_sync_read_file","manage_sync_status_current_place","manage_sync_write_file","manage_terrain_clear_bounds","manage_terrain_clear_region","manage_terrain_colors_get","manage_terrain_colors_set","manage_terrain_fill_ball","manage_terrain_fill_block","manage_terrain_fill_cylinder","manage_terrain_fill_wedge","manage_terrain_generate","manage_terrain_read_voxel","manage_terrain_read_voxels","manage_terrain_replace_material","manage_terrain_smooth","manage_terrain_write_voxels","manage_tween_cancel","manage_tween_create","manage_tween_pause","manage_tween_play","mutate_instances_create_tree","mutate_instances_mass_create","mutate_instances_mass_delete","mutate_instances_mass_duplicate","mutate_instances_smart_duplicate","query_instances_ancestors","query_instances_descendants","query_instances_file_tree","query_instances_project_structure","query_instances_search_property","query_instances_search_tag","spatial_query_analyze_walkable","spatial_query_bounds","spatial_query_check_placement","spatial_query_collision","spatial_query_find_flat","spatial_query_find_ground","spatial_query_find_space","spatial_query_find_spawn","spatial_query_multi_raycast","spatial_query_raycast","spatial_query_scan_area","spatial_query_snap_grid","spatial_query_spatial_map","system_info_pause","system_info_place_info","system_info_play","system_info_play_status","system_info_resume","system_info_run_test","system_info_services","system_info_stop","system_info_studio_settings","workspace_state_changes","workspace_state_clear_cache","workspace_state_clear_history","workspace_state_metadata","workspace_state_scripts","workspace_state_selection_info","workspace_state_snapshot","workspace_state_sync","workspace_state_viewport"]),RI=new Set(["system_info_connection","system_info_ping","system_info_usage"]);function rr(t){return u6.has(t)?"pro":"basic"}function Uoe(t){if(t===null||typeof t!="object")return;let e=t.proFallback;if(e===null||typeof e!="object")return;let n=e;if(typeof n.executedCommand!="string")return;let i=Array.isArray(n.alternatives)?n.alternatives.filter(r=>typeof r=="string"):void 0;return{executedCommand:n.executedCommand,...typeof n.requestedCommand=="string"?{requestedCommand:n.requestedCommand}:{},...i?{alternatives:i}:{}}}var Foe=/^Pro action '[^']+' is blocked in Basic mode\. Basic fallback '([^']+)' failed: (.*)$/s,qoe=/^Pro action '[^']+' is blocked in Basic mode\.(?! Basic fallback ')/,Boe=/instance not found|not found in|parent not found/,Zoe=/is required|missing required/,Hoe=/unknown action|unknown command/,Voe=/must be|invalid|cannot/;function Woe(t){let e=Foe.exec(t);return e?{fallbackCommand:e[1],reason:e[2]}:null}function Goe(t){return qoe.test(t)}function Joe(t){let e=t.toLowerCase();return Boe.test(e)?"not_found":Zoe.test(e)?"missing_param":Hoe.test(e)?"unknown_action":Voe.test(e)?"validation":"other"}function wc(t){if(t.resultSuccess){let n=Uoe(t.resultData);return n?{kind:"success_fallback",fallbackCommand:n.executedCommand,...n.alternatives?{alternatives:n.alternatives}:{}}:{kind:"success_ok"}}if(t.tier!=="pro"||!t.resultError)return{kind:"error"};let e=Woe(t.resultError);return e?{kind:"blocked_fallback_failed",fallbackCommand:e.fallbackCommand,blockedDetail:Joe(e.reason)}:Goe(t.resultError)?{kind:"blocked_unsupported"}:{kind:"error"}}var OI=100,Koe=12e4,Xoe=Object.entries(Sc).reduce((t,[e,n])=>{for(let i of n.validActions)t[`${e}_${i}`]=e;return t},{});function p6(t){return{toolName:Xoe[t]||t,commandName:t}}function d6(t,e){if(typeof t.contextId=="string"||!e||typeof e!="object")return t;let n=e.contextId;return typeof n=="string"?{...t,contextId:n}:t}function cv(t,e){if(t!=="manage_sync_status_current_place")return e;let{placeId:n,...i}=e;return i}async function NI(t,e,n,i,r,a){if(!t.historyManager)return;let o=wc({resultSuccess:r.success,resultData:r.data,resultError:r.error,tier:rr(n)});if(r.success){let s=o.kind==="success_fallback"&&r.data&&typeof r.data=="object"?{...r.data,outcomeStatus:"fallback",requestedCommand:n,executedCommand:o.fallbackCommand,...o.alternatives?{alternatives:o.alternatives}:{}}:r.data;await t.historyManager.recordSuccess(e,cv(n,i),s,a,n);return}if(o.kind==="blocked_unsupported"){await t.historyManager.recordBlockedOutcome(e,cv(n,i),r.error||"Pro action blocked in Basic mode","blocked_unsupported",{executionTimeMs:a,command:n});return}if(o.kind==="blocked_fallback_failed"){await t.historyManager.recordBlockedOutcome(e,cv(n,i),r.error||"Pro action blocked in Basic mode","blocked_fallback_failed",{executionTimeMs:a,command:n,...o.fallbackCommand?{fallbackCommand:o.fallbackCommand}:{},...o.blockedDetail?{blockedDetail:o.blockedDetail}:{}});return}await t.historyManager.recordFailure(e,cv(n,i),r.error||"Unknown error",a,void 0,n)}function Yoe(t,e,n){if(!e)return;let i=t.pluginClients.get(e);i?.inFlightRequestId===n&&(i.inFlightRequestId=void 0)}function Qoe(t,e,n){if(g.debug("Broadcasting command",{command:e.data.command,requestId:e.data.requestId,targetClientId:n||"all",activeWebsocketClients:Ja(t).length}),n){let i=t.pendingCommands.get(n)||[];i.length>=OI&&i.shift(),i.push(e),t.pendingCommands.set(n,i)}else{let i=Ja(t).sort((o,s)=>s.lastSeen-o.lastSeen),r=e.data.params?.placeId,a;if(r!==void 0&&i.length>1&&(a=i.find(o=>o.placeId===r),a&&g.debug("Routed command by placeId",{clientId:a.clientId,placeId:r})),a||(a=i[0],i.length>1&&g.warn("Multiple plugin clients connected, routing to most recent",{targetClientId:a?.clientId,targetPlaceId:a?.placeId,totalClients:i.length,command:e.data.command})),a){let o=t.pendingCommands.get(a.clientId)||[];o.length>=OI&&o.shift(),o.push(e),t.pendingCommands.set(a.clientId,o),g.debug("Routed command to client",{clientId:a.clientId,projectName:a.projectName,placeId:a.placeId})}else t.globalPendingCommands.length>=OI&&t.globalPendingCommands.shift(),t.globalPendingCommands.push(e)}KC(t,n)}async function m6(t,e,n){let i=Date.now(),r="",a={};try{let o=e.body;r=o.command??"",a=o.params||{};let s=o.requestId;if(o.instanceId||(t.serverLastCommandAt=Date.now()),o.instanceId){let m=t.mcpInstances.get(o.instanceId);m&&(m.lastCommandAt=Date.now(),m.lastSeen=m.lastCommandAt)}let c=p6(r),l=typeof o.timeout=="number"&&Number.isFinite(o.timeout)&&o.timeout>0?{timeout:o.timeout}:void 0,u=o.instanceId?t.mcpInstances.get(o.instanceId)?.sessionId??t.sessionId:t.sessionId;if(!r){n.status(400).json({error:"Missing command"});return}if(g.debug("Received execute request",{command:r,requestId:s}),r==="manage_camera_play_screenshot"){let m="manage_camera.play_screenshot is not supported. Screenshot capture is available only in Edit mode via manage_camera.screenshot.",h=Date.now()-i;t.historyManager&&await t.historyManager.recordFailure("manage_camera",a,m,h,"UNSUPPORTED_COMMAND",r),n.json({requestId:s,success:!1,error:m});return}if(r==="get_cached_selection"){let m=a.maxAge,h=Ap(t,m!==void 0?m:3e4),y=Date.now()-i;if(!h){let S={requestId:s,success:!0,data:{cached:!1,message:"No cached selection data available. The plugin may not be connected or no selection changes have occurred yet."}};t.historyManager&&await t.historyManager.recordSuccess(c.toolName,a,S.data,y,c.commandName),n.json(S);return}let v=Date.now()-h.timestamp,_={cached:!0,selection:h.selection,count:h.count,timestamp:h.timestamp,age:v};t.historyManager&&await t.historyManager.recordSuccess(c.toolName,a,_,y,c.commandName),n.json({requestId:s,success:!0,data:_});return}if(r==="get_connection_info"){let m=await ov(t),h=Date.now()-i;t.historyManager&&await t.historyManager.recordSuccess(c.toolName,a,m,h,c.commandName),n.json({requestId:s,success:!0,data:m});return}if(sv(r)==="internal"){if(!t.internalCommandExecutor){n.status(503).json({requestId:s,success:!1,error:`Internal command executor is not initialized for command: ${r}`});return}let m={...a,__sessionId:u},h=await t.internalCommandExecutor(r,m),y=Date.now()-i,v=d6(m,h.data);t.historyManager&&await NI(t,c.toolName,c.commandName,v,{success:h.success,data:h.data,error:h.error||"Internal command failed"},y),n.json({requestId:s,...h});return}let p=await kc(t,r,a,s,l),d=Date.now()-i,f={...d6(a,p.data),__sessionId:u};t.historyManager&&await NI(t,c.toolName,c.commandName,f,p,d),n.json(p)}catch(o){let s=Date.now()-i,c=nr(o);if(t.historyManager&&r){let l=p6(r);await t.historyManager.recordFailure(l.toolName,a,c,s,"EXCEPTION",l.commandName)}g.error("Execute request failed",o),n.status(500).json({success:!1,error:c})}}async function h6(t,e,n){let i=Date.now(),r=e.body,a=r.action;if(!a||typeof a!="string"){n.status(400).json({success:!1,error:"Missing required field: action"});return}if(!new Set(["get","set","get_all","set_multiple","get_attr","set_attr","get_all_attrs","delete_attr","add_tag","remove_tag","check_tag","get_tags","get_tagged"]).has(a)){n.status(400).json({success:!1,error:`Unsupported action: ${a}`});return}let s=`manage_properties_${a}`,c=f6(),{action:l,...u}=r;try{t.serverLastCommandAt=Date.now();let p=await kc(t,s,u,c),d=Date.now()-i;t.historyManager&&await NI(t,"manage_properties",s,u,p,d),n.json(p)}catch(p){let d=Date.now()-i,f=nr(p);t.historyManager&&await t.historyManager.recordFailure("manage_properties",u,f,d,"EXCEPTION",s),g.error("/api/properties \uC694\uCCAD \uCC98\uB9AC \uC2E4\uD328",p),n.status(500).json({success:!1,error:f})}}async function kc(t,e,n,i,r){g.debug("Executing command locally",{command:e,requestId:i});let a=t.config.requestTimeout,o=r?.timeout??Math.max(t.config.requestTimeout,Koe),s=new Promise((c,l)=>{let u={requestId:i,command:e,params:n,timestamp:Date.now(),queueTimeoutMs:a,ackedTimeoutMs:o,state:un.QUEUED,resolve:c,reject:l,timeoutId:null};t.commandQueue.set(i,u),u.timeoutId=setTimeout(()=>{t.commandQueue.delete(u.requestId),Yoe(t,u.targetClientId,u.requestId),u.reject(new Error(`Command timeout after ${a}ms: ${u.command}`))},a)});return Qoe(t,{event:"command",id:f6(),data:{command:e,requestId:i,params:n}}),s}ce();function lv(t,e){let n={};e.provider!==void 0&&(n.provider=e.provider),e.tier!==void 0&&(n.tier=e.tier),e.status!==void 0&&(n.status=e.status),e.canUsePro!==void 0&&(n.canUsePro=e.canUsePro),e.cleared===!0&&(n.cleared=!0),tt(t,"license",n)}function ir(t,e,n,i){t.status(e).json({success:!1,error:{code:n,message:i}})}function ese(t){if(typeof t!="string")return;let e=t.trim().toLowerCase();return e.length>0?e:void 0}function uv(t,e){if(e==="plugin")return t;let n={...t};return delete n.sessionToken,n}function zp(t,e,n,i){let r=ese(i??e.query.provider);return r?t.licenseState?.supportsProvider(r)===!1?(ir(n,400,"LICENSE_PROVIDER_UNSUPPORTED",`provider "${r}" is not supported`),null):r:(ir(n,400,"LICENSE_PROVIDER_REQUIRED","provider is required"),null)}function Mp(t,e){return t.licenseState?!0:(ir(e,503,"LICENSE_NOT_INITIALIZED","License system is not initialized"),!1)}function tse(t){if(!t||typeof t!="object")return!1;let e=t;return(e.canUsePro===!0||e.canUsePro===!1)&&typeof e.status=="string"&&typeof e.checkedAt=="number"}function Lp(t,e,n,i="plugin"){if(!Mp(t,n))return;let r=e.body,a=typeof r?.licenseKey=="string"?r.licenseKey:"";if(!a.trim()){ir(n,400,"LICENSE_KEY_REQUIRED","licenseKey is required");return}let o=zp(t,e,n,r?.provider);if(!o)return;let s=typeof r?.pluginClientId=="string"?r.pluginClientId:typeof e.query.clientId=="string"?e.query.clientId:void 0,c=typeof r?.deviceId=="string"?r.deviceId:void 0,l={licenseKey:a,provider:o,clientType:i};s&&(l.pluginClientId=s),c&&(l.deviceId=c),t.licenseState.activateGateway(l).then(u=>{t.analyticsManager?.setTier(u.canUsePro?"pro":"basic"),lv(t,{provider:o,tier:u.canUsePro?"pro":"basic",status:u.status,canUsePro:u.canUsePro}),n.json(uv(u,i))}).catch(u=>{g.warn("License activate failed",{error:u instanceof Error?u.message:"unknown_error"}),ir(n,502,"LICENSE_ACTIVATE_FAILED",u instanceof Error?u.message:"License activation failed")})}function Up(t,e,n,i="plugin"){if(!Mp(t,n))return;let r=zp(t,e,n);if(r)try{let a=t.licenseState.getStatus(r);t.analyticsManager?.setTier(a.canUsePro?"pro":"basic"),n.json(uv(a,i))}catch(a){g.warn("License status check failed",{error:a instanceof Error?a.message:"unknown_error"}),ir(n,502,"LICENSE_STATUS_FAILED",a instanceof Error?a.message:"License status check failed")}}function Fp(t,e,n,i="plugin"){if(!Mp(t,n))return;let r=e.body,a=zp(t,e,n,r?.provider);if(!a)return;let o={provider:a,clientType:i};i==="plugin"&&typeof r?.sessionToken=="string"&&r.sessionToken.trim()&&(o.sessionToken=r.sessionToken.trim()),typeof r?.pluginClientId=="string"&&(o.pluginClientId=r.pluginClientId),typeof r?.deviceId=="string"&&(o.deviceId=r.deviceId),t.licenseState.refreshGateway(o).then(s=>{t.analyticsManager?.setTier(s.canUsePro?"pro":"basic"),lv(t,{provider:a,tier:s.canUsePro?"pro":"basic",status:s.status,canUsePro:s.canUsePro}),n.json(uv(s,i))}).catch(s=>{g.warn("License refresh failed",{error:s instanceof Error?s.message:"unknown_error"}),ir(n,502,"LICENSE_REFRESH_FAILED",s instanceof Error?s.message:"License refresh failed")})}function qp(t,e,n,i="plugin"){if(!Mp(t,n))return;let r=e.body,a=zp(t,e,n,r?.provider);a&&t.licenseState.resetGateway({provider:a,clientType:i}).then(o=>{t.analyticsManager?.setTier("basic"),lv(t,{provider:a,tier:"basic",status:o.status,canUsePro:o.canUsePro,cleared:!0}),n.json(uv(o,i))}).catch(o=>{g.warn("License reset failed",{error:o instanceof Error?o.message:"unknown_error"}),ir(n,502,"LICENSE_RESET_FAILED",o instanceof Error?o.message:"License reset failed")})}function DI(t,e,n){if(!Mp(t,n))return;let i=e.body,r=zp(t,e,n,i?.provider);if(!r)return;if(i?.clientType!=="plugin"){ir(n,400,"LICENSE_CLIENT_TYPE_INVALID","clientType must be plugin");return}let a=typeof i?.sessionToken=="string"&&i.sessionToken.trim()?i.sessionToken.trim():void 0,o=tse(i?.snapshot)?i.snapshot:void 0;if(!o&&!a){ir(n,400,"LICENSE_BOOTSTRAP_REQUIRED","snapshot or sessionToken is required");return}let s={provider:r,clientType:"plugin"};o&&(s.snapshot=o),a&&(s.sessionToken=a),t.licenseState.bootstrap(s).then(c=>{t.analyticsManager?.setTier(c.canUsePro?"pro":"basic"),lv(t,{provider:r,tier:c.canUsePro?"pro":"basic",status:c.status,canUsePro:c.canUsePro}),n.json({ok:!0})}).catch(c=>{g.warn("License bootstrap failed",{error:c instanceof Error?c.message:"unknown_error"}),ir(n,502,"LICENSE_BOOTSTRAP_FAILED",c instanceof Error?c.message:"License bootstrap failed")})}function g6(t){t.app.post("/license/bootstrap",(e,n)=>DI(t,e,n)),t.app.post("/license/activate",(e,n)=>Lp(t,e,n,"plugin")),t.app.post("/license/refresh",(e,n)=>Fp(t,e,n,"plugin")),t.app.post("/license/reset",(e,n)=>qp(t,e,n,"plugin")),t.app.get("/license/status",(e,n)=>Up(t,e,n,"plugin"))}Pt();ce();import{promises as pv}from"fs";import jI from"path";function nse(t){let e=t.config.appDataDir??tn();return jI.join(e,"observability","logs")}async function Cc(t,e,n){let i=e.query.clientId;if(i&&t.pluginClients.has(i)){let r=t.pluginClients.get(i);r.lastSeen=Date.now()}try{let r=e.body;if(!r||!Array.isArray(r.logs)){g.warn("Invalid logs request",{body:r}),n.status(400).json({error:"Invalid request body"});return}let a=nse(t);await pv.mkdir(a,{recursive:!0});let c=`plugin-${new Date().toISOString().split("T")[0]}.log`,l=jI.join(a,c),u=jI.join(a,"current.log"),p=r.logs.map(d=>`[${new Date(d.timestamp*1e3).toISOString().replace("T"," ").substring(0,19)}] [${d.level}] ${d.message}`).join(`
140
140
  `)+`
141
141
  `;await pv.appendFile(l,p,"utf-8");try{let d="";try{d=await pv.readFile(u,"utf-8")}catch{}let m=(d+p).split(`
142
142
  `).slice(-500).join(`