Use this file to discover all available pages before exploring further.
The Roblox Studio plugin is the execution engine of RbxGenie, running inside Roblox Studio to poll for commands, execute tools against the DataModel, and report results back to the daemon.
function Bridge.startLoop() local backoff = BACKOFF_INITIAL while true do local ok, body = httpGet(POLL_ENDPOINT) if not ok then log("[Bridge] Poll failed: " .. body .. " — retry in " .. math.round(backoff) .. "s") task.wait(backoff) backoff = math.min(backoff * BACKOFF_FACTOR + math.random() * 0.5, BACKOFF_MAX) continue end backoff = BACKOFF_INITIAL local decoded = HttpService:JSONDecode(body) if not decoded or not decoded.hasCommand then task.wait(0.05) continue end local id = decoded.id local tool = decoded.tool local args = decoded.args log("[Bridge] Received: " .. tool .. " (" .. id:sub(1, 8) .. ")") if Bridge.onCommandStart then Bridge.onCommandStart(tool, args) end local recording = nil if not READ_ONLY_TOOLS[tool] then recording = ChangeHistoryService:TryBeginRecording("RbxGenie: " .. tool) end local t0 = os.clock() local result, err = Executor.dispatch(tool, args) local elapsedMs = math.round((os.clock() - t0) * 1000) if recording then ChangeHistoryService:FinishRecording(recording, Enum.FinishRecordingOperation.Commit) end if Bridge.onCommandEnd then Bridge.onCommandEnd(tool, err == nil, elapsedMs) end if err then postResult(id, nil, err) else postResult(id, result, nil) end task.wait(0.1) endend
The bridge implements exponential backoff on poll failures, starting at 2 seconds and capping at 30 seconds, with jitter to prevent thundering herd.
Resolves dot-separated path strings to Instance references.Location: plugin/PathResolver.lua
function PathResolver.resolve(path: string): (Instance?, string?) if not path or path == "" then return nil, "Empty path" end local parts = path:split(".") local root = ROOTS[parts[1]] -- e.g., "Workspace", "ReplicatedStorage" if not root then -- Try game:GetService fallback local ok, svc = pcall(function() return game:GetService(parts[1]) end) if ok and svc then root = svc else return nil, "Unknown root service: " .. parts[1] end end local current: Instance = root for i = 2, #parts do local child = current:FindFirstChild(parts[i]) if not child then return nil, ("Path not found at segment [%d] '%s' in '%s'"):format(i, parts[i], path) end current = child end return current, nilend
Example paths:
Workspace.Model.Part
ReplicatedStorage.Modules.Utility
ServerScriptService.GameController
Paths are case-sensitive and use FindFirstChild, so they match the exact instance names in the DataModel.
function ValueSerializer.fromJSON(v: any): any if type(v) == "table" and v.type then if v.type == "Vector3" then return Vector3.new(v.value[1], v.value[2], v.value[3]) elseif v.type == "Color3" then return Color3.new(v.value[1], v.value[2], v.value[3]) elseif v.type == "CFrame" then return CFrame.new(unpack(v.value)) -- 12 components elseif v.type == "Enum" then return toEnum(tostring(v.value)) -- e.g., "Enum.Material.Plastic" -- ... other types end end return v -- Pass through primitivesend
function ValueSerializer.toJSON(v: any): any local t = typeof(v) if t == "Vector3" then return { type = "Vector3", value = {v.X, v.Y, v.Z} } elseif t == "Color3" then return { type = "Color3", value = {v.R, v.G, v.B} } elseif t == "EnumItem" then return { type = "Enum", value = tostring(v) } -- ... other types else return v -- Pass through primitives endend
function PropertyTools.set_property(args: { path: string, property: string, value: any }): any local inst, err = PathResolver.resolve(args.path) if not inst then return { error = err } end local value = ValueSerializer.fromJSON(args.value) inst[args.property] = value return "Property set"end
function ScriptTools.edit_script_lines(args: { path: string, startLine: number, endLine: number, newText: string}): any local inst, err = PathResolver.resolve(args.path) if not inst then return { error = err } end if not inst:IsA("LuaSourceContainer") then return { error = "Instance is not a script" } end local lines = inst.Source:split("\n") -- Replace lines[startLine..endLine] with newText -- (implementation omitted) inst.Source = table.concat(lines, "\n") return "Lines edited"end
function ExecuteTools.execute_luau(args: { code: string }): any local outputBuffer = {} -- Redirect print() to buffer local oldPrint = print print = function(...) table.insert(outputBuffer, table.concat({...}, " ")) end local fn, loadErr = loadstring(args.code) if not fn then print = oldPrint return { error = "Syntax error: " .. loadErr, output = outputBuffer } end local ok, result = pcall(fn) print = oldPrint if not ok then return { error = tostring(result), output = outputBuffer } end return { result = ValueSerializer.toJSON(result), output = outputBuffer }end
Control Studio play modes and run integration tests.Location: plugin/tools/PlaytestTools.luaKey functions:
start_play() — Enter play mode
stop_play() — Exit play mode
run_server() — Enter server mode
get_studio_mode() — Current mode (“edit”/“play”/“server”)
run_script_in_play_mode() — Inject test script, run, capture logs
Example:
function PlaytestTools.run_script_in_play_mode(args: { code: string, timeout: number?, mode: string?}): any local RunService = game:GetService("RunService") local timeout = args.timeout or 10 local mode = args.mode or "play" -- 1. Create test script in ServerScriptService -- 2. Start play/server mode -- 3. Wait for script to complete or timeout -- 4. Capture LogService output -- 5. Stop play mode -- 6. Return { logs, errors, duration }end
The run_script_in_play_mode tool is designed for automated testing, allowing AI to write and execute integration tests in play mode.
local function instanceToTable( inst: Instance, depth: number, maxDepth: number, counter: { n: number }, maxNodes: number): { [string]: any }? if counter.n >= maxNodes then return nil end counter.n += 1 -- ... build tree if counter.n >= maxNodes then t.truncated = true break end return tend
The get_file_tree tool caps at 200 nodes by default to prevent massive responses. Use scoped paths or lower depth for large games.
Tools like mass_set_property and mass_create_objects operate on multiple instances in a single command:
function PropertyTools.mass_set_property(args: { paths: { string }, property: string, value: any}): any local results = {} for _, path in ipairs(args.paths) do local inst, err = PathResolver.resolve(path) if inst then inst[args.property] = ValueSerializer.fromJSON(args.value) table.insert(results, { path = path, ok = true }) else table.insert(results, { path = path, ok = false, error = err }) end end return resultsend