@mflrevan/ucp 0.2.0 → 0.2.3

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.
Files changed (66) hide show
  1. package/README.md +3 -3
  2. package/bridge/com.ucp.bridge/CHANGELOG.md +56 -0
  3. package/bridge/com.ucp.bridge/CHANGELOG.md.meta +7 -0
  4. package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +573 -0
  5. package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs.meta +2 -0
  6. package/bridge/com.ucp.bridge/Editor/Bridge.meta +8 -0
  7. package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +499 -0
  8. package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs.meta +2 -0
  9. package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs +230 -0
  10. package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs.meta +2 -0
  11. package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs +26 -0
  12. package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs.meta +2 -0
  13. package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs +435 -0
  14. package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs.meta +2 -0
  15. package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs +130 -0
  16. package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs.meta +2 -0
  17. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +319 -0
  18. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs.meta +2 -0
  19. package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +291 -0
  20. package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs.meta +2 -0
  21. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +295 -0
  22. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs.meta +2 -0
  23. package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +38 -0
  24. package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs.meta +2 -0
  25. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +242 -0
  26. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs.meta +2 -0
  27. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +551 -0
  28. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs.meta +2 -0
  29. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +70 -0
  30. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs.meta +2 -0
  31. package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs +125 -0
  32. package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs.meta +2 -0
  33. package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs +104 -0
  34. package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs.meta +2 -0
  35. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +227 -0
  36. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs.meta +2 -0
  37. package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +180 -0
  38. package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs.meta +2 -0
  39. package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs +611 -0
  40. package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs.meta +2 -0
  41. package/bridge/com.ucp.bridge/Editor/Controllers.meta +8 -0
  42. package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs +45 -0
  43. package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs.meta +2 -0
  44. package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs +80 -0
  45. package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs.meta +2 -0
  46. package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +358 -0
  47. package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs.meta +2 -0
  48. package/bridge/com.ucp.bridge/Editor/Protocol.meta +8 -0
  49. package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs +37 -0
  50. package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs.meta +2 -0
  51. package/bridge/com.ucp.bridge/Editor/Scripts.meta +8 -0
  52. package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef +16 -0
  53. package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef.meta +7 -0
  54. package/bridge/com.ucp.bridge/Editor.meta +8 -0
  55. package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +14 -0
  56. package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +7 -0
  57. package/bridge/com.ucp.bridge/Runtime.meta +8 -0
  58. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +194 -0
  59. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs.meta +2 -0
  60. package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef +12 -0
  61. package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef.meta +7 -0
  62. package/bridge/com.ucp.bridge/Tests/Editor.meta +8 -0
  63. package/bridge/com.ucp.bridge/Tests.meta +8 -0
  64. package/bridge/com.ucp.bridge/package.json +27 -0
  65. package/bridge/com.ucp.bridge/package.json.meta +7 -0
  66. package/package.json +1 -1
@@ -0,0 +1,319 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using UnityEditor;
4
+ using UnityEditor.SceneManagement;
5
+ using UnityEngine;
6
+ using UnityEngine.SceneManagement;
7
+
8
+ namespace UCP.Bridge
9
+ {
10
+ public static class HierarchyController
11
+ {
12
+ public static void Register(CommandRouter router)
13
+ {
14
+ router.Register("object/create", HandleCreate);
15
+ router.Register("object/delete", HandleDelete);
16
+ router.Register("object/reparent", HandleReparent);
17
+ router.Register("object/instantiate", HandleInstantiate);
18
+ router.Register("object/add-component", HandleAddComponent);
19
+ router.Register("object/remove-component", HandleRemoveComponent);
20
+ }
21
+
22
+ private static object HandleCreate(string paramsJson)
23
+ {
24
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
25
+ var name = "GameObject";
26
+ if (p != null && p.TryGetValue("name", out var nameObj) && nameObj != null)
27
+ name = nameObj.ToString();
28
+
29
+ var go = new GameObject(name);
30
+ Undo.RegisterCreatedObjectUndo(go, "UCP Create GameObject");
31
+
32
+ // Optional parent
33
+ if (p != null && p.TryGetValue("parent", out var parentObj))
34
+ {
35
+ int parentId = Convert.ToInt32(parentObj);
36
+ var parent = FindGameObject(parentId);
37
+ go.transform.SetParent(parent.transform, false);
38
+ }
39
+
40
+ // Optional position
41
+ if (p != null && p.TryGetValue("position", out var posObj) && posObj is List<object> pos && pos.Count >= 3)
42
+ {
43
+ go.transform.localPosition = new Vector3(
44
+ Convert.ToSingle(pos[0]),
45
+ Convert.ToSingle(pos[1]),
46
+ Convert.ToSingle(pos[2]));
47
+ }
48
+
49
+ // Optional rotation (euler angles)
50
+ if (p != null && p.TryGetValue("rotation", out var rotObj) && rotObj is List<object> rot && rot.Count >= 3)
51
+ {
52
+ go.transform.localEulerAngles = new Vector3(
53
+ Convert.ToSingle(rot[0]),
54
+ Convert.ToSingle(rot[1]),
55
+ Convert.ToSingle(rot[2]));
56
+ }
57
+
58
+ EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
59
+
60
+ return new Dictionary<string, object>
61
+ {
62
+ ["status"] = "ok",
63
+ ["instanceId"] = go.GetInstanceID(),
64
+ ["name"] = go.name
65
+ };
66
+ }
67
+
68
+ private static object HandleDelete(string paramsJson)
69
+ {
70
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
71
+ if (p == null || !p.TryGetValue("instanceId", out var idObj))
72
+ throw new ArgumentException("Missing 'instanceId' parameter");
73
+
74
+ int instanceId = Convert.ToInt32(idObj);
75
+ var go = FindGameObject(instanceId);
76
+ string name = go.name;
77
+
78
+ Undo.DestroyObjectImmediate(go);
79
+ EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
80
+
81
+ return new Dictionary<string, object>
82
+ {
83
+ ["status"] = "ok",
84
+ ["deleted"] = name,
85
+ ["instanceId"] = instanceId
86
+ };
87
+ }
88
+
89
+ private static object HandleReparent(string paramsJson)
90
+ {
91
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
92
+ if (p == null || !p.TryGetValue("instanceId", out var idObj))
93
+ throw new ArgumentException("Missing 'instanceId' parameter");
94
+
95
+ int instanceId = Convert.ToInt32(idObj);
96
+ var go = FindGameObject(instanceId);
97
+
98
+ Undo.SetTransformParent(go.transform, null, "UCP Reparent");
99
+
100
+ if (p.TryGetValue("parent", out var parentObj) && parentObj != null)
101
+ {
102
+ int parentId = Convert.ToInt32(parentObj);
103
+ var parent = FindGameObject(parentId);
104
+ Undo.SetTransformParent(go.transform, parent.transform, "UCP Reparent");
105
+ }
106
+ // else: parent = null means move to root
107
+
108
+ // Optional sibling index
109
+ if (p.TryGetValue("siblingIndex", out var sibObj))
110
+ {
111
+ go.transform.SetSiblingIndex(Convert.ToInt32(sibObj));
112
+ }
113
+
114
+ EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
115
+
116
+ return new Dictionary<string, object>
117
+ {
118
+ ["status"] = "ok",
119
+ ["instanceId"] = instanceId,
120
+ ["name"] = go.name,
121
+ ["parent"] = go.transform.parent != null ? go.transform.parent.gameObject.name : null
122
+ };
123
+ }
124
+
125
+ private static object HandleInstantiate(string paramsJson)
126
+ {
127
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
128
+ if (p == null)
129
+ throw new ArgumentException("Missing parameters");
130
+
131
+ GameObject source = null;
132
+
133
+ // Instantiate from prefab asset path
134
+ if (p.TryGetValue("prefab", out var prefabObj) && prefabObj != null)
135
+ {
136
+ string prefabPath = prefabObj.ToString();
137
+ var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
138
+ if (prefab == null)
139
+ throw new ArgumentException($"Prefab not found: {prefabPath}");
140
+ source = prefab;
141
+ }
142
+ // Instantiate from existing scene object (clone)
143
+ else if (p.TryGetValue("sourceId", out var srcObj))
144
+ {
145
+ int srcId = Convert.ToInt32(srcObj);
146
+ source = FindGameObject(srcId);
147
+ }
148
+ else
149
+ {
150
+ throw new ArgumentException("Must provide 'prefab' (asset path) or 'sourceId' (instanceId)");
151
+ }
152
+
153
+ var instance = (GameObject)PrefabUtility.InstantiatePrefab(source);
154
+ if (instance == null)
155
+ {
156
+ // Fallback for non-prefab objects (e.g. cloning scene objects)
157
+ instance = UnityEngine.Object.Instantiate(source);
158
+ }
159
+
160
+ Undo.RegisterCreatedObjectUndo(instance, "UCP Instantiate");
161
+
162
+ // Optional parent
163
+ if (p.TryGetValue("parent", out var parentObj) && parentObj != null)
164
+ {
165
+ int parentId = Convert.ToInt32(parentObj);
166
+ var parent = FindGameObject(parentId);
167
+ instance.transform.SetParent(parent.transform, false);
168
+ }
169
+
170
+ // Optional name override
171
+ if (p.TryGetValue("name", out var nameObj) && nameObj != null)
172
+ {
173
+ instance.name = nameObj.ToString();
174
+ }
175
+
176
+ // Optional position
177
+ if (p.TryGetValue("position", out var posObj) && posObj is List<object> pos && pos.Count >= 3)
178
+ {
179
+ instance.transform.localPosition = new Vector3(
180
+ Convert.ToSingle(pos[0]),
181
+ Convert.ToSingle(pos[1]),
182
+ Convert.ToSingle(pos[2]));
183
+ }
184
+
185
+ EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
186
+
187
+ return new Dictionary<string, object>
188
+ {
189
+ ["status"] = "ok",
190
+ ["instanceId"] = instance.GetInstanceID(),
191
+ ["name"] = instance.name
192
+ };
193
+ }
194
+
195
+ private static object HandleAddComponent(string paramsJson)
196
+ {
197
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
198
+ if (p == null || !p.TryGetValue("instanceId", out var idObj))
199
+ throw new ArgumentException("Missing 'instanceId' parameter");
200
+ if (!p.TryGetValue("type", out var typeObj) || typeObj == null)
201
+ throw new ArgumentException("Missing 'type' parameter");
202
+
203
+ int instanceId = Convert.ToInt32(idObj);
204
+ var go = FindGameObject(instanceId);
205
+ string typeName = typeObj.ToString();
206
+
207
+ // Resolve component type
208
+ Type compType = ResolveComponentType(typeName);
209
+ if (compType == null)
210
+ throw new ArgumentException($"Component type not found: {typeName}");
211
+
212
+ var comp = Undo.AddComponent(go, compType);
213
+ EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
214
+
215
+ return new Dictionary<string, object>
216
+ {
217
+ ["status"] = "ok",
218
+ ["instanceId"] = instanceId,
219
+ ["component"] = comp.GetType().Name,
220
+ ["componentFullType"] = comp.GetType().FullName
221
+ };
222
+ }
223
+
224
+ private static object HandleRemoveComponent(string paramsJson)
225
+ {
226
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
227
+ if (p == null || !p.TryGetValue("instanceId", out var idObj))
228
+ throw new ArgumentException("Missing 'instanceId' parameter");
229
+ if (!p.TryGetValue("type", out var typeObj) || typeObj == null)
230
+ throw new ArgumentException("Missing 'type' parameter");
231
+
232
+ int instanceId = Convert.ToInt32(idObj);
233
+ var go = FindGameObject(instanceId);
234
+ string typeName = typeObj.ToString();
235
+
236
+ Component target = null;
237
+ foreach (var c in go.GetComponents<Component>())
238
+ {
239
+ if (c == null) continue;
240
+ if (c.GetType().Name == typeName || c.GetType().FullName == typeName)
241
+ {
242
+ target = c;
243
+ break;
244
+ }
245
+ }
246
+
247
+ if (target == null)
248
+ throw new ArgumentException($"Component '{typeName}' not found on '{go.name}'");
249
+ if (target is Transform)
250
+ throw new ArgumentException("Cannot remove Transform component");
251
+
252
+ Undo.DestroyObjectImmediate(target);
253
+ EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
254
+
255
+ return new Dictionary<string, object>
256
+ {
257
+ ["status"] = "ok",
258
+ ["instanceId"] = instanceId,
259
+ ["removed"] = typeName
260
+ };
261
+ }
262
+
263
+ private static Type ResolveComponentType(string typeName)
264
+ {
265
+ // Try exact match first
266
+ var type = Type.GetType(typeName);
267
+ if (type != null && typeof(Component).IsAssignableFrom(type))
268
+ return type;
269
+
270
+ // Search Unity assemblies
271
+ foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
272
+ {
273
+ foreach (var t in assembly.GetTypes())
274
+ {
275
+ if (!typeof(Component).IsAssignableFrom(t)) continue;
276
+ if (t.Name == typeName || t.FullName == typeName)
277
+ return t;
278
+ }
279
+ }
280
+
281
+ // Common Unity types fallback
282
+ var unityType = Type.GetType($"UnityEngine.{typeName}, UnityEngine.CoreModule");
283
+ if (unityType != null && typeof(Component).IsAssignableFrom(unityType))
284
+ return unityType;
285
+
286
+ return null;
287
+ }
288
+
289
+ private static GameObject FindGameObject(int instanceId)
290
+ {
291
+ var obj = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
292
+ if (obj != null) return obj;
293
+
294
+ for (int i = 0; i < SceneManager.sceneCount; i++)
295
+ {
296
+ var scene = SceneManager.GetSceneAt(i);
297
+ if (!scene.isLoaded) continue;
298
+ foreach (var root in scene.GetRootGameObjects())
299
+ {
300
+ var found = FindInHierarchy(root, instanceId);
301
+ if (found != null) return found;
302
+ }
303
+ }
304
+
305
+ throw new ArgumentException($"GameObject not found: {instanceId}");
306
+ }
307
+
308
+ private static GameObject FindInHierarchy(GameObject go, int instanceId)
309
+ {
310
+ if (go.GetInstanceID() == instanceId) return go;
311
+ for (int i = 0; i < go.transform.childCount; i++)
312
+ {
313
+ var found = FindInHierarchy(go.transform.GetChild(i).gameObject, instanceId);
314
+ if (found != null) return found;
315
+ }
316
+ return null;
317
+ }
318
+ }
319
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 8d90db77ca2967941a28a26f7af1adfe
@@ -0,0 +1,291 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Linq;
4
+ using System.Text.RegularExpressions;
5
+ using UnityEngine;
6
+
7
+ namespace UCP.Bridge
8
+ {
9
+ public static class LogsController
10
+ {
11
+ private const int MaxHistoryEntries = 2000;
12
+ private const int MaxBulkResults = 10;
13
+ private const int DefaultSearchWindow = 200;
14
+ private const int MaxPreviewLength = 200;
15
+
16
+ private static readonly object s_historyLock = new object();
17
+ private static readonly List<LogRecord> s_history = new List<LogRecord>();
18
+ private static long s_nextId = 1;
19
+
20
+ public static void Register(CommandRouter router)
21
+ {
22
+ router.Register("logs/subscribe", _ => new Dictionary<string, object> { ["subscribed"] = true });
23
+ router.Register("logs/unsubscribe", _ => new Dictionary<string, object> { ["unsubscribed"] = true });
24
+ router.Register("logs/tail", HandleTail);
25
+ router.Register("logs/search", HandleSearch);
26
+ router.Register("logs/get", HandleGet);
27
+ }
28
+
29
+ public static Dictionary<string, object> RecordLog(string message, string stackTrace, LogType type)
30
+ {
31
+ return RecordLog(NormalizeLevel(type), message, stackTrace);
32
+ }
33
+
34
+ public static void ClearHistoryForTests()
35
+ {
36
+ lock (s_historyLock)
37
+ {
38
+ s_history.Clear();
39
+ s_nextId = 1;
40
+ }
41
+ }
42
+
43
+ public static Dictionary<string, object> RecordTestLog(string level, string message, string stackTrace = "")
44
+ {
45
+ return RecordLog(level, message, stackTrace);
46
+ }
47
+
48
+ private static object HandleTail(string paramsJson)
49
+ {
50
+ var query = ParseQuery(paramsJson, includePattern: false);
51
+ return BuildListResult(QueryHistory(query));
52
+ }
53
+
54
+ private static object HandleSearch(string paramsJson)
55
+ {
56
+ var query = ParseQuery(paramsJson, includePattern: true);
57
+ return BuildListResult(QueryHistory(query));
58
+ }
59
+
60
+ private static object HandleGet(string paramsJson)
61
+ {
62
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
63
+ if (p == null || !p.TryGetValue("id", out var idObj))
64
+ throw new ArgumentException("Missing 'id' parameter");
65
+
66
+ long id = Convert.ToInt64(idObj);
67
+
68
+ lock (s_historyLock)
69
+ {
70
+ var entry = s_history.FirstOrDefault(record => record.Id == id);
71
+ if (entry == null)
72
+ throw new ArgumentException($"Log entry not found: {id}");
73
+
74
+ return SerializeFull(entry);
75
+ }
76
+ }
77
+
78
+ private static Dictionary<string, object> RecordLog(string level, string message, string stackTrace)
79
+ {
80
+ lock (s_historyLock)
81
+ {
82
+ var entry = new LogRecord
83
+ {
84
+ Id = s_nextId++,
85
+ Level = NormalizeLevel(level),
86
+ Message = message ?? string.Empty,
87
+ StackTrace = stackTrace ?? string.Empty,
88
+ Timestamp = DateTime.UtcNow.ToString("o")
89
+ };
90
+
91
+ s_history.Add(entry);
92
+ if (s_history.Count > MaxHistoryEntries)
93
+ s_history.RemoveAt(0);
94
+
95
+ return SerializeFull(entry);
96
+ }
97
+ }
98
+
99
+ private static LogQuery ParseQuery(string paramsJson, bool includePattern)
100
+ {
101
+ var p = MiniJson.Deserialize(paramsJson) as Dictionary<string, object>;
102
+ var query = new LogQuery();
103
+
104
+ if (p != null)
105
+ {
106
+ if (p.TryGetValue("level", out var levelObj) && levelObj != null)
107
+ query.Level = NormalizeLevel(levelObj.ToString());
108
+ if (p.TryGetValue("count", out var countObj) && countObj != null)
109
+ query.Count = Math.Max(1, Convert.ToInt32(countObj));
110
+ if (p.TryGetValue("beforeId", out var beforeObj) && beforeObj != null)
111
+ query.BeforeId = Convert.ToInt64(beforeObj);
112
+ if (p.TryGetValue("afterId", out var afterObj) && afterObj != null)
113
+ query.AfterId = Convert.ToInt64(afterObj);
114
+
115
+ if (includePattern && p.TryGetValue("pattern", out var patternObj) && patternObj != null)
116
+ {
117
+ query.Pattern = patternObj.ToString();
118
+ try
119
+ {
120
+ query.Regex = new Regex(query.Pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
121
+ }
122
+ catch (Exception ex)
123
+ {
124
+ throw new ArgumentException($"Invalid regex pattern: {ex.Message}");
125
+ }
126
+ }
127
+ }
128
+
129
+ if (query.Count <= 0)
130
+ query.Count = DefaultSearchWindow;
131
+
132
+ return query;
133
+ }
134
+
135
+ private static LogQueryResult QueryHistory(LogQuery query)
136
+ {
137
+ lock (s_historyLock)
138
+ {
139
+ IEnumerable<LogRecord> candidates = s_history;
140
+
141
+ if (query.BeforeId.HasValue)
142
+ candidates = candidates.Where(entry => entry.Id < query.BeforeId.Value);
143
+ if (query.AfterId.HasValue)
144
+ candidates = candidates.Where(entry => entry.Id > query.AfterId.Value);
145
+ if (!string.IsNullOrEmpty(query.Level))
146
+ candidates = candidates.Where(entry => PassesLevel(entry.Level, query.Level));
147
+
148
+ candidates = candidates.OrderByDescending(entry => entry.Id).Take(query.Count);
149
+
150
+ if (query.Regex != null)
151
+ {
152
+ candidates = candidates.Where(entry =>
153
+ query.Regex.IsMatch(entry.Message)
154
+ || (!string.IsNullOrEmpty(entry.StackTrace) && query.Regex.IsMatch(entry.StackTrace))
155
+ );
156
+ }
157
+
158
+ var allMatches = candidates.ToList();
159
+ var returned = allMatches.Take(MaxBulkResults).Select(SerializeSummary).ToList();
160
+
161
+ return new LogQueryResult
162
+ {
163
+ Total = allMatches.Count,
164
+ Returned = returned,
165
+ Truncated = allMatches.Count > MaxBulkResults
166
+ };
167
+ }
168
+ }
169
+
170
+ private static Dictionary<string, object> BuildListResult(LogQueryResult queryResult)
171
+ {
172
+ return new Dictionary<string, object>
173
+ {
174
+ ["logs"] = queryResult.Returned.Cast<object>().ToList(),
175
+ ["total"] = queryResult.Total,
176
+ ["returned"] = queryResult.Returned.Count,
177
+ ["truncated"] = queryResult.Truncated
178
+ };
179
+ }
180
+
181
+ private static Dictionary<string, object> SerializeSummary(LogRecord entry)
182
+ {
183
+ return new Dictionary<string, object>
184
+ {
185
+ ["id"] = entry.Id,
186
+ ["level"] = entry.Level,
187
+ ["timestamp"] = entry.Timestamp,
188
+ ["messagePreview"] = Preview(entry.Message, MaxPreviewLength),
189
+ ["hasStackTrace"] = !string.IsNullOrEmpty(entry.StackTrace)
190
+ };
191
+ }
192
+
193
+ private static Dictionary<string, object> SerializeFull(LogRecord entry)
194
+ {
195
+ return new Dictionary<string, object>
196
+ {
197
+ ["id"] = entry.Id,
198
+ ["level"] = entry.Level,
199
+ ["timestamp"] = entry.Timestamp,
200
+ ["message"] = entry.Message,
201
+ ["stackTrace"] = entry.StackTrace
202
+ };
203
+ }
204
+
205
+ private static bool PassesLevel(string value, string threshold)
206
+ {
207
+ return Severity(value) >= Severity(threshold);
208
+ }
209
+
210
+ private static int Severity(string level)
211
+ {
212
+ switch (NormalizeLevel(level))
213
+ {
214
+ case "error":
215
+ case "exception":
216
+ return 2;
217
+ case "warning":
218
+ return 1;
219
+ default:
220
+ return 0;
221
+ }
222
+ }
223
+
224
+ private static string NormalizeLevel(LogType type)
225
+ {
226
+ switch (type)
227
+ {
228
+ case LogType.Error:
229
+ case LogType.Assert:
230
+ return "error";
231
+ case LogType.Exception:
232
+ return "exception";
233
+ case LogType.Warning:
234
+ return "warning";
235
+ default:
236
+ return "info";
237
+ }
238
+ }
239
+
240
+ private static string NormalizeLevel(string level)
241
+ {
242
+ if (string.IsNullOrEmpty(level))
243
+ return "info";
244
+
245
+ var normalized = level.Trim().ToLowerInvariant();
246
+ switch (normalized)
247
+ {
248
+ case "warn":
249
+ return "warning";
250
+ case "err":
251
+ return "error";
252
+ default:
253
+ return normalized;
254
+ }
255
+ }
256
+
257
+ private static string Preview(string value, int maxChars)
258
+ {
259
+ if (string.IsNullOrEmpty(value) || value.Length <= maxChars)
260
+ return value ?? string.Empty;
261
+
262
+ return value.Substring(0, maxChars) + "...";
263
+ }
264
+
265
+ private sealed class LogRecord
266
+ {
267
+ public long Id;
268
+ public string Level;
269
+ public string Message;
270
+ public string StackTrace;
271
+ public string Timestamp;
272
+ }
273
+
274
+ private sealed class LogQuery
275
+ {
276
+ public string Level;
277
+ public string Pattern;
278
+ public Regex Regex;
279
+ public int Count;
280
+ public long? BeforeId;
281
+ public long? AfterId;
282
+ }
283
+
284
+ private sealed class LogQueryResult
285
+ {
286
+ public int Total;
287
+ public List<Dictionary<string, object>> Returned;
288
+ public bool Truncated;
289
+ }
290
+ }
291
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 76c15fe92f2848f0a69e0b37b4d8c9a1