gdharness 1.2.42 → 1.3.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/build/cli.js +561 -354
- package/build/godot/addons/gdharness_editor/bridge_client.gd +1 -1
- package/build/godot/addons/gdharness_editor/property_values.gd +4 -9
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +9 -9
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +1 -1
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +29 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +2 -7
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +6 -14
- package/build/godot/operations/property_values.gd +4 -9
- package/build/keeper.js +10 -4
- package/package.json +1 -2
- package/build/index.js +0 -45101
|
@@ -518,7 +518,7 @@ func _handle_message(json_string: String) -> void:
|
|
|
518
518
|
|
|
519
519
|
|
|
520
520
|
## Says [param tool_name] was not run because the editor is scanning or importing, for the server to
|
|
521
|
-
## ask again once that is over.
|
|
521
|
+
## ask again once that is over.
|
|
522
522
|
func send_busy(request_id: String, tool_name: String) -> void:
|
|
523
523
|
_send_message(
|
|
524
524
|
{
|
|
@@ -600,18 +600,13 @@ func shown(value: Variant) -> String:
|
|
|
600
600
|
## not mine". A tag that names a type but lacks the keys to build it is left unhandled on purpose,
|
|
601
601
|
## so the caller can still read it against the type the property declares.
|
|
602
602
|
func _parse_tagged_dictionary(value: Dictionary) -> Array:
|
|
603
|
-
var type_tag: String = str(value.get("_type",
|
|
603
|
+
var type_tag: String = str(value.get("_type", ""))
|
|
604
604
|
if type_tag == "Resource":
|
|
605
605
|
return _parse_resource_tag(value)
|
|
606
606
|
|
|
607
607
|
# The shared builder is the same one the answer was written by, so every shape it writes comes
|
|
608
|
-
# back as itself.
|
|
609
|
-
|
|
610
|
-
var tagged: Dictionary = value
|
|
611
|
-
if not value.has("_type") and not type_tag.is_empty():
|
|
612
|
-
tagged = value.duplicate()
|
|
613
|
-
tagged["_type"] = type_tag
|
|
614
|
-
var rebuilt: Variant = _values.deserialize_value(tagged)
|
|
608
|
+
# back as itself.
|
|
609
|
+
var rebuilt: Variant = _values.deserialize_value(value)
|
|
615
610
|
if not rebuilt is Dictionary:
|
|
616
611
|
return [true, rebuilt]
|
|
617
612
|
|
|
@@ -660,7 +655,7 @@ func _parse_new_resource(type_tag: String, value: Dictionary) -> Array:
|
|
|
660
655
|
var wanted: Dictionary = {}
|
|
661
656
|
for key: Variant in value:
|
|
662
657
|
var property: String = str(key)
|
|
663
|
-
if property != "_type"
|
|
658
|
+
if property != "_type":
|
|
664
659
|
wanted[property] = value[key]
|
|
665
660
|
var refused: String = write_all(built, wanted)
|
|
666
661
|
if not refused.is_empty():
|
|
@@ -304,10 +304,10 @@ func read_resource(args: Dictionary) -> Dictionary:
|
|
|
304
304
|
|
|
305
305
|
|
|
306
306
|
func create_shader(args: Dictionary) -> Dictionary:
|
|
307
|
-
var shader_path: String = _ensure_res_path(str(args.get("
|
|
307
|
+
var shader_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
|
|
308
308
|
var shader_type: String = str(args.get("shaderType", "canvas_item"))
|
|
309
309
|
if shader_path == "res://":
|
|
310
|
-
return {"ok": false, "error": "
|
|
310
|
+
return {"ok": false, "error": "resourcePath is required"}
|
|
311
311
|
var taken: String = _taken(shader_path, "resource_edit op=create_shader", "Edit its text")
|
|
312
312
|
if not taken.is_empty():
|
|
313
313
|
return {"ok": false, "error": taken}
|
|
@@ -336,9 +336,9 @@ func create_shader(args: Dictionary) -> Dictionary:
|
|
|
336
336
|
|
|
337
337
|
|
|
338
338
|
func create_tileset(args: Dictionary) -> Dictionary:
|
|
339
|
-
var tileset_path: String = _ensure_res_path(str(args.get("
|
|
339
|
+
var tileset_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
|
|
340
340
|
if tileset_path == "res://":
|
|
341
|
-
return {"ok": false, "error": "
|
|
341
|
+
return {"ok": false, "error": "resourcePath is required"}
|
|
342
342
|
var taken: String = _taken(tileset_path, "resource_edit op=create_tileset")
|
|
343
343
|
if not taken.is_empty():
|
|
344
344
|
return {"ok": false, "error": taken}
|
|
@@ -478,7 +478,7 @@ func _source_ids(tile_set: TileSet) -> Array[int]:
|
|
|
478
478
|
|
|
479
479
|
func set_tilemap_cells(args: Dictionary) -> Dictionary:
|
|
480
480
|
var scene_path: String = _ensure_res_path(str(args.get("scenePath", "")))
|
|
481
|
-
var node_path: String = str(args.get("
|
|
481
|
+
var node_path: String = str(args.get("nodePath", ""))
|
|
482
482
|
if scene_path == "res://":
|
|
483
483
|
return {"ok": false, "error": "scenePath is required"}
|
|
484
484
|
|
|
@@ -702,9 +702,9 @@ func _save_theme(theme: Theme, theme_path: String, item: String) -> String:
|
|
|
702
702
|
|
|
703
703
|
|
|
704
704
|
func set_theme_color(args: Dictionary) -> Dictionary:
|
|
705
|
-
var theme_path: String = _ensure_res_path(str(args.get("
|
|
705
|
+
var theme_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
|
|
706
706
|
if theme_path == "res://":
|
|
707
|
-
return {"ok": false, "error": "
|
|
707
|
+
return {"ok": false, "error": "resourcePath is required"}
|
|
708
708
|
|
|
709
709
|
var color_name: String = str(args.get("colorName", ""))
|
|
710
710
|
var control_type: String = str(args.get("controlType", ""))
|
|
@@ -745,9 +745,9 @@ func set_theme_color(args: Dictionary) -> Dictionary:
|
|
|
745
745
|
|
|
746
746
|
|
|
747
747
|
func set_theme_font_size(args: Dictionary) -> Dictionary:
|
|
748
|
-
var theme_path: String = _ensure_res_path(str(args.get("
|
|
748
|
+
var theme_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
|
|
749
749
|
if theme_path == "res://":
|
|
750
|
-
return {"ok": false, "error": "
|
|
750
|
+
return {"ok": false, "error": "resourcePath is required"}
|
|
751
751
|
|
|
752
752
|
var font_size_name: String = str(args.get("fontSizeName", ""))
|
|
753
753
|
var control_type: String = str(args.get("controlType", ""))
|
|
@@ -410,7 +410,7 @@ func duplicate_node(args: Dictionary) -> Dictionary:
|
|
|
410
410
|
var scene_path: String = _to_scene_res_path(project_path, str(args.get("scenePath", "")))
|
|
411
411
|
var node_path: String = str(args.get("nodePath", ""))
|
|
412
412
|
var new_name: String = str(args.get("newName", ""))
|
|
413
|
-
var parent_path: String = str(args.get("
|
|
413
|
+
var parent_path: String = str(args.get("parentNodePath", ""))
|
|
414
414
|
|
|
415
415
|
if node_path.is_empty() or new_name.is_empty():
|
|
416
416
|
return {"ok": false, "error": "Missing nodePath or newName"}
|
|
@@ -23,7 +23,7 @@ const Typing = preload("runtime_typing.gd")
|
|
|
23
23
|
const Values = preload("runtime_values.gd")
|
|
24
24
|
const Waits = preload("runtime_waits.gd")
|
|
25
25
|
|
|
26
|
-
const PROTOCOL: int =
|
|
26
|
+
const PROTOCOL: int = 3
|
|
27
27
|
const DEFAULT_BIND_ADDRESS: String = "127.0.0.1"
|
|
28
28
|
const BIND_ADDRESS_SETTING: String = "gdharness/runtime/bind_address"
|
|
29
29
|
## 0 asks the operating system for a free port, which is the default and what the server
|
|
@@ -502,7 +502,7 @@ func _announce(bind_address: String) -> void:
|
|
|
502
502
|
# Written whole under a name no server reads and moved into place, so no look ever meets
|
|
503
503
|
# the file empty: a server sweeping the directory between an open and a write took the
|
|
504
504
|
# announcement for a dead game's leftover, and the directory is shared by every server on
|
|
505
|
-
# the machine
|
|
505
|
+
# the machine.
|
|
506
506
|
var staged: String = path + ".tmp"
|
|
507
507
|
var file: FileAccess = FileAccess.open(staged, FileAccess.WRITE)
|
|
508
508
|
if file == null:
|
|
@@ -36,6 +36,35 @@ func capture_viewport(params: Dictionary) -> Dictionary:
|
|
|
36
36
|
if not node is Viewport:
|
|
37
37
|
return {"type": "error", "message": "Node is not a Viewport: " + viewport_path}
|
|
38
38
|
var viewport: Viewport = node
|
|
39
|
+
# The root's own state is refused in _capture. Another window of the game's that is hidden or
|
|
40
|
+
# minimised holds no frame, and asking it for its image makes the engine log the empty read into
|
|
41
|
+
# the game's output, so the run would stop unclean for an error the capture raised.
|
|
42
|
+
if viewport is Window and viewport != _host.get_tree().root:
|
|
43
|
+
var window: Window = viewport
|
|
44
|
+
if not window.visible:
|
|
45
|
+
return {
|
|
46
|
+
"type": "error",
|
|
47
|
+
"message":
|
|
48
|
+
(
|
|
49
|
+
(
|
|
50
|
+
"The Window at %s is hidden, so nothing is drawn to it. Show it (its visible "
|
|
51
|
+
+ "property) and ask again."
|
|
52
|
+
)
|
|
53
|
+
% viewport_path
|
|
54
|
+
),
|
|
55
|
+
}
|
|
56
|
+
if not window.can_draw():
|
|
57
|
+
return {
|
|
58
|
+
"type": "error",
|
|
59
|
+
"message":
|
|
60
|
+
(
|
|
61
|
+
(
|
|
62
|
+
"Nothing is being drawn to the Window at %s, which is minimised. Restore it and "
|
|
63
|
+
+ "ask again."
|
|
64
|
+
)
|
|
65
|
+
% viewport_path
|
|
66
|
+
),
|
|
67
|
+
}
|
|
39
68
|
if not viewport is SubViewport:
|
|
40
69
|
return await _capture(viewport, params)
|
|
41
70
|
# Drawn now rather than taken as it stands. A SubViewport the game does not redraw every frame
|
|
@@ -284,7 +284,7 @@ func _glyph_of(keycode: Key, shifted: bool) -> int:
|
|
|
284
284
|
## `pressed` is how a caller asks for one half, the way a drag is made: true holds the button down,
|
|
285
285
|
## false lets go of one being held.
|
|
286
286
|
func inject_mouse_click(params: Dictionary) -> Dictionary:
|
|
287
|
-
var point: Variant = Mouse.read_point(params, "x", "y"
|
|
287
|
+
var point: Variant = Mouse.read_point(params, "x", "y")
|
|
288
288
|
if point is String:
|
|
289
289
|
return {"type": "error", "message": point}
|
|
290
290
|
var position: Vector2 = point
|
|
@@ -349,7 +349,7 @@ func inject_mouse_click(params: Dictionary) -> Dictionary:
|
|
|
349
349
|
## would have carried. A relative the caller gives is kept, since a game may want to read a motion
|
|
350
350
|
## the position does not show.
|
|
351
351
|
func inject_mouse_motion(params: Dictionary) -> Dictionary:
|
|
352
|
-
var point: Variant = Mouse.read_point(params, "x", "y"
|
|
352
|
+
var point: Variant = Mouse.read_point(params, "x", "y")
|
|
353
353
|
if point is String:
|
|
354
354
|
return {"type": "error", "message": point}
|
|
355
355
|
var position: Vector2 = point
|
|
@@ -358,11 +358,6 @@ func inject_mouse_motion(params: Dictionary) -> Dictionary:
|
|
|
358
358
|
relative = Vector2(
|
|
359
359
|
Read.as_float(params.get("relativeX", 0.0)), Read.as_float(params.get("relativeY", 0.0))
|
|
360
360
|
)
|
|
361
|
-
elif params.has("relative"):
|
|
362
|
-
var movement: Variant = Mouse.read_point(params, "relativeX", "relativeY", "relative")
|
|
363
|
-
if movement is String:
|
|
364
|
-
return {"type": "error", "message": movement}
|
|
365
|
-
relative = movement
|
|
366
361
|
|
|
367
362
|
var supplied: bool = _follow_in_the_window(position)
|
|
368
363
|
Input.parse_input_event(_motion(position, relative))
|
|
@@ -6,20 +6,12 @@ extends RefCounted
|
|
|
6
6
|
const Read = preload("reading.gd")
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
## A point the tool schema sends as two flat numbers, or the
|
|
10
|
-
##
|
|
11
|
-
static func read_point(params: Dictionary, x_key: String, y_key: String
|
|
12
|
-
if params.has(x_key)
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
if raw is Vector2:
|
|
16
|
-
return raw
|
|
17
|
-
if raw is Array:
|
|
18
|
-
var pair: Array = raw
|
|
19
|
-
if pair.size() < 2:
|
|
20
|
-
return "%s array must contain [x, y]" % pair_key
|
|
21
|
-
return Vector2(Read.as_float(pair[0]), Read.as_float(pair[1]))
|
|
22
|
-
return "%s must be Vector2 or [x, y]" % pair_key
|
|
9
|
+
## A point the tool schema sends as two flat numbers. Answers a Vector2, or the String that says
|
|
10
|
+
## which was missing.
|
|
11
|
+
static func read_point(params: Dictionary, x_key: String, y_key: String) -> Variant:
|
|
12
|
+
if not params.has(x_key) or not params.has(y_key):
|
|
13
|
+
return "%s and %s are both needed." % [x_key, y_key]
|
|
14
|
+
return Vector2(Read.as_float(params[x_key]), Read.as_float(params[y_key]))
|
|
23
15
|
|
|
24
16
|
|
|
25
17
|
## The points of a drag: x, y, then each of via, then toX, toY. Answers the String saying what is
|
|
@@ -600,18 +600,13 @@ func shown(value: Variant) -> String:
|
|
|
600
600
|
## not mine". A tag that names a type but lacks the keys to build it is left unhandled on purpose,
|
|
601
601
|
## so the caller can still read it against the type the property declares.
|
|
602
602
|
func _parse_tagged_dictionary(value: Dictionary) -> Array:
|
|
603
|
-
var type_tag: String = str(value.get("_type",
|
|
603
|
+
var type_tag: String = str(value.get("_type", ""))
|
|
604
604
|
if type_tag == "Resource":
|
|
605
605
|
return _parse_resource_tag(value)
|
|
606
606
|
|
|
607
607
|
# The shared builder is the same one the answer was written by, so every shape it writes comes
|
|
608
|
-
# back as itself.
|
|
609
|
-
|
|
610
|
-
var tagged: Dictionary = value
|
|
611
|
-
if not value.has("_type") and not type_tag.is_empty():
|
|
612
|
-
tagged = value.duplicate()
|
|
613
|
-
tagged["_type"] = type_tag
|
|
614
|
-
var rebuilt: Variant = _values.deserialize_value(tagged)
|
|
608
|
+
# back as itself.
|
|
609
|
+
var rebuilt: Variant = _values.deserialize_value(value)
|
|
615
610
|
if not rebuilt is Dictionary:
|
|
616
611
|
return [true, rebuilt]
|
|
617
612
|
|
|
@@ -660,7 +655,7 @@ func _parse_new_resource(type_tag: String, value: Dictionary) -> Array:
|
|
|
660
655
|
var wanted: Dictionary = {}
|
|
661
656
|
for key: Variant in value:
|
|
662
657
|
var property: String = str(key)
|
|
663
|
-
if property != "_type"
|
|
658
|
+
if property != "_type":
|
|
664
659
|
wanted[property] = value[key]
|
|
665
660
|
var refused: String = write_all(built, wanted)
|
|
666
661
|
if not refused.is_empty():
|
package/build/keeper.js
CHANGED
|
@@ -1428,17 +1428,23 @@ function recordAt(path) {
|
|
|
1428
1428
|
return null;
|
|
1429
1429
|
}
|
|
1430
1430
|
const args = fields["arguments"];
|
|
1431
|
+
const startedBy = fields["startedBy"];
|
|
1432
|
+
const projectPath = fields["projectPath"];
|
|
1433
|
+
const command = fields["command"];
|
|
1434
|
+
if (typeof startedBy !== "number" || typeof projectPath !== "string" || projectPath === "" || typeof command !== "string" || !Array.isArray(args)) {
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1431
1437
|
return {
|
|
1432
1438
|
pid,
|
|
1433
1439
|
transcript,
|
|
1434
1440
|
startedAt,
|
|
1435
|
-
|
|
1436
|
-
projectPath
|
|
1441
|
+
startedBy,
|
|
1442
|
+
projectPath,
|
|
1437
1443
|
...typeof fields["servedBy"] === "string" && fields["servedBy"] !== "" ? { servedBy: fields["servedBy"] } : {},
|
|
1438
1444
|
...typeof fields["keeper"] === "string" && fields["keeper"] !== "" ? { keeper: fields["keeper"] } : {},
|
|
1439
1445
|
...typeof fields["profile"] === "string" && fields["profile"] !== "" ? { profile: fields["profile"] } : {},
|
|
1440
|
-
arguments:
|
|
1441
|
-
|
|
1446
|
+
arguments: args.filter((value) => typeof value === "string"),
|
|
1447
|
+
command,
|
|
1442
1448
|
...typeof fields["exitCode"] === "number" ? { exitCode: fields["exitCode"] } : {},
|
|
1443
1449
|
...typeof fields["exitSignal"] === "string" ? { exitSignal: fields["exitSignal"] } : {},
|
|
1444
1450
|
...typeof fields["endedAt"] === "number" ? { endedAt: fields["endedAt"] } : {}
|
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"main": "./build/index.js",
|
|
8
7
|
"bin": {
|
|
9
8
|
"gdharness": "build/cli.js"
|
|
10
9
|
},
|