@el4cteo/rbx-studio-mcp 0.1.5 → 0.2.0

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.
@@ -84,6 +84,35 @@ local overflow = 0
84
84
  local installed = false
85
85
  local installError: string? = nil
86
86
 
87
+ --[[
88
+ Which lines currently carry a breakpoint, per script.
89
+
90
+ `ScriptDebuggerService` exposes no way to list what is set -- `AddBreakpoint`
91
+ and `RemoveBreakpoint` are the whole surface, one line at a time -- so
92
+ "clear every breakpoint in this script" needs its own record of what this
93
+ module put there, or it cannot be done at all without either the caller
94
+ naming every line back or this file falling back to `ClearBreakpoints()`,
95
+ which takes the whole session's breakpoints with it, including ones set by
96
+ someone else sharing this Studio.
97
+ ]]
98
+ local trackedBreakpoints: { [Instance]: { [number]: boolean } } = {}
99
+
100
+ local function track(target: Instance, line: number)
101
+ local lines = trackedBreakpoints[target]
102
+ if lines == nil then
103
+ lines = {}
104
+ trackedBreakpoints[target] = lines
105
+ end
106
+ lines[line] = true
107
+ end
108
+
109
+ local function untrack(target: Instance, line: number)
110
+ local lines = trackedBreakpoints[target]
111
+ if lines then
112
+ lines[line] = nil
113
+ end
114
+ end
115
+
87
116
  --[[
88
117
  Flattens whatever the service hands back.
89
118
 
@@ -309,6 +338,7 @@ function Debug.set(params: { [string]: any }): { [string]: any }
309
338
  end)
310
339
 
311
340
  if ok then
341
+ track(target, descriptor.Line)
312
342
  table.insert(added, {
313
343
  path = Paths.of(target),
314
344
  line = descriptor.Line,
@@ -362,13 +392,39 @@ function Debug.clear(params: { [string]: any }): { [string]: any }
362
392
  if typeof(path) == "string" and path ~= "" then
363
393
  local target = Paths.resolve(path)
364
394
  local line = tonumber(params.line)
365
- if line == nil then
366
- Dispatch.fail("BAD_PARAMS", "removing one breakpoint needs a `line`.")
395
+
396
+ if line ~= nil then
397
+ local ok, removed = pcall(function()
398
+ return service:RemoveBreakpoint(target, line)
399
+ end)
400
+ untrack(target, line)
401
+ return { removed = ok and removed == true, path = Paths.of(target), line = line }
367
402
  end
368
- local ok, removed = pcall(function()
369
- return service:RemoveBreakpoint(target, line)
370
- end)
371
- return { removed = ok and removed == true, path = Paths.of(target), line = line }
403
+
404
+ --[[
405
+ `path` alone, no `line`: every breakpoint this module put in that
406
+ script. `ScriptDebuggerService` has no "list breakpoints in this
407
+ script" of its own -- only `AddBreakpoint`/`RemoveBreakpoint`, one
408
+ line at a time -- so `trackedBreakpoints` is what makes this
409
+ possible at all, and it is also the reason it can only remove what
410
+ this session set: a breakpoint another client or the user placed
411
+ by hand was never tracked here, and this cannot see it to touch it.
412
+ ]]
413
+ local lines = trackedBreakpoints[target]
414
+ local removedLines: { number } = {}
415
+ if lines then
416
+ for lineNumber in lines do
417
+ local ok = pcall(function()
418
+ service:RemoveBreakpoint(target, lineNumber)
419
+ end)
420
+ if ok then
421
+ table.insert(removedLines, lineNumber)
422
+ end
423
+ end
424
+ trackedBreakpoints[target] = nil
425
+ end
426
+ table.sort(removedLines)
427
+ return { removed = #removedLines > 0, path = Paths.of(target), lines = removedLines }
372
428
  end
373
429
 
374
430
  local ok, err = pcall(function()
@@ -377,6 +433,7 @@ function Debug.clear(params: { [string]: any }): { [string]: any }
377
433
  if not ok then
378
434
  Dispatch.fail("REFUSED", string.format("ClearBreakpoints refused: %s", tostring(err)))
379
435
  end
436
+ table.clear(trackedBreakpoints)
380
437
  return { cleared = true }
381
438
  end
382
439
 
@@ -138,7 +138,6 @@ function Geometry.combine(params: { [string]: any }): { [string]: any }
138
138
  then (if #produced == 1 then params.name else string.format("%s%d", params.name, index))
139
139
  else subject.Name
140
140
  part.Parent = parent
141
- table.insert(created, Paths.of(part))
142
141
  end
143
142
 
144
143
  -- Removing the inputs is opt-in, and only once the results exist: an
@@ -150,6 +149,26 @@ function Geometry.combine(params: { [string]: any }): { [string]: any }
150
149
  part:Destroy()
151
150
  end
152
151
  end
152
+
153
+ --[[
154
+ Paths for the produced parts are read only now, after the originals
155
+ are gone rather than in the loop above that named and parented them.
156
+
157
+ A result keeps the subject's name by default, so for the entire
158
+ window before the destroy above runs, the new part and the subject
159
+ it was built from are two same-named siblings in the same parent --
160
+ and Paths.of correctly, but uselessly, disambiguates that with a
161
+ `[2]` suffix that stops being true the instant the destroy runs a
162
+ few lines later. The caller reads this path after the call returns,
163
+ by which point the disambiguation was already wrong. Reading it here
164
+ instead describes where the part actually is once nothing further
165
+ in this function is going to move or remove anything -- correct
166
+ whether or not `keepOriginals` left a real, permanent collision for
167
+ it to still be disambiguating.
168
+ ]]
169
+ for _, part in produced do
170
+ table.insert(created, Paths.of(part))
171
+ end
153
172
  end)
154
173
 
155
174
  return { created = created, removed = removed, undoable = undoable }
@@ -147,6 +147,25 @@ function World.collision(params: { [string]: any }): { [string]: any }
147
147
  return { group = name, assigned = #assigned, parts = assigned, undoable = undoable }
148
148
  end
149
149
 
150
+ if action == "remove" then
151
+ --[[
152
+ Registering a group is one line and nothing here undid it: create,
153
+ assign and collidable all had a way back through the ordinary undo
154
+ stack, and this did not, because nothing called it at all. Over a
155
+ session that experiments -- try a group, decide against it -- that
156
+ leaves the place permanently carrying a group nobody wants, with no
157
+ way to remove it short of `execute_luau`. `UnregisterCollisionGroup`
158
+ has existed the whole time; it was just never wired up.
159
+ ]]
160
+ local ok, err = pcall(function()
161
+ PhysicsService:UnregisterCollisionGroup(name)
162
+ end)
163
+ if not ok then
164
+ Dispatch.fail("COLLISION_FAILED", string.format("Could not remove %q: %s", name, tostring(err)))
165
+ end
166
+ return { group = name, removed = true }
167
+ end
168
+
150
169
  if action == "collidable" then
151
170
  local other = params.with
152
171
  if typeof(other) ~= "string" or other == "" then
@@ -20,6 +20,7 @@ const identity = (studioId, placeId) => ({
20
20
  placeId,
21
21
  pluginVersion: "test",
22
22
  buildId: "test",
23
+ protocolVersion: 1,
23
24
  transport: "poll",
24
25
  context: "edit",
25
26
  });