@beremaran/godot-agent-loop 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +925 -0
  3. package/addons/godot_agent_loop/LICENSE +23 -0
  4. package/addons/godot_agent_loop/README.md +31 -0
  5. package/addons/godot_agent_loop/plugin.cfg +8 -0
  6. package/addons/godot_agent_loop/plugin.gd +417 -0
  7. package/agent-plugin/.claude-plugin/plugin.json +19 -0
  8. package/agent-plugin/.codex-plugin/plugin.json +37 -0
  9. package/agent-plugin/.mcp.json +14 -0
  10. package/agent-plugin/adapter-manifest.json +45 -0
  11. package/agent-plugin/pi/extension.ts +136 -0
  12. package/agent-plugin/skills/build-godot-game/SKILL.md +41 -0
  13. package/agent-plugin/skills/debug-godot-game/SKILL.md +37 -0
  14. package/agent-plugin/skills/ship-godot-game/SKILL.md +46 -0
  15. package/agent-plugin/skills/ship-godot-game/agents/openai.yaml +4 -0
  16. package/agent-plugin/skills/verify-godot-change/SKILL.md +35 -0
  17. package/build/authoring-session-manager.js +237 -0
  18. package/build/domain-tool-registries.js +207 -0
  19. package/build/editor-bridge-protocol.js +1 -0
  20. package/build/editor-connection.js +112 -0
  21. package/build/editor-mutation-guard.js +30 -0
  22. package/build/editor-plugin-installer.js +220 -0
  23. package/build/engine-api/extension_api-4.7.stable.official.5b4e0cb0f.json +356830 -0
  24. package/build/game-command-service.js +49 -0
  25. package/build/game-connection.js +337 -0
  26. package/build/godot-executable.js +156 -0
  27. package/build/godot-process-manager.js +112 -0
  28. package/build/godot-subprocess.js +23 -0
  29. package/build/headless-operation-runner.js +68 -0
  30. package/build/headless-operation-service.js +65 -0
  31. package/build/index.js +478 -0
  32. package/build/interaction-server-installer.js +234 -0
  33. package/build/opencode-setup.js +199 -0
  34. package/build/project-support.js +219 -0
  35. package/build/runtime-protocol.js +202 -0
  36. package/build/scripts/godot_operations.gd +1534 -0
  37. package/build/scripts/mcp_editor_plugin.gd +417 -0
  38. package/build/scripts/mcp_interaction_server.gd +1255 -0
  39. package/build/scripts/mcp_runtime/audio_animation_domain.gd +568 -0
  40. package/build/scripts/mcp_runtime/command_params.gd +339 -0
  41. package/build/scripts/mcp_runtime/core_domain.gd +591 -0
  42. package/build/scripts/mcp_runtime/input_domain.gd +695 -0
  43. package/build/scripts/mcp_runtime/networking_domain.gd +356 -0
  44. package/build/scripts/mcp_runtime/physics_domain.gd +653 -0
  45. package/build/scripts/mcp_runtime/privileged_command_policy.gd +81 -0
  46. package/build/scripts/mcp_runtime/rendering_domain.gd +807 -0
  47. package/build/scripts/mcp_runtime/runtime_domain.gd +108 -0
  48. package/build/scripts/mcp_runtime/scene_2d_domain.gd +527 -0
  49. package/build/scripts/mcp_runtime/scene_3d_domain.gd +573 -0
  50. package/build/scripts/mcp_runtime/system_domain.gd +277 -0
  51. package/build/scripts/mcp_runtime/ui_domain.gd +619 -0
  52. package/build/scripts/mcp_runtime/variant_codec.gd +335 -0
  53. package/build/scripts/validate_script.gd +28 -0
  54. package/build/server-instructions.js +11 -0
  55. package/build/session-timing.js +20 -0
  56. package/build/tool-argument-validation.js +120 -0
  57. package/build/tool-definitions.js +2727 -0
  58. package/build/tool-handlers/game-tool-handlers.js +1345 -0
  59. package/build/tool-handlers/lifecycle-tool-handlers.js +346 -0
  60. package/build/tool-handlers/project-handler-services.js +1458 -0
  61. package/build/tool-handlers/project-tool-handlers.js +1506 -0
  62. package/build/tool-handlers/visual-regression-service.js +139 -0
  63. package/build/tool-manifest.js +1193 -0
  64. package/build/tool-mutation-policy.js +122 -0
  65. package/build/tool-registry.js +34 -0
  66. package/build/tool-surface.js +70 -0
  67. package/build/utils.js +273 -0
  68. package/package.json +110 -0
  69. package/product.json +52 -0
  70. package/scripts/prepare-package.js +42 -0
@@ -0,0 +1,339 @@
1
+ extends RefCounted
2
+
3
+ # Validated accessor for one command's params dictionary. Records the first
4
+ # failure as a message plus structured {param, reason, ...} details; every
5
+ # handler must stop via _params_invalid() once any accessor fails, so no
6
+ # command continues past invalid input.
7
+ var _params: Dictionary
8
+ var error_message: String = ""
9
+ var error_details: Dictionary = {}
10
+
11
+ func _init(params: Dictionary) -> void:
12
+ _params = params
13
+
14
+ func failed() -> bool:
15
+ return not error_message.is_empty()
16
+
17
+ func fail(message: String, details: Dictionary = {}) -> void:
18
+ if failed():
19
+ return
20
+ error_message = message
21
+ error_details = details
22
+
23
+ func _fail_param(name: String, reason: String, message: String, extra: Dictionary = {}) -> void:
24
+ var details: Dictionary = {"param": name, "reason": reason}
25
+ details.merge(extra)
26
+ fail(message, details)
27
+
28
+ func has_param(name: String) -> bool:
29
+ return _params.has(name)
30
+
31
+ # Raw passthrough for values that are converted later (e.g. typed Variant
32
+ # payloads handled by the JSON/Variant codec).
33
+ func raw(name: String, default_value: Variant = null) -> Variant:
34
+ return _params.get(name, default_value)
35
+
36
+ func required_string(name: String) -> String:
37
+ if not _params.has(name):
38
+ _fail_param(name, "missing", "%s is required" % name)
39
+ return ""
40
+ return _string_value(name, "")
41
+
42
+ func optional_string(name: String, default_value: String = "") -> String:
43
+ if not _params.has(name):
44
+ return default_value
45
+ return _string_value(name, default_value)
46
+
47
+ func _string_value(name: String, default_value: String) -> String:
48
+ var value: Variant = _params[name]
49
+ if not value is String:
50
+ _fail_param(name, "invalid_type", "%s must be a string" % name)
51
+ return default_value
52
+ return value
53
+
54
+ func required_number(name: String, min_value: float = -INF, max_value: float = INF) -> float:
55
+ if not _params.has(name):
56
+ _fail_param(name, "missing", "%s is required" % name)
57
+ return 0.0
58
+ return _number_value(name, 0.0, min_value, max_value)
59
+
60
+ func optional_number(name: String, default_value: float, min_value: float = -INF, max_value: float = INF) -> float:
61
+ if not _params.has(name):
62
+ return default_value
63
+ return _number_value(name, default_value, min_value, max_value)
64
+
65
+ func _number_value(name: String, default_value: float, min_value: float, max_value: float) -> float:
66
+ var value: Variant = _params[name]
67
+ if not (value is float or value is int):
68
+ _fail_param(name, "invalid_type", "%s must be a number" % name)
69
+ return default_value
70
+ var number: float = value
71
+ if number < min_value or number > max_value:
72
+ _fail_range(name, number, min_value, max_value)
73
+ return default_value
74
+ return number
75
+
76
+ func required_int(name: String, min_value: float = -INF, max_value: float = INF) -> int:
77
+ if not _params.has(name):
78
+ _fail_param(name, "missing", "%s is required" % name)
79
+ return 0
80
+ return _int_value(name, 0, min_value, max_value)
81
+
82
+ func optional_int(name: String, default_value: int, min_value: float = -INF, max_value: float = INF) -> int:
83
+ if not _params.has(name):
84
+ return default_value
85
+ return _int_value(name, default_value, min_value, max_value)
86
+
87
+ func _int_value(name: String, default_value: int, min_value: float, max_value: float) -> int:
88
+ var value: Variant = _params[name]
89
+ var number: int
90
+ if value is int:
91
+ number = value
92
+ elif value is float and _is_integral(value):
93
+ # JSON parsing yields floats for every number; accept integral ones.
94
+ number = to_int(value)
95
+ else:
96
+ _fail_param(name, "invalid_type", "%s must be an integer" % name)
97
+ return default_value
98
+ if number < min_value or number > max_value:
99
+ _fail_range(name, number, min_value, max_value)
100
+ return default_value
101
+ return number
102
+
103
+ func _fail_range(name: String, value: Variant, min_value: float, max_value: float) -> void:
104
+ var details: Dictionary = {"value": value}
105
+ var constraint: String
106
+ if min_value != -INF and max_value != INF:
107
+ details.merge({"min": min_value, "max": max_value})
108
+ constraint = "between %s and %s" % [min_value, max_value]
109
+ elif min_value != -INF:
110
+ details["min"] = min_value
111
+ constraint = "at least %s" % min_value
112
+ else:
113
+ details["max"] = max_value
114
+ constraint = "at most %s" % max_value
115
+ _fail_param(name, "out_of_range", "%s must be %s" % [name, constraint], details)
116
+
117
+ func optional_bool(name: String, default_value: bool) -> bool:
118
+ if not _params.has(name):
119
+ return default_value
120
+ var value: Variant = _params[name]
121
+ if not value is bool:
122
+ _fail_param(name, "invalid_type", "%s must be a boolean" % name)
123
+ return default_value
124
+ return value
125
+
126
+ func required_enum(name: String, allowed: Array) -> String:
127
+ var value: String = required_string(name)
128
+ return _enum_value(name, value, "", allowed)
129
+
130
+ func optional_enum(name: String, default_value: String, allowed: Array) -> String:
131
+ var value: String = optional_string(name, default_value)
132
+ return _enum_value(name, value, default_value, allowed)
133
+
134
+ func _enum_value(name: String, value: String, default_value: String, allowed: Array) -> String:
135
+ if failed():
136
+ return default_value
137
+ if not allowed.has(value):
138
+ _fail_param(name, "invalid_value", "%s must be one of: %s" % [name, ", ".join(allowed)], {"allowed": allowed, "value": value})
139
+ return default_value
140
+ return value
141
+
142
+ func required_array(name: String) -> Array:
143
+ if not _params.has(name):
144
+ _fail_param(name, "missing", "%s is required" % name)
145
+ return []
146
+ return _array_value(name, [])
147
+
148
+ func optional_array(name: String, default_value: Array = []) -> Array:
149
+ if not _params.has(name):
150
+ return default_value
151
+ return _array_value(name, default_value)
152
+
153
+ func _array_value(name: String, default_value: Array) -> Array:
154
+ var value: Variant = _params[name]
155
+ if not value is Array:
156
+ _fail_param(name, "invalid_type", "%s must be an array" % name)
157
+ return default_value
158
+ return value
159
+
160
+ func required_dictionary(name: String) -> Dictionary:
161
+ if not _params.has(name):
162
+ _fail_param(name, "missing", "%s is required" % name)
163
+ return {}
164
+ return _dictionary_value(name, {})
165
+
166
+ func optional_dictionary(name: String, default_value: Dictionary = {}) -> Dictionary:
167
+ if not _params.has(name):
168
+ return default_value
169
+ return _dictionary_value(name, default_value)
170
+
171
+ func _dictionary_value(name: String, default_value: Dictionary) -> Dictionary:
172
+ var value: Variant = _params[name]
173
+ if not value is Dictionary:
174
+ _fail_param(name, "invalid_type", "%s must be an object" % name)
175
+ return default_value
176
+ return value
177
+
178
+ func required_node_path(name: String = "node_path") -> String:
179
+ var value: String = required_string(name)
180
+ if failed():
181
+ return ""
182
+ if value.is_empty():
183
+ _fail_param(name, "invalid_value", "%s must be a non-empty node path" % name)
184
+ return ""
185
+ return value
186
+
187
+ func required_resource_path(name: String) -> String:
188
+ var value: String = required_string(name)
189
+ if failed():
190
+ return ""
191
+ if not (value.begins_with("res://") or value.begins_with("user://")):
192
+ _fail_param(name, "invalid_value", "%s must be a res:// or user:// path" % name, {"value": value})
193
+ return ""
194
+ return value
195
+
196
+
197
+ # --- Variant narrowing at the JSON boundary ---
198
+ # The accessors above cover a command's own params. Handlers also read nested
199
+ # JSON objects (a position's `x`, a color's `r`), whose members are Variant
200
+ # because JSON is untyped. These narrowers are the only place that boundary is
201
+ # crossed, so the type-strictness suppressions stay confined to three functions
202
+ # instead of spreading through every handler.
203
+ #
204
+ # The engine's float()/int()/bool() constructors raise a script error on a type
205
+ # they cannot convert -- and bool() rejects even a String. Inside a handler that
206
+ # error abandoned the request without a response, so the narrowers fall back to
207
+ # the caller's default instead.
208
+
209
+ static func _is_integral(value: Variant) -> bool:
210
+ if not value is float:
211
+ return false
212
+ var number: float = value
213
+ return number == floorf(number)
214
+
215
+
216
+ static func to_float(value: Variant, default_value: float = 0.0) -> float:
217
+ if value is float or value is int or value is bool:
218
+ @warning_ignore("unsafe_call_argument")
219
+ return float(value)
220
+ if value is String:
221
+ var text: String = value
222
+ return text.to_float()
223
+ return default_value
224
+
225
+
226
+ static func to_int(value: Variant, default_value: int = 0) -> int:
227
+ if value is float or value is int or value is bool:
228
+ @warning_ignore("unsafe_call_argument", "narrowing_conversion")
229
+ return int(value)
230
+ if value is String:
231
+ var text: String = value
232
+ return text.to_int()
233
+ return default_value
234
+
235
+
236
+ static func to_bool(value: Variant, default_value: bool = false) -> bool:
237
+ if value is bool:
238
+ return value
239
+ if value is float or value is int:
240
+ return not is_zero_approx(to_float(value))
241
+ if value is String:
242
+ var text: String = value
243
+ return text.to_lower() == "true"
244
+ return default_value
245
+
246
+
247
+ # Members of a nested JSON object.
248
+ static func json_float(source: Dictionary, key: String, default_value: float = 0.0) -> float:
249
+ return to_float(source.get(key), default_value)
250
+
251
+
252
+ static func json_int(source: Dictionary, key: String, default_value: int = 0) -> int:
253
+ return to_int(source.get(key), default_value)
254
+
255
+
256
+ static func json_bool(source: Dictionary, key: String, default_value: bool = false) -> bool:
257
+ return to_bool(source.get(key), default_value)
258
+
259
+
260
+ static func json_string(source: Dictionary, key: String, default_value: String = "") -> String:
261
+ var value: Variant = source.get(key)
262
+ if value is String:
263
+ return value
264
+ return default_value
265
+
266
+
267
+ static func as_dictionary(value: Variant) -> Dictionary:
268
+ if value is Dictionary:
269
+ return value
270
+ return {}
271
+
272
+
273
+ static func as_array(value: Variant) -> Array:
274
+ if value is Array:
275
+ return value
276
+ return []
277
+
278
+
279
+ static func json_dictionary(source: Dictionary, key: String) -> Dictionary:
280
+ var value: Variant = source.get(key)
281
+ if value is Dictionary:
282
+ return value
283
+ return {}
284
+
285
+
286
+ static func json_array(source: Dictionary, key: String) -> Array:
287
+ var value: Variant = source.get(key)
288
+ if value is Array:
289
+ return value
290
+ return []
291
+
292
+
293
+ # Nested JSON vectors and colors, which every geometry-facing domain reads.
294
+ static func json_vector2(source: Dictionary, key: String, default_value: Vector2 = Vector2.ZERO) -> Vector2:
295
+ var value: Dictionary = json_dictionary(source, key)
296
+ if value.is_empty():
297
+ return default_value
298
+ return Vector2(json_float(value, "x", default_value.x), json_float(value, "y", default_value.y))
299
+
300
+
301
+ static func json_vector3(source: Dictionary, key: String, default_value: Vector3 = Vector3.ZERO) -> Vector3:
302
+ var value: Dictionary = json_dictionary(source, key)
303
+ if value.is_empty():
304
+ return default_value
305
+ return Vector3(
306
+ json_float(value, "x", default_value.x),
307
+ json_float(value, "y", default_value.y),
308
+ json_float(value, "z", default_value.z),
309
+ )
310
+
311
+
312
+ static func to_vector2(value: Variant, default_value: Vector2 = Vector2.ZERO) -> Vector2:
313
+ if not value is Dictionary:
314
+ return default_value
315
+ var source: Dictionary = value
316
+ return Vector2(json_float(source, "x", default_value.x), json_float(source, "y", default_value.y))
317
+
318
+
319
+ static func to_vector3(value: Variant, default_value: Vector3 = Vector3.ZERO) -> Vector3:
320
+ if not value is Dictionary:
321
+ return default_value
322
+ var source: Dictionary = value
323
+ return Vector3(
324
+ json_float(source, "x", default_value.x),
325
+ json_float(source, "y", default_value.y),
326
+ json_float(source, "z", default_value.z),
327
+ )
328
+
329
+
330
+ static func to_color(value: Variant, default_value: Color = Color.WHITE) -> Color:
331
+ if not value is Dictionary:
332
+ return default_value
333
+ var source: Dictionary = value
334
+ return Color(
335
+ json_float(source, "r", default_value.r),
336
+ json_float(source, "g", default_value.g),
337
+ json_float(source, "b", default_value.b),
338
+ json_float(source, "a", default_value.a),
339
+ )