@adiholick/nvstudio-mcp 2.0.2 → 2.0.4

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.
@@ -39,7 +39,7 @@ function buildRbxmx() {
39
39
  xml += `\t\t\t<bool name="DefinesCapabilities">false</bool>\n`;
40
40
  xml += `\t\t\t<string name="Name">nvstudio_mcp</string>\n`;
41
41
  xml += `\t\t\t<int64 name="SourceAssetId">-1</int64>\n`;
42
- xml += `\t\t\t<SharedString name="Tags"></SharedString>\n`;
42
+ xml += `\t\t\t<BinaryString name="Tags"></BinaryString>\n`;
43
43
  xml += `\t\t</Properties>\n`;
44
44
  // Process files
45
45
  for (const file of files) {
@@ -61,7 +61,7 @@ function buildRbxmx() {
61
61
  xml += `\t\t\t\t<Content name="LinkedSource"><null></null></Content>\n`;
62
62
  xml += `\t\t\t\t<string name="Name">${scriptName}</string>\n`;
63
63
  xml += `\t\t\t\t<int64 name="SourceAssetId">-1</int64>\n`;
64
- xml += `\t\t\t\t<SharedString name="Tags"></SharedString>\n`;
64
+ xml += `\t\t\t\t<BinaryString name="Tags"></BinaryString>\n`;
65
65
  if (isMain) {
66
66
  xml += `\t\t\t\t<token name="RunContext">0</token>\n`;
67
67
  }
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ function backupScript(target, code) {
27
27
  // 2. Inisialisasi server MCP
28
28
  const server = new index_js_1.Server({
29
29
  name: "nvstudio-mcp",
30
- version: "2.0.1",
30
+ version: "2.0.4",
31
31
  }, {
32
32
  capabilities: {
33
33
  tools: {},
@@ -89,6 +89,25 @@ function startBridgeServer(port = 3055) {
89
89
  app.get('/api/stats', (req, res) => {
90
90
  res.json({ totalTasks, successCount, errorCount, sessionStart, studioConnected, port });
91
91
  });
92
+ // Endpoint Studio Instant Ping (Handshake cepat tanpa menunggu long-polling)
93
+ app.get('/api/ping', (req, res) => {
94
+ if (!studioConnected) {
95
+ studioConnected = true;
96
+ io.emit('studio-status', { connected: true });
97
+ addLog(io, 'system', 'Roblox Studio terhubung ke Bridge Server via Ping.');
98
+ }
99
+ if (!dashboardOpened) {
100
+ dashboardOpened = true;
101
+ const url = `http://localhost:${port}`;
102
+ const startCmd = process.platform === 'win32' ? `start "" "${url}"` : process.platform === 'darwin' ? `open "${url}"` : `xdg-open "${url}"`;
103
+ (0, child_process_1.exec)(startCmd, (error) => {
104
+ if (error)
105
+ console.error(`[Bridge] Gagal membuka browser:`, error);
106
+ });
107
+ console.error(`[Bridge] Pertama kali terhubung dengan Studio! Membuka Dashboard...`);
108
+ }
109
+ res.json({ status: 'ok', studioConnected: true, timestamp: Date.now() });
110
+ });
92
111
  const waitingPollers = [];
93
112
  task_queue_1.taskEmitter.on('new_task', () => {
94
113
  if (task_queue_1.taskQueue.length > 0 && waitingPollers.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adiholick/nvstudio-mcp",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Local Bridge Server MCP for Roblox Studio",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,6 +1,7 @@
1
1
  local HttpService = game:GetService("HttpService")
2
2
  local LogService = game:GetService("LogService")
3
3
 
4
+ local PING_URL = "http://localhost:3055/api/ping"
4
5
  local TASK_URL = "http://localhost:3055/api/tasks"
5
6
  local RESPONSE_URL = "http://localhost:3055/api/response"
6
7
 
@@ -24,8 +25,10 @@ local ctx = {
24
25
  }
25
26
  local MAX_LOGS = 50
26
27
  local isConnected = false
27
- local autoConnect = plugin:GetSetting("AutoConnect") or false
28
+ local savedAutoConnect = plugin:GetSetting("AutoConnect")
29
+ local autoConnect = (savedAutoConnect == nil) and true or savedAutoConnect
28
30
  local isPolling = false
31
+ local isManualDisconnect = false -- Mencegah auto-reconnect saat user sengaja disconnect
29
32
  local isClickDebounce = false -- Mencegah rapid double-click
30
33
 
31
34
  -- Deklarasi fungsi polling di awal agar bisa dipanggil oleh UI
@@ -39,7 +42,11 @@ if uiModule then
39
42
 
40
43
  -- Sync Initial State ke UI
41
44
  ui:setAutoConnectState(autoConnect)
42
- ui:setStatus("disconnected")
45
+ if autoConnect then
46
+ ui:setStatus("waiting")
47
+ else
48
+ ui:setStatus("disconnected")
49
+ end
43
50
 
44
51
  -- Sync state tombol toolbar dengan status widget
45
52
  if ui.widget then
@@ -62,10 +69,29 @@ if uiModule then
62
69
  if not isConnected then
63
70
  -- CONNECT (manual) — reset manual disconnect flag agar auto-detect bisa bekerja
64
71
  isManualDisconnect = false
65
- isConnected = true
66
72
  ui:setStatus("waiting")
67
- ui:addLog("[System] Koneksi manual dimulai. Mencari AI Server (Waiting)...", Color3.fromRGB(255, 220, 100))
68
- startPolling()
73
+ ui:addLog("[System] Menghubungkan ke Server MCP...", Color3.fromRGB(255, 220, 100))
74
+
75
+ task.spawn(function()
76
+ local cacheBuster = "?t=" .. tostring(os.clock())
77
+ local success, _ = pcall(function()
78
+ return HttpService:GetAsync(PING_URL .. cacheBuster)
79
+ end)
80
+
81
+ isConnected = true
82
+ if success then
83
+ if ui then
84
+ ui:setStatus("connected")
85
+ ui:addLog("[System] Berhasil terhubung ke Server MCP!", Color3.fromRGB(100, 255, 100))
86
+ end
87
+ else
88
+ if ui then
89
+ ui:setStatus("waiting")
90
+ ui:addLog("[System] Server MCP belum aktif di port 3055. Menunggu koneksi...", Color3.fromRGB(255, 200, 100))
91
+ end
92
+ end
93
+ startPolling()
94
+ end)
69
95
  else
70
96
  -- DISCONNECT — hentikan polling dan tandai manual disconnect
71
97
  stopPolling()
@@ -193,8 +219,6 @@ local function processTask(taskData)
193
219
  end
194
220
 
195
221
  -- Menghentikan polling dengan bersih (dipanggil oleh tombol Disconnect)
196
- local isManualDisconnect = false -- Mencegah auto-reconnect saat user sengaja disconnect
197
-
198
222
  function stopPolling()
199
223
  isConnected = false
200
224
  isManualDisconnect = true
@@ -205,6 +229,7 @@ end
205
229
  function startPolling()
206
230
  if isPolling then return end
207
231
  isPolling = true
232
+ if ui and isConnected then ui:setStatus("connected") end
208
233
 
209
234
  task.spawn(function()
210
235
  while isConnected do
@@ -220,7 +245,7 @@ function startPolling()
220
245
  end
221
246
 
222
247
  if success and response then
223
- -- JIKA PING SUKSES, Node.js menyala (IDE / Agen Sedang Terbuka!)
248
+ -- JIKA PING/POLL SUKSES, Node.js menyala (IDE / Agen Sedang Terbuka!)
224
249
  if ui then ui:setStatus("connected") end
225
250
 
226
251
  local decodeSuccess, taskData = pcall(function()
@@ -243,7 +268,7 @@ function startPolling()
243
268
  else
244
269
  -- Fetch gagal (Node.js server mati karena IDE/Antigravity/Cursor ditutup)
245
270
  if ui then ui:setStatus("waiting") end
246
- task.wait(1)
271
+ task.wait(1.5)
247
272
  end
248
273
 
249
274
  -- Cek lagi sebelum sleep (agar disconnect lebih responsif)
@@ -257,54 +282,52 @@ end
257
282
 
258
283
  -- ══════════════════════════════════════════════════════════════════════
259
284
  -- AUTO-DETECT: Background heartbeat yang SELALU berjalan.
260
- -- Mendeteksi apakah server MCP aktif setiap 2 detik.
261
- -- Jika terdeteksi aktif → otomatis connect (tanpa perlu tekan tombol).
262
- -- Jika terdeteksi mati → otomatis update status.
285
+ -- Mendeteksi apakah server MCP aktif setiap 1.5 detik via PING_URL.
286
+ -- Menggunakan /api/ping agar instan (tidak tertahan 20 detik oleh long-poll).
263
287
  -- ══════════════════════════════════════════════════════════════════════
264
288
  task.spawn(function()
265
- task.wait(1) -- Tunggu sebentar agar UI siap
289
+ task.wait(0.5) -- Tunggu sebentar agar UI siap
266
290
 
267
291
  if ui then
268
292
  ui:addLog("[System] Plugin termuat. Auto-detect server MCP aktif...", Color3.fromRGB(200, 200, 200))
269
293
  end
270
294
 
271
295
  while true do
272
- -- Skip heartbeat jika polling loop sudah aktif (ia sudah menangani semuanya)
273
- if not isPolling then
274
- local success, errorMsg = pcall(function()
275
- local cacheBuster = "?t=" .. tostring(os.clock())
276
- return HttpService:GetAsync(TASK_URL .. cacheBuster)
277
- end)
278
-
279
- if success then
280
- -- Server terdeteksi aktif!
281
- if not isConnected and not isManualDisconnect then
282
- isConnected = true
283
- if ui then
284
- ui:setStatus("connected")
285
- ui:addLog("[System] Server MCP terdeteksi! Auto-connected.", Color3.fromRGB(100, 255, 100))
296
+ -- Hanya jalankan auto-detect jika user tidak sengaja menekan Disconnect
297
+ if not isManualDisconnect and (autoConnect or isConnected) then
298
+ if not isPolling then
299
+ local success, _ = pcall(function()
300
+ local cacheBuster = "?t=" .. tostring(os.clock())
301
+ return HttpService:GetAsync(PING_URL .. cacheBuster)
302
+ end)
303
+
304
+ if success then
305
+ -- Server terdeteksi aktif via Ping!
306
+ if not isConnected then
307
+ isConnected = true
308
+ if ui then
309
+ ui:setStatus("connected")
310
+ ui:addLog("[System] Server MCP terdeteksi! Terhubung otomatis.", Color3.fromRGB(100, 255, 100))
311
+ end
312
+ startPolling()
313
+ else
314
+ startPolling()
286
315
  end
287
- startPolling()
288
- end
289
- else
290
- -- Server tidak aktif
291
- if isConnected then
292
- -- Server baru saja mati (sebelumnya connected) — polling loop juga akan exit
293
- isConnected = false
294
- if ui then
316
+ else
317
+ -- Server tidak aktif / belum menyala
318
+ if isConnected then
319
+ isConnected = false
320
+ if ui then
321
+ ui:setStatus("waiting")
322
+ ui:addLog("[System] Server MCP terputus. Menunggu reconnect...", Color3.fromRGB(255, 200, 100))
323
+ end
324
+ elseif ui and ui.status ~= "waiting" and not isManualDisconnect then
295
325
  ui:setStatus("waiting")
296
- ui:addLog("[System] Server MCP terputus. Menunggu reconnect...", Color3.fromRGB(255, 200, 100))
297
- end
298
- elseif not isManualDisconnect then
299
- if ui then
300
- ui:setStatus("waiting")
301
- -- HANYA print error jika belum terkoneksi, untuk debugging
302
- ui:addLog("[Debug] Deteksi gagal: " .. tostring(errorMsg), Color3.fromRGB(150, 150, 150))
303
326
  end
304
327
  end
305
328
  end
306
329
  end
307
330
 
308
- task.wait(2)
331
+ task.wait(1.5)
309
332
  end
310
333
  end)
@@ -8,7 +8,7 @@
8
8
  <bool name="DefinesCapabilities">false</bool>
9
9
  <string name="Name">nvstudio_mcp</string>
10
10
  <int64 name="SourceAssetId">-1</int64>
11
- <SharedString name="Tags"></SharedString>
11
+ <BinaryString name="Tags"></BinaryString>
12
12
  </Properties>
13
13
  <Item class="ModuleScript" referent="RBX_create_instance">
14
14
  <Properties>
@@ -19,7 +19,7 @@
19
19
  <Content name="LinkedSource"><null></null></Content>
20
20
  <string name="Name">create_instance</string>
21
21
  <int64 name="SourceAssetId">-1</int64>
22
- <SharedString name="Tags"></SharedString>
22
+ <BinaryString name="Tags"></BinaryString>
23
23
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
24
24
  local success, config = pcall(function() return ctx.HttpService:JSONDecode(data) end)
25
25
  if not success or not config.ClassName then
@@ -56,7 +56,7 @@ end
56
56
  <Content name="LinkedSource"><null></null></Content>
57
57
  <string name="Name">delete_instance</string>
58
58
  <int64 name="SourceAssetId">-1</int64>
59
- <SharedString name="Tags"></SharedString>
59
+ <BinaryString name="Tags"></BinaryString>
60
60
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
61
61
  -- Guardrail: Cegah penghapusan Service utama
62
62
  local protectedServices = {
@@ -100,7 +100,7 @@ end
100
100
  <Content name="LinkedSource"><null></null></Content>
101
101
  <string name="Name">execute_luau</string>
102
102
  <int64 name="SourceAssetId">-1</int64>
103
- <SharedString name="Tags"></SharedString>
103
+ <BinaryString name="Tags"></BinaryString>
104
104
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
105
105
  local code = data
106
106
  if not code or code == "" then
@@ -184,7 +184,7 @@ end
184
184
  <Content name="LinkedSource"><null></null></Content>
185
185
  <string name="Name">generate_terrain</string>
186
186
  <int64 name="SourceAssetId">-1</int64>
187
- <SharedString name="Tags"></SharedString>
187
+ <BinaryString name="Tags"></BinaryString>
188
188
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
189
189
  local success, config = pcall(function() return ctx.HttpService:JSONDecode(data) end)
190
190
  if not success or not config.Size or not config.Position or not config.Material then
@@ -220,7 +220,7 @@ end
220
220
  <Content name="LinkedSource"><null></null></Content>
221
221
  <string name="Name">get_children</string>
222
222
  <int64 name="SourceAssetId">-1</int64>
223
- <SharedString name="Tags"></SharedString>
223
+ <BinaryString name="Tags"></BinaryString>
224
224
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
225
225
  local children = {}
226
226
  for _, child in ipairs(targetInstance:GetChildren()) do
@@ -240,7 +240,7 @@ end
240
240
  <Content name="LinkedSource"><null></null></Content>
241
241
  <string name="Name">get_logs</string>
242
242
  <int64 name="SourceAssetId">-1</int64>
243
- <SharedString name="Tags"></SharedString>
243
+ <BinaryString name="Tags"></BinaryString>
244
244
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
245
245
  if #ctx.recentLogs == 0 then
246
246
  return { status = "success", result = {"Console kosong. Tidak ada error atau log terbaru."} }
@@ -260,7 +260,7 @@ end
260
260
  <Content name="LinkedSource"><null></null></Content>
261
261
  <string name="Name">get_properties</string>
262
262
  <int64 name="SourceAssetId">-1</int64>
263
- <SharedString name="Tags"></SharedString>
263
+ <BinaryString name="Tags"></BinaryString>
264
264
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
265
265
  local CollectionService = game:GetService("CollectionService")
266
266
 
@@ -325,7 +325,7 @@ end
325
325
  <Content name="LinkedSource"><null></null></Content>
326
326
  <string name="Name">get_script_source</string>
327
327
  <int64 name="SourceAssetId">-1</int64>
328
- <SharedString name="Tags"></SharedString>
328
+ <BinaryString name="Tags"></BinaryString>
329
329
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
330
330
  if targetInstance:IsA("LuaSourceContainer") then
331
331
  return { status = "success", result = targetInstance.Source }
@@ -345,7 +345,7 @@ end
345
345
  <Content name="LinkedSource"><null></null></Content>
346
346
  <string name="Name">insert_asset</string>
347
347
  <int64 name="SourceAssetId">-1</int64>
348
- <SharedString name="Tags"></SharedString>
348
+ <BinaryString name="Tags"></BinaryString>
349
349
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
350
350
  local InsertService = game:GetService("InsertService")
351
351
  local assetId = tonumber(data)
@@ -376,11 +376,12 @@ end
376
376
  <Content name="LinkedSource"><null></null></Content>
377
377
  <string name="Name">Main</string>
378
378
  <int64 name="SourceAssetId">-1</int64>
379
- <SharedString name="Tags"></SharedString>
379
+ <BinaryString name="Tags"></BinaryString>
380
380
  <token name="RunContext">0</token>
381
381
  <ProtectedString name="Source"><![CDATA[local HttpService = game:GetService("HttpService")
382
382
  local LogService = game:GetService("LogService")
383
383
 
384
+ local PING_URL = "http://localhost:3055/api/ping"
384
385
  local TASK_URL = "http://localhost:3055/api/tasks"
385
386
  local RESPONSE_URL = "http://localhost:3055/api/response"
386
387
 
@@ -404,8 +405,10 @@ local ctx = {
404
405
  }
405
406
  local MAX_LOGS = 50
406
407
  local isConnected = false
407
- local autoConnect = plugin:GetSetting("AutoConnect") or false
408
+ local savedAutoConnect = plugin:GetSetting("AutoConnect")
409
+ local autoConnect = (savedAutoConnect == nil) and true or savedAutoConnect
408
410
  local isPolling = false
411
+ local isManualDisconnect = false -- Mencegah auto-reconnect saat user sengaja disconnect
409
412
  local isClickDebounce = false -- Mencegah rapid double-click
410
413
 
411
414
  -- Deklarasi fungsi polling di awal agar bisa dipanggil oleh UI
@@ -419,7 +422,11 @@ if uiModule then
419
422
 
420
423
  -- Sync Initial State ke UI
421
424
  ui:setAutoConnectState(autoConnect)
422
- ui:setStatus("disconnected")
425
+ if autoConnect then
426
+ ui:setStatus("waiting")
427
+ else
428
+ ui:setStatus("disconnected")
429
+ end
423
430
 
424
431
  -- Sync state tombol toolbar dengan status widget
425
432
  if ui.widget then
@@ -442,10 +449,29 @@ if uiModule then
442
449
  if not isConnected then
443
450
  -- CONNECT (manual) — reset manual disconnect flag agar auto-detect bisa bekerja
444
451
  isManualDisconnect = false
445
- isConnected = true
446
452
  ui:setStatus("waiting")
447
- ui:addLog("[System] Koneksi manual dimulai. Mencari AI Server (Waiting)...", Color3.fromRGB(255, 220, 100))
448
- startPolling()
453
+ ui:addLog("[System] Menghubungkan ke Server MCP...", Color3.fromRGB(255, 220, 100))
454
+
455
+ task.spawn(function()
456
+ local cacheBuster = "?t=" .. tostring(os.clock())
457
+ local success, _ = pcall(function()
458
+ return HttpService:GetAsync(PING_URL .. cacheBuster)
459
+ end)
460
+
461
+ isConnected = true
462
+ if success then
463
+ if ui then
464
+ ui:setStatus("connected")
465
+ ui:addLog("[System] Berhasil terhubung ke Server MCP!", Color3.fromRGB(100, 255, 100))
466
+ end
467
+ else
468
+ if ui then
469
+ ui:setStatus("waiting")
470
+ ui:addLog("[System] Server MCP belum aktif di port 3055. Menunggu koneksi...", Color3.fromRGB(255, 200, 100))
471
+ end
472
+ end
473
+ startPolling()
474
+ end)
449
475
  else
450
476
  -- DISCONNECT — hentikan polling dan tandai manual disconnect
451
477
  stopPolling()
@@ -573,8 +599,6 @@ local function processTask(taskData)
573
599
  end
574
600
 
575
601
  -- Menghentikan polling dengan bersih (dipanggil oleh tombol Disconnect)
576
- local isManualDisconnect = false -- Mencegah auto-reconnect saat user sengaja disconnect
577
-
578
602
  function stopPolling()
579
603
  isConnected = false
580
604
  isManualDisconnect = true
@@ -585,6 +609,7 @@ end
585
609
  function startPolling()
586
610
  if isPolling then return end
587
611
  isPolling = true
612
+ if ui and isConnected then ui:setStatus("connected") end
588
613
 
589
614
  task.spawn(function()
590
615
  while isConnected do
@@ -600,7 +625,7 @@ function startPolling()
600
625
  end
601
626
 
602
627
  if success and response then
603
- -- JIKA PING SUKSES, Node.js menyala (IDE / Agen Sedang Terbuka!)
628
+ -- JIKA PING/POLL SUKSES, Node.js menyala (IDE / Agen Sedang Terbuka!)
604
629
  if ui then ui:setStatus("connected") end
605
630
 
606
631
  local decodeSuccess, taskData = pcall(function()
@@ -623,7 +648,7 @@ function startPolling()
623
648
  else
624
649
  -- Fetch gagal (Node.js server mati karena IDE/Antigravity/Cursor ditutup)
625
650
  if ui then ui:setStatus("waiting") end
626
- task.wait(1)
651
+ task.wait(1.5)
627
652
  end
628
653
 
629
654
  -- Cek lagi sebelum sleep (agar disconnect lebih responsif)
@@ -637,55 +662,53 @@ end
637
662
 
638
663
  -- ══════════════════════════════════════════════════════════════════════
639
664
  -- AUTO-DETECT: Background heartbeat yang SELALU berjalan.
640
- -- Mendeteksi apakah server MCP aktif setiap 2 detik.
641
- -- Jika terdeteksi aktif → otomatis connect (tanpa perlu tekan tombol).
642
- -- Jika terdeteksi mati → otomatis update status.
665
+ -- Mendeteksi apakah server MCP aktif setiap 1.5 detik via PING_URL.
666
+ -- Menggunakan /api/ping agar instan (tidak tertahan 20 detik oleh long-poll).
643
667
  -- ══════════════════════════════════════════════════════════════════════
644
668
  task.spawn(function()
645
- task.wait(1) -- Tunggu sebentar agar UI siap
669
+ task.wait(0.5) -- Tunggu sebentar agar UI siap
646
670
 
647
671
  if ui then
648
672
  ui:addLog("[System] Plugin termuat. Auto-detect server MCP aktif...", Color3.fromRGB(200, 200, 200))
649
673
  end
650
674
 
651
675
  while true do
652
- -- Skip heartbeat jika polling loop sudah aktif (ia sudah menangani semuanya)
653
- if not isPolling then
654
- local success, errorMsg = pcall(function()
655
- local cacheBuster = "?t=" .. tostring(os.clock())
656
- return HttpService:GetAsync(TASK_URL .. cacheBuster)
657
- end)
658
-
659
- if success then
660
- -- Server terdeteksi aktif!
661
- if not isConnected and not isManualDisconnect then
662
- isConnected = true
663
- if ui then
664
- ui:setStatus("connected")
665
- ui:addLog("[System] Server MCP terdeteksi! Auto-connected.", Color3.fromRGB(100, 255, 100))
676
+ -- Hanya jalankan auto-detect jika user tidak sengaja menekan Disconnect
677
+ if not isManualDisconnect and (autoConnect or isConnected) then
678
+ if not isPolling then
679
+ local success, _ = pcall(function()
680
+ local cacheBuster = "?t=" .. tostring(os.clock())
681
+ return HttpService:GetAsync(PING_URL .. cacheBuster)
682
+ end)
683
+
684
+ if success then
685
+ -- Server terdeteksi aktif via Ping!
686
+ if not isConnected then
687
+ isConnected = true
688
+ if ui then
689
+ ui:setStatus("connected")
690
+ ui:addLog("[System] Server MCP terdeteksi! Terhubung otomatis.", Color3.fromRGB(100, 255, 100))
691
+ end
692
+ startPolling()
693
+ else
694
+ startPolling()
666
695
  end
667
- startPolling()
668
- end
669
- else
670
- -- Server tidak aktif
671
- if isConnected then
672
- -- Server baru saja mati (sebelumnya connected) — polling loop juga akan exit
673
- isConnected = false
674
- if ui then
696
+ else
697
+ -- Server tidak aktif / belum menyala
698
+ if isConnected then
699
+ isConnected = false
700
+ if ui then
701
+ ui:setStatus("waiting")
702
+ ui:addLog("[System] Server MCP terputus. Menunggu reconnect...", Color3.fromRGB(255, 200, 100))
703
+ end
704
+ elseif ui and ui.status ~= "waiting" and not isManualDisconnect then
675
705
  ui:setStatus("waiting")
676
- ui:addLog("[System] Server MCP terputus. Menunggu reconnect...", Color3.fromRGB(255, 200, 100))
677
- end
678
- elseif not isManualDisconnect then
679
- if ui then
680
- ui:setStatus("waiting")
681
- -- HANYA print error jika belum terkoneksi, untuk debugging
682
- ui:addLog("[Debug] Deteksi gagal: " .. tostring(errorMsg), Color3.fromRGB(150, 150, 150))
683
706
  end
684
707
  end
685
708
  end
686
709
  end
687
710
 
688
- task.wait(2)
711
+ task.wait(1.5)
689
712
  end
690
713
  end)
691
714
  ]]></ProtectedString>
@@ -700,7 +723,7 @@ end)
700
723
  <Content name="LinkedSource"><null></null></Content>
701
724
  <string name="Name">patch_script_source</string>
702
725
  <int64 name="SourceAssetId">-1</int64>
703
- <SharedString name="Tags"></SharedString>
726
+ <BinaryString name="Tags"></BinaryString>
704
727
  <ProtectedString name="Source"><![CDATA[local HttpService = game:GetService("HttpService")
705
728
 
706
729
  return function(targetInstance, data, ctx, targetPath)
@@ -810,7 +833,7 @@ end
810
833
  <Content name="LinkedSource"><null></null></Content>
811
834
  <string name="Name">rollback_script</string>
812
835
  <int64 name="SourceAssetId">-1</int64>
813
- <SharedString name="Tags"></SharedString>
836
+ <BinaryString name="Tags"></BinaryString>
814
837
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
815
838
  if targetInstance:IsA("LuaSourceContainer") then
816
839
  local prevSource = ctx.scriptHistory[targetPath]
@@ -836,7 +859,7 @@ end
836
859
  <Content name="LinkedSource"><null></null></Content>
837
860
  <string name="Name">script_grep</string>
838
861
  <int64 name="SourceAssetId">-1</int64>
839
- <SharedString name="Tags"></SharedString>
862
+ <BinaryString name="Tags"></BinaryString>
840
863
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
841
864
  local query = tostring(data)
842
865
  if not query or query == "" then
@@ -912,7 +935,7 @@ end
912
935
  <Content name="LinkedSource"><null></null></Content>
913
936
  <string name="Name">search_instance</string>
914
937
  <int64 name="SourceAssetId">-1</int64>
915
- <SharedString name="Tags"></SharedString>
938
+ <BinaryString name="Tags"></BinaryString>
916
939
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
917
940
  local searchName = tostring(data)
918
941
  local results = {}
@@ -945,7 +968,7 @@ end
945
968
  <Content name="LinkedSource"><null></null></Content>
946
969
  <string name="Name">ui_manager</string>
947
970
  <int64 name="SourceAssetId">-1</int64>
948
- <SharedString name="Tags"></SharedString>
971
+ <BinaryString name="Tags"></BinaryString>
949
972
  <ProtectedString name="Source"><![CDATA[local UIManager = {}
950
973
  local widget = nil
951
974
 
@@ -1401,7 +1424,7 @@ return UIManager
1401
1424
  <Content name="LinkedSource"><null></null></Content>
1402
1425
  <string name="Name">update_script_source</string>
1403
1426
  <int64 name="SourceAssetId">-1</int64>
1404
- <SharedString name="Tags"></SharedString>
1427
+ <BinaryString name="Tags"></BinaryString>
1405
1428
  <ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
1406
1429
  if targetInstance:IsA("LuaSourceContainer") then
1407
1430
  local newCode = data or ""