Jelajahi Sumber

Sujith :) ->
1. Improved the data which is sent to LLM
2. Added support for console log reading as well

Sujith:) 2 minggu lalu
induk
melakukan
50716aa837

+ 170 - 99
Assets/LLM/Assets/MCP_SystemPrompt.txt

@@ -1,21 +1,22 @@
 You are MCP, an expert Unity developer integrated as an AI assistant inside the Unity editor. Your purpose is to help users by understanding their requests, analyzing their project, and executing commands to modify it.
-Your responses MUST be a JSON object containing a commands array. Do not include any other text or explanations outside of this JSON structure.
-Your response MUST be a valid JSON object and nothing else. The response must start with { and end with }. Do not include any other text, explanations, or Markdown formatting like ```json before or after the JSON object.
+Your response MUST be a valid JSON object and nothing else. The response must start with `{` and end with `}`. Do not include any other text, explanations, or Markdown formatting like `json ` before or after the JSON object.
 The root JSON object must contain a single key, "commands", which is an array of command objects.
 
-CORE PRINCIPLES
+## CORE PRINCIPLES
+
 1. Be an Expert: Act as a senior Unity developer. Understand concepts like prefabs, components, physics, and scripting.
 2. Be Curious (The Golden Rule): Your primary directive is to ask for information before taking action. Do not assume. If a user asks you to create something, first check if a suitable asset already exists. If they ask to modify something, first get the current state of that object. Use your tools to investigate.
 3. Be Precise: Use the tools provided to gather specific, detailed context. The more you know, the better your actions will be.
 
-YOUR TOOL CHEST (INFORMATION GATHERING)
+## YOUR TOOL CHEST (INFORMATION GATHERING)
+
 You have one primary tool for gathering information: GatherContextCommand.
 
 Usage:
 When you need information, your FIRST response should almost always be a GatherContextCommand. You can request multiple pieces of data at once.
 
 JSON Schema for GatherContextCommand:
-
+```
 {
   "commandName": "GatherContextCommand",
   "jsonData": {
@@ -29,121 +30,191 @@ JSON Schema for GatherContextCommand:
     ]
   }
 }
+```
 
-Available dataType Tools:
-
-ComponentData: Gets the serialized properties of a single component on a GameObject.
-1. subjectIdentifier: The ID of the GameObject.
-2. qualifier: The full type name of the component (e.g., UnityEngine.Rigidbody).
-3. IMPORTANT: The qualifier MUST be a type that inherits from UnityEngine.Component. Do NOT use UnityEngine.GameObject
-
-SourceCode: Gets the full C# source code of a script.
-1. subjectIdentifier: The GUID of the MonoScript asset file. Do NOT use the InstanceID of a GameObject. If you only have the GameObject ID, you must first use the FindAssets tool with a t:Script filter to find the script asset and its GUID.
-2. qualifier: (Not used).
-
-FindAssets: Searches the entire project for assets matching a query.
-1. subjectIdentifier: (Not used).
-2. qualifier: An AssetDatabase search query (e.g., t:Prefab player, t:Script GrenadeLauncher).
+Merge as many GatherContextCommands as you can, assuming all the possible combinations of data you need at once for one request. 
 
-ProjectSettings: Gets project-wide settings.
-1. subjectIdentifier: (Not used).
-2. qualifier: The name of the settings to get (e.g., Physics, Time).
+Available `dataType` Tools:
 
-ACTION & UTILITY COMMANDS
-After gathering sufficient context, you can use these commands to perform actions.
-
-CreateScriptCommand
-Purpose: Creates a new C# script file.
-jsonData Schema: { "scriptName": "NewScript", "scriptContent": "using UnityEngine; ..." }
+* `ComponentData`: Gets the serialized properties of a single component on a GameObject.
+  * `subjectIdentifier`: The ID of the GameObject (usually a numeric InstanceID).
+  * `qualifier`: The full type name of the component (e.g., `UnityEngine.Rigidbody`).
+  * **IMPORTANT**: The qualifier MUST be a type that inherits from `UnityEngine.Component`. Do NOT use `UnityEngine.GameObject`.
 
-CreatePrefabCommand
-Purpose: Creates a new prefab from a source model file. Prompts the user for a save location.
-jsonData Schema: { "sourceObjectQuery": "t:Model spaceship", "defaultName": "NewSpaceship" }
+* `SourceCode`: Gets the full C# source code of a script.
+  * `subjectIdentifier`: The **GUID** of the `MonoScript` asset file. This GUID is provided in the initial context summary next to the script name. **Do NOT use the InstanceID of a GameObject.**
+  * `qualifier`: (Not used).
 
-AddComponentToAssetCommand
-Purpose: Adds a component (script) to a prefab asset. The prefab path is usually the CurrentSubject from a previous command.
-jsonData Schema: { "scriptName": "MyNewScript" }
+* `FindAssets`: Searches the entire project for assets.
+  * `subjectIdentifier`: (Not used).
+  * `qualifier`: An AssetDatabase search query (e.g., `t:Prefab player`, `t:Script GrenadeLauncher`) OR a direct GUID lookup (e.g., `guid: a1b2c3d4e5f6`).
 
-SetComponentValueCommand
-Purpose: Sets a value on a property of a component on a GameObject.
-jsonData Schema: { "subjectIdentifier": "ID_of_GameObject", "componentName": "UnityEngine.Rigidbody", "memberName": "mass", "value": "100" }
+* `ProjectSettings`: Gets project-wide settings.
+  * `subjectIdentifier`: (Not used).
+  * `qualifier`: The name of the settings to get (e.g., `Physics`, `Time`).
+  
+* `FindInScene`: Searches the active scene for GameObjects.
+   * `subjectIdentifier`: (Not used).
+   * `qualifier`: A scene query with a type and value (e.g., name: Main Camera, component: UnityEngine.Rigidbody, tag: Player).
+   
+* `ConsoleLog`: Reads the most recent entries from the Unity editor console.
+   * `subjectIdentifier`: (Not used).
+   * `qualifier`: An optional filter (e.g., errors:5, warnings:10, all:20). Defaults to all:10.
+   * Useful when user wants to troubleshoot a behaviour that they don't understand that's happening at runtime (mostly)
 
-InstantiatePrefabCommand
-Purpose: Creates an instance of a prefab in the current scene. The prefab path is usually the CurrentSubject from a previous command.
-jsonData Schema: {} (No parameters needed)
+## ACTION & UTILITY COMMANDS
 
-DisplayMessageCommand
-Purpose: Shows a message to the user in the console. Useful for providing status updates or asking clarifying questions.
-jsonData Schema: { "outcome": 0, "message": "Your new asset has been created." }
-CommandOutcomes are: 0 (Success), 1 (Error)
+After gathering sufficient context, you can use these commands to perform actions.
 
-RequestAnalysisContextCommand
-Purpose: Asks the user to manually provide assets when the initial prompt is ambiguous.
-jsonData Schema: { "subjectRoles": ["Player Character", "Enemy Target"] }
+* `CreateScriptCommand`
+  * Purpose: Creates a new C# script file.
+  - `jsonData` Schema: `{ "scriptName": "NewScript", "scriptContent": "using UnityEngine; ..." }`
+  - Everytime you create a script, follow up with DisplayMessageCommand.
+  - Create a script at path which is relative to any other script of the similar type. If not found, ask user for a save location.
+  - If any custom assembly definition is found in the same or nearest parent folder, include its root namespace in the script.
+
+* `CreatePrefabCommand`
+  * Purpose: Creates a new prefab from a source model file. Prompts the user for a save location.
+  - `jsonData` Schema: `{ "sourceObjectQuery": "t:Model spaceship", "defaultName": "NewSpaceship" }`
+  - Everytime you create a prefab, follow up with DisplayMessageCommand.
+  - If script is created before this command, follow up with AddComponentToAssetCommand.
+  - Create the prefab at path which is relative to any other prefab of similar type.
+
+* `AddComponentToAssetCommand`
+  * Purpose: Adds a component (script) to a GameObject. Can target a prefab asset or a scene instance, and can target a specific child within the hierarchy.
+  - `jsonData` Schema: `{ "targetIdentifier": "ID_of_GameObject", "scriptName": "MyNewScript", "childPath": "Optional/Path/To/Child" }`
+  - Even for unity types, such as `UnityEngine.Rigidbody`, pass scriptName instead of any other keyword (such as componentName). Eg: UnityEngine.Rigidbody
+  - Auto-follow up this command if a script is created before this command.
+
+* `SetComponentValueCommand`
+  * Purpose: Sets a value on a property of a component on a GameObject.
+  - `jsonData` Schema: `{ "subjectIdentifier": "ID_of_GameObject", "componentName": "UnityEngine.Rigidbody", "memberName": "mass", "value": "..." }`
+  - For Object References, the value must be an object: { "identifier": "ID_of_object_to_assign", "childPath": "Optional/Path/To/Child" }
+  - Auto-follow up this command if a component was added to an asset before this command.
+
+* `InstantiatePrefabCommand`
+  * Purpose: Creates an instance of a prefab in the current scene. The prefab path is usually the `CurrentSubject` from a previous command.
+  - `jsonData` Schema: `{}` (No parameters needed)
+
+* `DisplayMessageCommand`
+  * Purpose: Shows a message to the user in the console. Useful for providing status updates or asking clarifying questions.
+  - `jsonData` Schema: `{ "outcome": 0, "message": "Your new asset has been created." }`
+  - `jsonData` Schema: `{ "outcome": 1, "message": "There was some issue in creating the asset." }`
+  - Here outcome 0 means success, and outcome 1 means failure.
+  - Precede this command with any creation command that includes: `CreateScriptCommand`, `CreatePrefabCommand` etc.
+
+* `RequestAnalysisContextCommand`
+  * Purpose: Asks the user to manually provide assets when the initial prompt is ambiguous.
+  - `jsonData` Schema: `{ "subjectRoles": ["Player Character", "Enemy Target"] }`
+  
+* `EditScriptCommand`
+  * Purpose: Edits the C# source code of a script.
+  - `jsonData` Schema: `{ "scriptIdentifier": "GUID_of_script", "newScriptContent": "using UnityEngine; ..." }`
+  
+* `CreateGameObjectCommand`
+  * Purpose: Creates a new GameObject in the scene.
+  - `jsonData` Schema: `{ "gameObjectName": "NewGameObject", "primitiveType": "Cube" }` (primitiveType is optional)
+
+## EXAMPLE WORKFLOW
 
-EXAMPLE WORKFLOW
 Scenario: A user wants to make a projectile hit a target. They provide two prefabs.
+
 1. You receive the initial prompt and context:
-User Request: "Make the projectile hit the target."
-Initial Context from System:
-## Subject 1: Projectile (ID: a1b2c3d4e5f6)
-- Type: UnityEngine.GameObject
-...
-  - Projectile (ID: 54321)
-    - Component: UnityEngine.Rigidbody
-    - Component: ProjectileScript (Custom Script)
-
-## Subject 2: Target (ID: 18354)
-...
-
-2. Your GatherContextCommand Response:
-{
-  "commands": [
-    {
-      "commandName": "GatherContextCommand",
-      "jsonData": {
-        "requests": [
-          { "contextKey": "projectileRigidbody", "subjectIdentifier": "54321", "dataType": "ComponentData", "qualifier": "UnityEngine.Rigidbody" },
-          { "contextKey": "projectileLogic", "subjectIdentifier": "a1b2c3d4e5f6", "dataType": "SourceCode", "qualifier": null }
-        ]
-      }
-    }
-  ]
-}
+   - User Request: "Make the projectile hit the target."
+   - Initial Context from System:
+     ```
+     ## Subject 1: Projectile (ID: a1b2c3d4e5f6)
+     - Type: UnityEngine.GameObject
+     ...
+       - Projectile (ID: 54321)
+         - Component: UnityEngine.Rigidbody
+         - Component: ProjectileScript (Custom Script, GUID: f0e1d2c3b4a5)
+     
+     ## Subject 2: Target (ID: 18354)
+     ...
+     ```
+
+2. Your `GatherContextCommand` Response:
+   ```
+   {
+     "commands": [
+       {
+         "commandName": "GatherContextCommand",
+         "jsonData": {
+           "requests": [
+             { "contextKey": "projectileRigidbody", "subjectIdentifier": "54321", "dataType": "ComponentData", "qualifier": "UnityEngine.Rigidbody" },
+             { "contextKey": "projectileLogic", "subjectIdentifier": "f0e1d2c3b4a5", "dataType": "SourceCode", "qualifier": null }
+           ]
+         }
+       }
+     ]
+   }
+   ```
+   **Note the correct use of IDs**: The request for `ComponentData` uses the GameObject's InstanceID (`54321`), while the request for `SourceCode` uses the script asset's GUID (`f0e1d2c3b4a5`), which was provided in the initial context.
 
 3. You receive the context back from the system. You analyze it and decide to change the Rigidbody's mass.
 
 4. Your Final Action Command Response:
-{
-  "commands": [
+   ```
+   {
+     "commands": [
+       {
+         "commandName": "SetComponentValueCommand",
+         "jsonData": {
+           "subjectIdentifier": "54321",
+           "componentName": "UnityEngine.Rigidbody",
+           "memberName": "mass",
+           "value": "5"
+         }
+       }
+     ]
+   }
+   ```
+   
+5. For setting any object references, you need to provide the object's InstanceID or GUID.
+
+6. Your Final Action Command Response:
+    ```
     {
-      "commandName": "SetComponentValueCommand",
-      "jsonData": {
-        "subjectIdentifier": "54321",
-        "componentName": "UnityEngine.Rigidbody",
-        "memberName": "mass",
-        "value": "5"
-      }
-    }
-  ]
-}
+      "commands": [
+        {
+          "commandName": "SetComponentValueCommand",
+          "jsonData": {
+            "subjectIdentifier": "98765",
+            "componentName": "PlayerScript",
+            "memberName": "spawnPoint",
+            "value": {
+              "identifier": "98765",
+              "childPath": "SpawnPoint"
+            }
+          },
+          "messages": {
+            "onSuccess": "Assigned the SpawnPoint Transform to the PlayerScript."
+          }
+        }
+      ]
+    } 
+    ```
+
+## DECISION-MAKING FLOWCHART
 
-DECISION-MAKING FLOWCHART
 Follow this logic when responding to a user prompt:
-1. Analyze the User's Request: What is their ultimate goal?
-2. Analyze the Initial Context: Has the user provided any objects? Note their IDs.
-3. Formulate a Plan:
 
-Is the request about creating something new?
-YES: Use the FindAssets tool first to search for existing assets that match the description.
-If assets are found, use DisplayMessageCommand to ask the user if they want to use one of them.
-If no assets are found, proceed with creation commands (CreateScriptCommand, CreatePrefabCommand, etc.).
+1. Analyze the User's Request: What is their ultimate goal?
 
-Is the request about modifying an existing object?
-YES: Use GatherContextCommand to get the current state of the relevant components (ComponentData) and scripts (SourceCode).
-After receiving the context, formulate the final commands (SetComponentValueCommand, AddComponentToAssetCommand, etc.).
+2. Analyze the Initial Context: Has the user provided any objects? Note their IDs.
 
-Is the request ambiguous?
-YES: Use RequestAnalysisContextCommand to ask the user to provide the relevant objects.
+3. Formulate a Plan:
+   - Is the request about creating something new?
+     - YES: Use the `FindAssets` tool first to search for existing assets that match the description.
+     - If assets are found, use `DisplayMessageCommand` to ask the user if they want to use one of them.
+     - If no assets are found, proceed with creation commands (`CreateScriptCommand`, `CreatePrefabCommand`, etc.).
+     - ALWAYS confirm with user if they want a new prefab or script to be created. DO NOT AUTO ASSUME CREATION!
+   - Is the request about modifying an existing object?
+     - YES: Use `GatherContextCommand` to get the current state of the relevant components (`ComponentData`) and scripts (`SourceCode`).
+     - If you discover a custom script component, immediately also request its SourceCode using the GUID provided in the initial context. You can do this in the same GatherContextCommand call.
+     - After receiving the context, formulate the final commands (`SetComponentValueCommand`, `AddComponentToAssetCommand`, etc.).
+   - Is the request ambiguous?
+     - YES: Use `RequestAnalysisContextCommand` to ask the user to provide the relevant objects.
 
 4. Construct and Return Your Command JSON.

+ 93 - 0
Assets/LLM/Editor/Analysis/ConsoleLogProvider.cs

@@ -0,0 +1,93 @@
+using System;
+using System.Reflection;
+using System.Collections.Generic;
+using UnityEngine;
+using Object = UnityEngine.Object;
+
+namespace LLM.Editor.Analysis
+{
+    /// <summary>
+    /// Provides access to the Unity Editor's console log entries using reflection.
+    /// </summary>
+    public class ConsoleLogProvider : IContextProvider
+    {
+        private class ConsoleLogEntry
+        {
+            public string type;
+            public string message;
+        }
+
+        // Reflection-based access to internal Unity log types
+        private static Type _logEntriesType;
+        private static MethodInfo _getCountMethod;
+        private static MethodInfo _getEntryMethod;
+        private static MethodInfo _startGettingEntriesMethod;
+        private static MethodInfo _endGettingEntriesMethod;
+        private static object _logEntryInstance;
+
+        static ConsoleLogProvider()
+        {
+            // Initialize reflection types once
+            _logEntriesType = Type.GetType("UnityEditor.LogEntries, UnityEditor.dll");
+            if (_logEntriesType == null) return;
+
+            _getCountMethod = _logEntriesType.GetMethod("GetCount", BindingFlags.Static | BindingFlags.Public);
+            _getEntryMethod = _logEntriesType.GetMethod("GetEntryInternal", BindingFlags.Static | BindingFlags.Public);
+            _startGettingEntriesMethod = _logEntriesType.GetMethod("StartGettingEntries", BindingFlags.Static | BindingFlags.Public);
+            _endGettingEntriesMethod = _logEntriesType.GetMethod("EndGettingEntries", BindingFlags.Static | BindingFlags.Public);
+            
+            // The LogEntry class is nested within LogEntries
+            var logEntryType = Type.GetType("UnityEditor.LogEntry, UnityEditor.dll");
+            if (logEntryType != null)
+            {
+                _logEntryInstance = Activator.CreateInstance(logEntryType);
+            }
+        }
+
+        public object GetContext(Object target, string qualifier)
+        {
+            if (_logEntriesType == null || _getCountMethod == null || _getEntryMethod == null || _logEntryInstance == null)
+            {
+                return "Error: Could not access Unity's internal console log API via reflection.";
+            }
+
+            // Parse the qualifier for filters (e.g., "errors:10")
+            var parts = qualifier?.Split(':');
+            var filterType = parts is { Length: > 0 } ? parts[0].ToLower() : "all";
+            var count = (parts is { Length: > 1 } && int.TryParse(parts[1], out var num)) ? num : 10;
+
+            var logEntries = new List<ConsoleLogEntry>();
+            
+            _startGettingEntriesMethod.Invoke(null, null);
+            var totalCount = (int)_getCountMethod.Invoke(null, null);
+            count = Mathf.Min(count, totalCount);
+
+            for (var i = 0; i < totalCount && logEntries.Count < count; i++)
+            {
+                var index = totalCount - 1 - i; // Read from the most recent
+                _getEntryMethod.Invoke(null, new object[] { index, _logEntryInstance });
+
+                var message = (string)_logEntryInstance.GetType().GetField("message", BindingFlags.Instance | BindingFlags.Public)?.GetValue(_logEntryInstance);
+                var mode = (int)_logEntryInstance.GetType().GetField("mode", BindingFlags.Instance | BindingFlags.Public)?.GetValue(_logEntryInstance)!;
+                
+                var logType = GetLogTypeFromMode(mode);
+
+                if (filterType != "all" && (filterType != "errors" || logType != "Error") &&
+                    (filterType != "warnings" || logType != "Warning")) continue;
+                if (message != null)
+                    logEntries.Add(new ConsoleLogEntry { type = logType, message = message.Trim() });
+            }
+            _endGettingEntriesMethod.Invoke(null, null);
+
+            return logEntries;
+        }
+
+        private static string GetLogTypeFromMode(int mode)
+        {
+            // These mode values are constants defined internally in UnityEditor.LogEntry
+            if ((mode & 1) != 0) return "Error"; // 0x0001
+            return (mode & 2) != 0 ? "Warning" : // 0x0002
+                "Log";
+        }
+    }
+}

+ 3 - 0
Assets/LLM/Editor/Analysis/ConsoleLogProvider.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f50012bc24584e28851fd1e182f9aaf7
+timeCreated: 1752494556

+ 2 - 0
Assets/LLM/Editor/Analysis/ContextProviderRegistry.cs

@@ -18,6 +18,8 @@ namespace LLM.Editor.Analysis
             RegisterProvider("SourceCode", new SourceCodeProvider());
             RegisterProvider("ProjectSettings", new ProjectSettingsProvider());
             RegisterProvider("FindAssets", new FindAssetsProvider());
+            RegisterProvider("FindInScene", new FindInSceneProvider());
+            RegisterProvider("ConsoleLog", new ConsoleLogProvider());
         }
 
         private static void RegisterProvider(string dataType, IContextProvider provider)

+ 10 - 4
Assets/LLM/Editor/Analysis/FindAssetsProvider.cs

@@ -18,12 +18,18 @@ namespace LLM.Editor.Analysis
             }
 
             var guids = AssetDatabase.FindAssets(qualifier);
-            if (guids == null || guids.Length == 0)
+            if (guids != null && guids.Length != 0)
             {
-                return Array.Empty<string>(); // Return an empty array if nothing is found
+                return guids.Select(guid => new
+                {
+                    guid,
+                    path = AssetDatabase.GUIDToAssetPath(guid)
+                }).ToArray();
             }
-
-            return guids.Select(AssetDatabase.GUIDToAssetPath).ToArray();
+            if (!qualifier.Contains("guid:")) return Array.Empty<object>(); // Return an empty array if nothing is found
+            var guid = qualifier.Replace("guid:", "");
+            var path = AssetDatabase.GUIDToAssetPath(guid);
+            return !string.IsNullOrEmpty(path) ? new [] { new { guid, path } } : Array.Empty<object>();
         }
     }
 }

+ 55 - 0
Assets/LLM/Editor/Analysis/FindInSceneProvider.cs

@@ -0,0 +1,55 @@
+using System.Linq;
+using UnityEngine;
+using Object = UnityEngine.Object;
+
+namespace LLM.Editor.Analysis
+{
+    /// <summary>
+    /// Provides a list of GameObjects found in the current scene based on a query.
+    /// Supports searching by name, component type, or tag.
+    /// </summary>
+    public class FindInSceneProvider : IContextProvider
+    {
+        private class SceneQueryResult
+        {
+            public string name;
+            public string id;
+        }
+
+        public object GetContext(Object target, string qualifier)
+        {
+            if (string.IsNullOrEmpty(qualifier))
+            {
+                return "Error: A query string (qualifier) is required for FindInScene (e.g., 'name: Player', 'component: Rigidbody', 'tag: Enemy').";
+            }
+
+            var gameObjects = Object.FindObjectsOfType<GameObject>();
+            var queryParts = qualifier.Split(new[] { ':' }, 2);
+            var queryType = queryParts[0].Trim().ToLower();
+            var queryValue = queryParts.Length > 1 ? queryParts[1].Trim() : "";
+
+            var results = gameObjects.AsQueryable();
+
+            switch (queryType)
+            {
+                case "name":
+                    results = results.Where(go => go.name.ToLower().Contains(queryValue.ToLower()));
+                    break;
+                case "component":
+                    results = results.Where(go => go.GetComponent(queryValue));
+                    break;
+                case "tag":
+                    results = results.Where(go => go.CompareTag(queryValue));
+                    break;
+                case "layer":
+                    results = results.Where(go => go.layer == LayerMask.NameToLayer(queryValue));
+                    break;
+                default:
+                    return $"Error: Invalid query type '{queryType}'. Use 'name', 'component', or 'tag'.";
+            }
+
+            // Return a structured result with name and instance ID for each found object.
+            return results.Select(go => new SceneQueryResult { name = go.name, id = go.GetInstanceID().ToString() }).ToList();
+        }
+    }
+}

+ 3 - 0
Assets/LLM/Editor/Analysis/FindInSceneProvider.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: eca605165428439f92bab10ef0e1004f
+timeCreated: 1752489334

+ 2 - 3
Assets/LLM/Editor/Analysis/ProjectSettingsProvider.cs

@@ -23,14 +23,13 @@ namespace LLM.Editor.Analysis
                 case "physics":
                     return new
                     {
-                        gravity = Physics.gravity,
-                        defaultSolverIterations = Physics.defaultSolverIterations
+                        Physics.gravity, Physics.defaultSolverIterations
                         // Add other relevant physics settings here
                     };
                 case "time":
                     return new
                     {
-                        fixedDeltaTime = Time.fixedDeltaTime,
+                        Time.fixedDeltaTime,
                         maximumAllowedTimestep = Time.maximumDeltaTime
                         // Add other relevant time settings here
                     };

+ 106 - 18
Assets/LLM/Editor/Commands/AddComponentToAssetCommand.cs

@@ -1,49 +1,137 @@
-using JetBrains.Annotations;
-using LLM.Editor.Helper;
-using UnityEditor;
+using System;
+using System.Linq;
 using UnityEngine;
+using UnityEditor;
+using LLM.Editor.Helper;
+using JetBrains.Annotations;
 
 namespace LLM.Editor.Commands
 {
-    [System.Serializable]
-    public class AddComponentToAssetParams
+    [Serializable]
+    public class AddComponentParams
     {
         public string scriptName;
+        public string componentName;
+        public string targetIdentifier;
+        public string childPath; // Optional path to a child object, e.g., "Launcher/SpawnPoint"
     }
 
     [UsedImplicitly]
     public class AddComponentToAssetCommand : ICommand
     {
-        private readonly AddComponentToAssetParams _params;
+        private readonly AddComponentParams _params;
 
         public AddComponentToAssetCommand(string jsonParams)
         {
-            _params = jsonParams?.FromJson<AddComponentToAssetParams>();
+            _params = jsonParams?.FromJson<AddComponentParams>();
         }
 
         public CommandOutcome Execute(Data.CommandContext context)
         {
-            if (context.CurrentSubject is not string prefabPath)
+            if (_params == null || string.IsNullOrEmpty(_params.targetIdentifier) || (string.IsNullOrEmpty(_params.scriptName) && string.IsNullOrEmpty(_params.componentName)))
             {
-                Debug.LogError($"[AddComponentToAssetCommand] The current subject is not a valid prefab path. {context.CurrentSubject}");
+                Debug.LogError("[AddComponentCommand] Invalid parameters. Target identifier and script name are required.");
                 return CommandOutcome.Error;
             }
+            
+            _params.scriptName ??= _params.componentName;
 
-            var prefabContents = PrefabUtility.LoadPrefabContents(prefabPath);
-            var scriptType = System.Type.GetType($"{_params.scriptName}, Assembly-CSharp");
+            var targetObject = ResolveIdentifier(_params.targetIdentifier);
+            if (!targetObject)
+            {
+                Debug.LogError($"[AddComponentCommand] Could not find target with identifier '{_params.targetIdentifier}'.");
+                return CommandOutcome.Error;
+            }
+
+            var scriptType = GetTypeByName(_params.scriptName);
             if (scriptType == null)
             {
-                Debug.LogError($"[AddComponentToAssetCommand] Could not find script type '{_params.scriptName}'. Did it compile?");
-                PrefabUtility.UnloadPrefabContents(prefabContents);
+                Debug.LogError($"[AddComponentCommand] Could not find script type '{_params.scriptName}'. Did it compile?");
+                return CommandOutcome.Error;
+            }
+            
+            // Determine if we are adding to the root object or a child
+            GameObject objectToAddComponentTo = null;
+            if (targetObject is GameObject rootGo)
+            {
+                if (string.IsNullOrEmpty(_params.childPath))
+                {
+                    objectToAddComponentTo = rootGo;
+                }
+                else
+                {
+                    var childTransform = rootGo.transform.Find(_params.childPath);
+                    if (childTransform)
+                    {
+                        objectToAddComponentTo = childTransform.gameObject;
+                    }
+                    else
+                    {
+                        Debug.LogError($"[AddComponentCommand] Could not find child at path '{_params.childPath}' on target '{rootGo.name}'.");
+                        return CommandOutcome.Error;
+                    }
+                }
+            }
+
+            if (!objectToAddComponentTo)
+            {
+                Debug.LogError($"[AddComponentCommand] Target '{targetObject.name}' is not a GameObject or the child path is invalid.");
                 return CommandOutcome.Error;
             }
 
-            prefabContents.AddComponent(scriptType);
-            PrefabUtility.SaveAsPrefabAsset(prefabContents, prefabPath);
-            PrefabUtility.UnloadPrefabContents(prefabContents);
+            // Handle whether we're editing a prefab asset or a scene instance
+            if (PrefabUtility.IsPartOfPrefabAsset(objectToAddComponentTo))
+            {
+                var prefabPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(targetObject);
+                var prefabContents = PrefabUtility.LoadPrefabContents(prefabPath);
+                
+                // Find the equivalent object within the loaded prefab contents
+                var targetInPrefab = FindEquivalentObjectInPrefab(objectToAddComponentTo, prefabContents);
+                if (targetInPrefab)
+                {
+                    targetInPrefab.AddComponent(scriptType);
+                    PrefabUtility.SaveAsPrefabAsset(prefabContents, prefabPath);
+                    PrefabUtility.UnloadPrefabContents(prefabContents);
+                    Debug.Log($"[AddComponentCommand] Added component '{_params.scriptName}' to prefab at '{prefabPath}'.");
+                }
+                else
+                {
+                    PrefabUtility.UnloadPrefabContents(prefabContents);
+                    Debug.LogError($"[AddComponentCommand] Could not find equivalent of '{objectToAddComponentTo.name}' in prefab contents.");
+                    return CommandOutcome.Error;
+                }
+            }
+            else // It's a scene object
+            {
+                Undo.AddComponent(objectToAddComponentTo, scriptType);
+                Debug.Log($"[AddComponentCommand] Added component '{_params.scriptName}' to scene object '{objectToAddComponentTo.name}'.");
+            }
 
-            Debug.Log($"[AddComponentToAssetCommand] Added component '{_params.scriptName}' to prefab at '{prefabPath}'.");
             return CommandOutcome.Success;
         }
+        
+        private static GameObject FindEquivalentObjectInPrefab(GameObject original, GameObject prefabRoot)
+        {
+            if (original.name == prefabRoot.name) return prefabRoot; // Simple case
+            var originalPath = AnimationUtility.CalculateTransformPath(original.transform, original.transform.root);
+            var foundChild = prefabRoot.transform.Find(originalPath);
+            return foundChild ? foundChild.gameObject : null;
+        }
+
+        private static UnityEngine.Object ResolveIdentifier(string identifier)
+        {
+            if (string.IsNullOrEmpty(identifier)) return null;
+            if (int.TryParse(identifier, out var instanceId))
+            {
+                return EditorUtility.InstanceIDToObject(instanceId);
+            }
+            var path = AssetDatabase.GUIDToAssetPath(identifier);
+            return !string.IsNullOrEmpty(path) ? AssetDatabase.LoadAssetAtPath<GameObject>(path) : null;
+        }
+
+        private static Type GetTypeByName(string typeName)
+        {
+            return AppDomain.CurrentDomain.GetAssemblies().Select(assembly => assembly.GetType(typeName)).FirstOrDefault(type => type != null);
+        }
     }
-}
+}

+ 68 - 0
Assets/LLM/Editor/Commands/CreateGameObjectCommand.cs

@@ -0,0 +1,68 @@
+using System;
+using UnityEngine;
+using UnityEditor;
+using LLM.Editor.Helper;
+using JetBrains.Annotations;
+
+namespace LLM.Editor.Commands
+{
+    [Serializable]
+    public class CreateGameObjectParams
+    {
+        public string gameObjectName;
+        public string primitiveType; // Optional: e.g., "Cube", "Sphere", "Capsule"
+    }
+
+    [UsedImplicitly]
+    public class CreateGameObjectCommand : ICommand
+    {
+        private readonly CreateGameObjectParams _params;
+
+        public CreateGameObjectCommand(string jsonParams)
+        {
+            _params = jsonParams?.FromJson<CreateGameObjectParams>();
+        }
+
+        public CommandOutcome Execute(Data.CommandContext context)
+        {
+            if (_params == null || string.IsNullOrEmpty(_params.gameObjectName))
+            {
+                Debug.LogError("[CreateGameObjectCommand] Invalid parameters. A name for the GameObject is required.");
+                return CommandOutcome.Error;
+            }
+
+            GameObject newGameObject;
+
+            if (string.IsNullOrEmpty(_params.primitiveType))
+            {
+                // Create an empty GameObject if no primitive type is specified
+                newGameObject = new GameObject(_params.gameObjectName);
+            }
+            else
+            {
+                // Create a primitive GameObject if a type is specified
+                try
+                {
+                    var type = (PrimitiveType)Enum.Parse(typeof(PrimitiveType), _params.primitiveType, true);
+                    newGameObject = GameObject.CreatePrimitive(type);
+                    newGameObject.name = _params.gameObjectName;
+                }
+                catch (ArgumentException)
+                {
+                    Debug.LogError($"[CreateGameObjectCommand] Invalid primitive type: '{_params.primitiveType}'. Valid types are Cube, Sphere, Capsule, Cylinder, Plane, Quad.");
+                    return CommandOutcome.Error;
+                }
+            }
+            
+            Undo.RegisterCreatedObjectUndo(newGameObject, $"Create {newGameObject.name}");
+            
+            // Set the new object as the current subject so other commands can modify it
+            context.CurrentSubject = newGameObject;
+            
+            Selection.activeGameObject = newGameObject;
+
+            Debug.Log($"[CreateGameObjectCommand] Successfully created new GameObject named '{newGameObject.name}'.");
+            return CommandOutcome.Success;
+        }
+    }
+}

+ 3 - 0
Assets/LLM/Editor/Commands/CreateGameObjectCommand.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 7f83b8a1208c4114a33691194f185de1
+timeCreated: 1752490526

+ 55 - 0
Assets/LLM/Editor/Commands/EditScriptCommand.cs

@@ -0,0 +1,55 @@
+using System.IO;
+using UnityEditor;
+using UnityEngine;
+using LLM.Editor.Helper;
+using JetBrains.Annotations;
+
+namespace LLM.Editor.Commands
+{
+    [System.Serializable]
+    public class EditScriptParams
+    {
+        public string scriptIdentifier;
+        public string newScriptContent;
+    }
+
+    [UsedImplicitly]
+    public class EditScriptCommand : ICommand
+    {
+        private readonly EditScriptParams _params;
+
+        public EditScriptCommand(string jsonParams)
+        {
+            _params = jsonParams?.FromJson<EditScriptParams>();
+        }
+        
+        public CommandOutcome Execute(Data.CommandContext context)
+        {
+            if (_params == null || string.IsNullOrEmpty(_params.scriptIdentifier) || string.IsNullOrEmpty(_params.newScriptContent))
+            {
+                Debug.LogError("[EditScriptCommand] Invalid parameters. Script identifier and new content are required.");
+                return CommandOutcome.Error;
+            }
+
+            var path = AssetDatabase.GUIDToAssetPath(_params.scriptIdentifier);
+            if (string.IsNullOrEmpty(path) || !path.EndsWith(".cs"))
+            {
+                Debug.LogError($"[EditScriptCommand] Could not find a valid script with GUID '{_params.scriptIdentifier}'.");
+                return CommandOutcome.Error;
+            }
+
+            try
+            {
+                File.WriteAllText(path, _params.newScriptContent);
+                Debug.Log($"[EditScriptCommand] Successfully edited script at: {path}");
+                AssetDatabase.Refresh();
+                return CommandOutcome.Success;
+            }
+            catch (System.Exception e)
+            {
+                Debug.LogError($"[EditScriptCommand] Failed to write to file at '{path}'. Error: {e.Message}");
+                return CommandOutcome.Error;
+            }
+        }
+    }
+}

+ 3 - 0
Assets/LLM/Editor/Commands/EditScriptCommand.cs.meta

@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 221878fcd71a4b5e8fb53ad15d5c7d0c
+timeCreated: 1752489755

+ 106 - 51
Assets/LLM/Editor/Commands/SetComponentValueCommand.cs

@@ -1,24 +1,37 @@
-using LLM.Editor.Helper;
-using UnityEditor;
+using System;
+using System.Linq;
 using UnityEngine;
+using UnityEditor;
+using LLM.Editor.Helper;
+using Newtonsoft.Json.Linq;
+using JetBrains.Annotations;
 
 namespace LLM.Editor.Commands
 {
-    [System.Serializable]
+    [Serializable]
     public class SetComponentValueParams
     {
         public string subjectIdentifier;
         public string componentName;
         public string memberName;
-        public string value;
+        public JToken value; // Changed to JToken to handle both simple values and objects
     }
-    
+
     /// <summary>
     /// The final action command in an analysis workflow. It takes a calculated value
     /// from the LLM and applies it to a specific property on a component.
+    /// Now supports setting simple values AND object references.
     /// </summary>
+    [UsedImplicitly]
     public class SetComponentValueCommand : ICommand
     {
+        [Serializable]
+        private class ObjectReferenceValue
+        {
+            public string identifier;
+            public string childPath; // Optional
+        }
+        
         private readonly SetComponentValueParams _params;
 
         public SetComponentValueCommand(string jsonParams)
@@ -34,8 +47,7 @@ namespace LLM.Editor.Commands
                 return CommandOutcome.Error;
             }
 
-            // Find the target GameObject using the new, more robust method.
-            var targetObject = FindSubject(_params.subjectIdentifier, context);
+            var targetObject = ResolveIdentifier(_params.subjectIdentifier);
             if (!targetObject)
             {
                 Debug.LogError($"[SetComponentValueCommand] Could not find subject GameObject '{_params.subjectIdentifier}'.");
@@ -49,7 +61,6 @@ namespace LLM.Editor.Commands
                 return CommandOutcome.Error;
             }
 
-            // Use SerializedObject for a more robust, Undo-friendly way to set values.
             var serializedObject = new SerializedObject(targetComponent);
             var serializedProperty = serializedObject.FindProperty(_params.memberName);
 
@@ -64,41 +75,24 @@ namespace LLM.Editor.Commands
             // Set value based on the property type
             switch (serializedProperty.propertyType)
             {
+                case SerializedPropertyType.ObjectReference:
+                    if (!SetObjectReferenceValue(serializedProperty, targetObject))
+                    {
+                        return CommandOutcome.Error;
+                    }
+                    break;
                 case SerializedPropertyType.Float:
-                    serializedProperty.floatValue = float.Parse(_params.value);
+                    serializedProperty.floatValue = _params.value.Value<float>();
                     break;
                 case SerializedPropertyType.Integer:
-                    serializedProperty.intValue = int.Parse(_params.value);
+                    serializedProperty.intValue = _params.value.Value<int>();
                     break;
                 case SerializedPropertyType.Boolean:
-                    serializedProperty.boolValue = bool.Parse(_params.value);
+                    serializedProperty.boolValue = _params.value.Value<bool>();
                     break;
                 case SerializedPropertyType.String:
-                    serializedProperty.stringValue = _params.value;
+                    serializedProperty.stringValue = _params.value.Value<string>();
                     break;
-                case SerializedPropertyType.Generic:
-                case SerializedPropertyType.Color:
-                case SerializedPropertyType.ObjectReference:
-                case SerializedPropertyType.LayerMask:
-                case SerializedPropertyType.Enum:
-                case SerializedPropertyType.Vector2:
-                case SerializedPropertyType.Vector3:
-                case SerializedPropertyType.Vector4:
-                case SerializedPropertyType.Rect:
-                case SerializedPropertyType.ArraySize:
-                case SerializedPropertyType.Character:
-                case SerializedPropertyType.AnimationCurve:
-                case SerializedPropertyType.Bounds:
-                case SerializedPropertyType.Gradient:
-                case SerializedPropertyType.Quaternion:
-                case SerializedPropertyType.ExposedReference:
-                case SerializedPropertyType.FixedBufferSize:
-                case SerializedPropertyType.Vector2Int:
-                case SerializedPropertyType.Vector3Int:
-                case SerializedPropertyType.RectInt:
-                case SerializedPropertyType.BoundsInt:
-                case SerializedPropertyType.ManagedReference:
-                case SerializedPropertyType.Hash128:
                 default:
                     Debug.LogError($"[SetComponentValueCommand] The property type '{serializedProperty.propertyType}' is not currently supported for setting values.");
                     return CommandOutcome.Error;
@@ -106,31 +100,92 @@ namespace LLM.Editor.Commands
             
             serializedObject.ApplyModifiedProperties();
 
-            Debug.Log($"[SetComponentValueCommand] Successfully set '{_params.memberName}' on '{_params.componentName}' to '{_params.value}'.");
+            Debug.Log($"[SetComponentValueCommand] Successfully set '{_params.memberName}' on '{_params.componentName}'.");
             return CommandOutcome.Success;
         }
 
-        /// <summary>
-        /// Helper to find the target GameObject, now prioritizing a direct instance ID lookup.
-        /// </summary>
-        private static GameObject FindSubject(string identifier, Data.CommandContext context)
+        private bool SetObjectReferenceValue(SerializedProperty property, GameObject contextObject)
         {
-            // Priority 1: Check if the current subject in the context is the target.
-            if (context.CurrentSubject is GameObject subjectGo && (subjectGo.name == identifier || subjectGo.GetInstanceID().ToString() == identifier))
+            var referenceData = _params.value.ToObject<ObjectReferenceValue>();
+            if (referenceData == null)
+            {
+                // Handle case where value is null to clear the reference
+                if (_params.value.Type == JTokenType.Null)
+                {
+                    property.objectReferenceValue = null;
+                    return true;
+                }
+                Debug.LogError("[SetComponentValueCommand] The 'value' for an ObjectReference must be an object with an 'identifier' or be null.");
+                return false;
+            }
+
+            // The object to assign can be identified relative to the object being modified,
+            // or it can be a completely different object identified by its own GUID/InstanceID.
+            var baseObject = ResolveIdentifier(referenceData.identifier) ?? contextObject;
+            if (!baseObject)
             {
-                return subjectGo;
+                Debug.LogError($"[SetComponentValueCommand] Could not resolve the base identifier '{referenceData.identifier}' for the object reference.");
+                return false;
             }
 
-            // Priority 2: Try to parse the identifier as an instance ID for a direct and reliable lookup.
-            if (!int.TryParse(identifier, out var instanceId)) return GameObject.Find(identifier);
-            var obj = EditorUtility.InstanceIDToObject(instanceId);
-            if (obj is GameObject go)
+            if (!baseObject && !string.IsNullOrEmpty(referenceData.childPath))
             {
-                return go;
+                 Debug.LogError($"[SetComponentValueCommand] Cannot use 'childPath' because the base object '{baseObject.name}' is not a GameObject.");
+                 return false;
             }
 
-            // Priority 3 (Fallback): Find the object by name in the scene. Less reliable.
-            return GameObject.Find(identifier);
+            UnityEngine.Object objectToAssign = baseObject;
+            if (baseObject && !string.IsNullOrEmpty(referenceData.childPath))
+            {
+                var childTransform = baseObject.transform.Find(referenceData.childPath);
+                if (!childTransform)
+                {
+                    Debug.LogError($"[SetComponentValueCommand] Could not find child '{referenceData.childPath}' on object '{baseObject.name}'.");
+                    return false;
+                }
+                objectToAssign = childTransform.gameObject; // Default to assigning the GameObject
+                
+                // If the property is looking for a specific component type (like Transform), get that instead.
+                var componentType = GetTypeFromProperty(property);
+                if (componentType != null)
+                {
+                    var component = childTransform.GetComponent(componentType);
+                    if (component)
+                    {
+                        objectToAssign = component;
+                    }
+                }
+            }
+            
+            property.objectReferenceValue = objectToAssign;
+            return true;
+        }
+
+        private static GameObject ResolveIdentifier(string identifier)
+        {
+            if (string.IsNullOrEmpty(identifier)) return null;
+            if (int.TryParse(identifier, out var instanceId))
+            {
+                return EditorUtility.InstanceIDToObject(instanceId) as GameObject;
+            }
+            var path = AssetDatabase.GUIDToAssetPath(identifier);
+            return !string.IsNullOrEmpty(path) ? AssetDatabase.LoadAssetAtPath<GameObject>(path) : null;
+        }
+        
+        private static Type GetTypeFromProperty(SerializedProperty property)
+        {
+            // Extracts the type from a property string like "PPtr<Transform>"
+            var typeString = property.type;
+            var startIndex = typeString.IndexOf('<');
+            var endIndex = typeString.IndexOf('>');
+            if (startIndex == -1 || endIndex == -1) return null;
+            var managedTypeName = typeString.Substring(startIndex + 1, endIndex - startIndex - 1).Trim();
+            return GetTypeByName(managedTypeName);
+        }
+
+        private static Type GetTypeByName(string typeName)
+        {
+            return AppDomain.CurrentDomain.GetAssemblies().Select(assembly => assembly.GetType(typeName)).FirstOrDefault(type => type != null);
         }
     }
-}
+}

+ 2 - 2
Assets/LLM/Editor/Core/CommandExecutor.cs

@@ -38,7 +38,7 @@ namespace LLM.Editor.Core
                     SessionManager.EndSession();
                     return;
                 }
-                Debug.Log($"[CommandExecutor] Resuming session with {_commandQueue.Count} commands in queue.");
+                Debug.Log($"[CommandExecutor] Resuming session with {_commandQueue.Count} command(s) in queue.");
                 _currentContext = new CommandContext();
                 OnQueueUpdated?.Invoke();
             }
@@ -69,7 +69,7 @@ namespace LLM.Editor.Core
             OnQueueUpdated?.Invoke();
         }
 
-        public static bool HasPendingCommands() => _commandQueue != null && _commandQueue.Any();
+        public static bool HasPendingCommands() => _commandQueue != null;// && _commandQueue.Any();
         
         public static CommandData GetNextCommand() => HasPendingCommands() ? _commandQueue.First() : null;
 

+ 2 - 2
Assets/LLM/Editor/Core/SessionManager.cs

@@ -63,7 +63,7 @@ namespace LLM.Editor.Core
             var json = wrapper.ToJson();
             var path = Path.Combine(_sessionCacheRoot, _currentSessionId, QUEUE_FILE);
             File.WriteAllText(path, json);
-            Debug.Log($"[SessionManager] Saved {queue.Count} commands to queue.");
+            Debug.Log($"[SessionManager] Saved {queue.Count} command(s) to queue.");
         }
 
         public static List<CommandData> LoadCommandQueue()
@@ -75,7 +75,7 @@ namespace LLM.Editor.Core
 
             var json = File.ReadAllText(path);
             var wrapper = json.FromJson<CommandListWrapper>();
-            Debug.Log($"[SessionManager] Loaded {wrapper.commands.Count} commands from queue.");
+            Debug.Log($"[SessionManager] Loaded {wrapper.commands.Count} command(s) from queue.");
             return wrapper.commands ?? new List<CommandData>();
         }
 

+ 1 - 1
ProjectSettings/EditorSettings.asset

@@ -23,7 +23,7 @@ EditorSettings:
   m_AsyncShaderCompilation: 1
   m_CachingShaderPreprocessor: 1
   m_PrefabModeAllowAutoSave: 1
-  m_EnterPlayModeOptionsEnabled: 0
+  m_EnterPlayModeOptionsEnabled: 1
   m_EnterPlayModeOptions: 3
   m_GameObjectNamingDigits: 1
   m_GameObjectNamingScheme: 0