Transform Stud from a chat interface into a full agentic AI workflow for Roblox Studio. The goal: an app that does things rather than just chats.
Repository: https://github.com/madebyshaurya/stud License: AGPL-3.0 (open source, copyleft)
- Scripts:
get_script,set_script,edit_script - Instances:
get_children,get_properties,set_property,create,delete,clone,move,search,get_selection,run_code - Bulk:
bulk_create,bulk_delete,bulk_set_property
PromptInput,ChatContainer,Message,ToolCall,Loader,Markdown,CodeBlock,PromptSuggestionContextChips(UI only, not wired)
- Tauri 2 desktop app with Rust bridge server
- Multi-provider support (OpenAI, Anthropic, ChatGPT Plus/Pro Codex)
- OAuth PKCE flow for ChatGPT subscriptions
- Studio plugin with 18 request handlers
- File:
LICENSE - Replace MIT with AGPL-3.0 text
- Update README.md badge
- File:
src/index.css - Remove purple accent colors
- Pure black/white palette
- Files:
src/pages/Home.tsx - Change
loading-dots→waveorterminal
- File:
src/components/chat/ContextChips.tsx - File:
src/pages/Home.tsx - Connect chips to actual functionality:
search-models→ Opens toolbox search (Phase 3)docs→ Adds "search Roblox docs" to promptweb→ Adds "search the web" to promptrun-code→ Pre-fills with code execution templateplan→ Triggers plan mode (Phase 5)
The AI needs to ask questions during agentic workflows.
File: src/lib/roblox/tools.ts
roblox_ask_user: tool({
description: "Ask the user questions when you need clarification",
parameters: z.object({
questions: z.array(z.object({
question: z.string(),
options: z.array(z.string()).optional(),
type: z.enum(["single", "multi", "text"]).default("text")
})).min(1).max(4).describe("1-4 questions to ask at once")
}),
execute: async (params) => {
// Emit event to UI, wait for responses
return { waiting: true, questionId: uuid() };
}
})File: src/components/chat/QuestionPrompt.tsx
interface QuestionPromptProps {
question: string;
options?: string[];
type: "single" | "multi" | "text";
onAnswer: (answer: string | string[]) => void;
}- Renders inline in chat when AI uses
ask_usertool - Blocks AI until user responds
- Integrates with streaming flow
File: src/stores/chat.ts
- Add
pendingQuestionstate - Add
answerQuestion(id, answer)action - Emit answer back to AI stream
Allow AI to search and insert free models from Roblox Creator Store.
File: src/lib/roblox/toolbox.ts
interface ToolboxAsset {
id: number;
name: string;
description: string;
thumbnailUrl: string;
creator: { id: number; name: string };
favoriteCount: number;
productId?: number;
}
export async function searchToolbox(query: string, category?: string): Promise<ToolboxAsset[]>
export async function getAssetDetails(assetId: number): Promise<ToolboxAsset>API Endpoints (public, no auth required):
- Search:
https://apis.roblox.com/toolbox-service/v1/items/details - Thumbnails:
https://thumbnails.roblox.com/v1/assets
File: src/lib/roblox/tools.ts
roblox_toolbox_search: tool({
description: "Search the Roblox Creator Store for free models",
parameters: z.object({
query: z.string(),
category: z.enum(["Model", "Decal", "Audio", "Plugin"]).optional(),
limit: z.number().default(10)
})
})
roblox_insert_asset: tool({
description: "Insert a free model from the Creator Store into the game",
parameters: z.object({
assetId: z.number(),
parent: z.string().default("game.Workspace")
})
})File: studio-plugin/stud-bridge.server.lua
Add handler:
["/asset/insert"] = function(data)
local success, model = pcall(function()
return game:GetObjects("rbxassetid://" .. data.assetId)[1]
end)
if success and model then
model.Parent = resolvePath(data.parent)
return { success = true, name = model.Name }
end
return { success = false, error = "Failed to load asset" }
endFile: src/components/chat/AssetGrid.tsx
- Thumbnail grid view for search results
- Click to preview, double-click to insert
- Loading states with skeleton
Type @ in input to browse and select game instances.
File: src/components/chat/InstanceTree.tsx
interface InstanceNode {
path: string;
name: string;
className: string;
children: InstanceNode[];
icon?: string;
}
// Fetches tree from Studio via bridge
export function InstanceTree({ onSelect }: { onSelect: (path: string) => void })File: src/components/chat/InstancePicker.tsx
- Triggered when user types
@in PromptInput OR clicks browse button - Shows popover with InstanceTree
- Search/filter within picker
- Selection inserts
@game.Workspace.PartNameinto input
File: src/components/ui/prompt-input.tsx
- Detect
@keypress → show picker - Add browse button (folder icon) → show picker
- Replace
@...with selected path on confirm
File: src/lib/ai/providers.ts
- Before sending to AI, resolve
@pathmentions - Fetch instance properties automatically
- Include context in message
For complex tasks, AI plans before executing.
File: src/stores/plan.ts
interface PlanStore {
isActive: boolean;
plan: PlanStep[];
currentStep: number;
enter: () => void;
exit: () => void;
approve: () => void;
addStep: (step: PlanStep) => void;
}
interface PlanStep {
id: string;
description: string;
tools: string[];
status: "pending" | "approved" | "executing" | "done" | "error";
}File: src/lib/roblox/tools.ts
plan_enter: tool({
description: "Enter planning mode - explore and design before making changes",
execute: async () => {
// Emit to UI, restrict to read-only tools
}
})
plan_exit: tool({
description: "Exit planning mode and request user approval",
parameters: z.object({
plan: z.array(z.object({
step: z.string(),
tools: z.array(z.string())
}))
})
})File: src/components/chat/PlanView.tsx
- Shows plan steps in a structured list
- Checkboxes for user to approve/reject steps
- "Execute Plan" button
- Progress indicator during execution
File: src/components/chat/PlanModeIndicator.tsx
- Persistent banner when in plan mode
- Shows "Read-only mode" reminder
- Exit button
Show before/after when editing scripts.
File: src/components/chat/DiffView.tsx
- Use
difflibrary for line-by-line comparison - Syntax highlighting with Shiki
- Side-by-side or unified view toggle
File: src/pages/Home.tsx
- When tool result contains script changes, render DiffView
- Accept/Reject buttons
- Apply changes on accept
- Store previous script content
- "Undo" button restores original
- Syncs with Studio's ChangeHistoryService
AI can search the web and Roblox documentation.
File: src/lib/roblox/tools.ts
web_fetch: tool({
description: "Fetch and read content from a URL",
parameters: z.object({
url: z.string().describe("URL to fetch"),
prompt: z.string().describe("What to extract from the page")
}),
execute: async ({ url, prompt }) => {
// Use Tauri HTTP plugin to fetch, then summarize
}
})File: src/lib/roblox/tools.ts
roblox_docs_search: tool({
description: "Search Roblox Creator documentation",
parameters: z.object({
query: z.string()
}),
execute: async ({ query }) => {
// Scrape create.roblox.com/docs or use RAG
}
})
roblox_api_lookup: tool({
description: "Look up a specific Roblox API class, method, or event",
parameters: z.object({
name: z.string().describe("Class name like 'BasePart' or method like 'TweenService:Create'")
})
})File: src/components/chat/SourceCard.tsx
- Shows fetched sources inline
- Link to original docs
- Collapsible content preview
DataStore access, publishing, and more.
File: src/lib/roblox/cloud.ts
interface OpenCloudConfig {
apiKey: string;
universeId: string;
}
export class OpenCloudClient {
constructor(config: OpenCloudConfig);
// DataStores
async listDataStores(): Promise<DataStore[]>;
async getEntry(dataStore: string, key: string): Promise<any>;
async setEntry(dataStore: string, key: string, value: any): Promise<void>;
// Publishing
async publishPlace(placeId: string): Promise<void>;
// Universe Info
async getUniverseInfo(): Promise<UniverseInfo>;
}File: src/lib/roblox/tools.ts
roblox_datastore_get: tool({ ... })
roblox_datastore_set: tool({ ... })
roblox_datastore_list: tool({ ... })
roblox_publish_place: tool({ ... })File: src/components/Settings.tsx
- Add Open Cloud API key input
- Add Universe ID input
- Validation and connection test
Launch parallel workers for complex tasks.
File: src/lib/agents/index.ts
interface Agent {
id: string;
type: "explore" | "plan" | "execute";
status: "running" | "done" | "error";
messages: Message[];
result?: any;
}
export function spawnAgent(type: string, prompt: string): Agent;
export function waitForAgent(agentId: string): Promise<any>;File: src/lib/roblox/tools.ts
spawn_agent: tool({
description: "Launch a sub-agent to work on a specific task",
parameters: z.object({
type: z.enum(["explore", "plan", "execute"]),
prompt: z.string(),
background: z.boolean().default(false)
})
})File: src/components/chat/AgentStatus.tsx
- Shows active agents with progress
- Expandable to show agent's messages
- Cancel button
src/lib/roblox/toolbox.ts # Toolbox API client
src/lib/roblox/cloud.ts # Open Cloud API client
src/lib/agents/index.ts # Sub-agent system
src/stores/plan.ts # Plan mode state
src/components/chat/QuestionPrompt.tsx # AI question UI
src/components/chat/AssetGrid.tsx # Toolbox results grid
src/components/chat/InstanceTree.tsx # Game hierarchy tree
src/components/chat/InstancePicker.tsx # @ mention picker + browse button
src/components/chat/PlanView.tsx # Plan mode display
src/components/chat/PlanModeIndicator.tsx # Plan mode banner
src/components/chat/DiffView.tsx # Script diff
src/components/chat/SourceCard.tsx # Web/docs source display
src/components/chat/AgentStatus.tsx # Sub-agent progress
LICENSE # MIT → AGPL-3.0
src/lib/roblox/tools.ts # Add 12+ new tools
src/stores/chat.ts # Add question handling
src/pages/Home.tsx # Integrate new components
src/components/ui/prompt-input.tsx # Add @ detection
studio-plugin/stud-bridge.server.lua # Add asset insert handler
| Tool | Category | Description |
|---|---|---|
roblox_ask_user |
Core | Ask user questions during execution |
roblox_toolbox_search |
Toolbox | Search Creator Store |
roblox_insert_asset |
Toolbox | Insert free model |
plan_enter |
Planning | Enter read-only plan mode |
plan_exit |
Planning | Exit with plan for approval |
web_fetch |
Research | Fetch and read URL content |
roblox_docs_search |
Research | Search Roblox docs |
roblox_api_lookup |
Research | Look up specific API |
roblox_datastore_get |
Cloud | Read DataStore entry |
roblox_datastore_set |
Cloud | Write DataStore entry |
roblox_datastore_list |
Cloud | List DataStores |
roblox_publish_place |
Cloud | Publish to production |
spawn_agent |
Agents | Launch sub-agent |
Total: 15 existing + 13 new = 28 tools
After each phase, verify:
- TypeScript:
npx tsc --noEmitpasses - Runtime:
npm run tauri devlaunches without errors - Tools: AI can use new tools correctly
- UI: Components render properly
- Studio: Plugin handles new requests
End-to-end tests:
- Send message, get streaming response
- AI uses ask_user tool, user responds
- AI searches toolbox, inserts model
- User types @, picks instance
- AI enters plan mode, user approves
- Script edit shows diff view
- AI searches docs, cites sources
- AI reads/writes DataStore
- Sub-agent runs in background
- Phase 2: Ask User Tool (enables all agentic flows)
- Phase 1: Foundation (theme already done, wire chips)
- Phase 4: @ Instance Picker (core UX improvement)
- Phase 3: Toolbox Integration (major feature)
- Phase 6: Diff View (essential for script editing)
- Phase 5: Plan Mode (complex tasks)
- Phase 7: Web & Docs Search (research capability)
- Phase 8: Cloud API (production features)
- Phase 9: Sub-Agents (advanced agentic)
npm install diff # For DiffView
npm install uuid # For question IDs (if not already)No other major dependencies needed - using existing:
- Vercel AI SDK for tools
- Zod for schemas
- shadcn/ui + prompt-kit for UI
- Tauri HTTP plugin for API calls
Starting with Phase 2: Ask User Tool as it's the foundation for all agentic workflows, then proceeding through the phases in priority order.