@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.
- package/LICENSE +23 -0
- package/README.md +925 -0
- package/addons/godot_agent_loop/LICENSE +23 -0
- package/addons/godot_agent_loop/README.md +31 -0
- package/addons/godot_agent_loop/plugin.cfg +8 -0
- package/addons/godot_agent_loop/plugin.gd +417 -0
- package/agent-plugin/.claude-plugin/plugin.json +19 -0
- package/agent-plugin/.codex-plugin/plugin.json +37 -0
- package/agent-plugin/.mcp.json +14 -0
- package/agent-plugin/adapter-manifest.json +45 -0
- package/agent-plugin/pi/extension.ts +136 -0
- package/agent-plugin/skills/build-godot-game/SKILL.md +41 -0
- package/agent-plugin/skills/debug-godot-game/SKILL.md +37 -0
- package/agent-plugin/skills/ship-godot-game/SKILL.md +46 -0
- package/agent-plugin/skills/ship-godot-game/agents/openai.yaml +4 -0
- package/agent-plugin/skills/verify-godot-change/SKILL.md +35 -0
- package/build/authoring-session-manager.js +237 -0
- package/build/domain-tool-registries.js +207 -0
- package/build/editor-bridge-protocol.js +1 -0
- package/build/editor-connection.js +112 -0
- package/build/editor-mutation-guard.js +30 -0
- package/build/editor-plugin-installer.js +220 -0
- package/build/engine-api/extension_api-4.7.stable.official.5b4e0cb0f.json +356830 -0
- package/build/game-command-service.js +49 -0
- package/build/game-connection.js +337 -0
- package/build/godot-executable.js +156 -0
- package/build/godot-process-manager.js +112 -0
- package/build/godot-subprocess.js +23 -0
- package/build/headless-operation-runner.js +68 -0
- package/build/headless-operation-service.js +65 -0
- package/build/index.js +478 -0
- package/build/interaction-server-installer.js +234 -0
- package/build/opencode-setup.js +199 -0
- package/build/project-support.js +219 -0
- package/build/runtime-protocol.js +202 -0
- package/build/scripts/godot_operations.gd +1534 -0
- package/build/scripts/mcp_editor_plugin.gd +417 -0
- package/build/scripts/mcp_interaction_server.gd +1255 -0
- package/build/scripts/mcp_runtime/audio_animation_domain.gd +568 -0
- package/build/scripts/mcp_runtime/command_params.gd +339 -0
- package/build/scripts/mcp_runtime/core_domain.gd +591 -0
- package/build/scripts/mcp_runtime/input_domain.gd +695 -0
- package/build/scripts/mcp_runtime/networking_domain.gd +356 -0
- package/build/scripts/mcp_runtime/physics_domain.gd +653 -0
- package/build/scripts/mcp_runtime/privileged_command_policy.gd +81 -0
- package/build/scripts/mcp_runtime/rendering_domain.gd +807 -0
- package/build/scripts/mcp_runtime/runtime_domain.gd +108 -0
- package/build/scripts/mcp_runtime/scene_2d_domain.gd +527 -0
- package/build/scripts/mcp_runtime/scene_3d_domain.gd +573 -0
- package/build/scripts/mcp_runtime/system_domain.gd +277 -0
- package/build/scripts/mcp_runtime/ui_domain.gd +619 -0
- package/build/scripts/mcp_runtime/variant_codec.gd +335 -0
- package/build/scripts/validate_script.gd +28 -0
- package/build/server-instructions.js +11 -0
- package/build/session-timing.js +20 -0
- package/build/tool-argument-validation.js +120 -0
- package/build/tool-definitions.js +2727 -0
- package/build/tool-handlers/game-tool-handlers.js +1345 -0
- package/build/tool-handlers/lifecycle-tool-handlers.js +346 -0
- package/build/tool-handlers/project-handler-services.js +1458 -0
- package/build/tool-handlers/project-tool-handlers.js +1506 -0
- package/build/tool-handlers/visual-regression-service.js +139 -0
- package/build/tool-manifest.js +1193 -0
- package/build/tool-mutation-policy.js +122 -0
- package/build/tool-registry.js +34 -0
- package/build/tool-surface.js +70 -0
- package/build/utils.js +273 -0
- package/package.json +110 -0
- package/product.json +52 -0
- 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
|
+
)
|