@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.
- package/dist/build-plugin.js +2 -2
- package/dist/index.js +1 -1
- package/dist/roblox-bridge.js +19 -0
- package/package.json +1 -1
- package/studio-plugin/Main.server.lua +66 -43
- package/studio-plugin/nvstudio_mcp.rbxmx +83 -60
package/dist/build-plugin.js
CHANGED
|
@@ -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<
|
|
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<
|
|
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
package/dist/roblox-bridge.js
CHANGED
|
@@ -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,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
|
|
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
|
-
|
|
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]
|
|
68
|
-
|
|
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
|
|
261
|
-
--
|
|
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(
|
|
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
|
-
--
|
|
273
|
-
if not
|
|
274
|
-
|
|
275
|
-
local
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
isConnected
|
|
283
|
-
|
|
284
|
-
ui
|
|
285
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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(
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
|
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
|
-
|
|
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]
|
|
448
|
-
|
|
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
|
|
641
|
-
--
|
|
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(
|
|
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
|
-
--
|
|
653
|
-
if not
|
|
654
|
-
|
|
655
|
-
local
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
isConnected
|
|
663
|
-
|
|
664
|
-
ui
|
|
665
|
-
|
|
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
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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(
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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 ""
|