gdharness 0.5.10 → 0.5.11

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 CHANGED
@@ -15751,7 +15751,10 @@ var init_tool_definitions = __esm(() => {
15751
15751
  type: "string",
15752
15752
  description: "tree, find: where to start, default /root. rect: the node to place. property: the node to read."
15753
15753
  },
15754
- property: { type: "string", description: "property: which one to read." },
15754
+ property: {
15755
+ type: "string",
15756
+ description: "property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label."
15757
+ },
15755
15758
  depth: { type: "number", description: "tree: levels to descend. Default 3." },
15756
15759
  includeProperties: {
15757
15760
  type: "boolean",
@@ -15778,7 +15781,7 @@ var init_tool_definitions = __esm(() => {
15778
15781
  operations: {
15779
15782
  tree: { summary: "the live scene tree", requires: [] },
15780
15783
  find: {
15781
- summary: "the paths of every node matching className, script, namePattern or group",
15784
+ summary: "the paths of every node matching className, script, namePattern or group, with property read off each",
15782
15785
  requires: []
15783
15786
  },
15784
15787
  rect: {
@@ -37251,11 +37254,13 @@ class GodotServer {
37251
37254
  if (Object.keys(filters).length === 0) {
37252
37255
  return this.createErrorResponse("runtime_inspect find needs at least one of className, script, namePattern, group.");
37253
37256
  }
37257
+ const property = readNonEmptyString(args, "property");
37254
37258
  return await this.handleRuntimeCommand("find_nodes", {
37255
37259
  ...filters,
37256
37260
  projectPath: args["projectPath"],
37257
37261
  root: readNonEmptyString(args, "nodePath") ?? "/root",
37258
- limit: readPositiveNumber(args, "limit") ?? 100
37262
+ limit: readPositiveNumber(args, "limit") ?? 100,
37263
+ ...property === undefined ? {} : { property }
37259
37264
  });
37260
37265
  }
37261
37266
  async handleRuntimeWait(op, args) {
@@ -36,12 +36,19 @@ func get_tree(params: Dictionary) -> Dictionary:
36
36
  ## Nodes matching every filter given, as paths, so a caller can name what it wants without
37
37
  ## reading the whole tree to find it. `class` matches native classes and their subclasses, and
38
38
  ## the global name of a script class; `name` is a case-insensitive glob; `script` is a path.
39
+ ##
40
+ ## `property` names one to read off each of them, which is the difference between one question and
41
+ ## one call per answer. A panel of a dozen labels took thirteen calls to read, and a tree that
42
+ ## rebuilds between them, which any HUD following a clock does, hands back paths that are gone by
43
+ ## the time they are asked about. A node without that property says so rather than answering null,
44
+ ## because null is what a node holding null answers.
39
45
  func find_nodes(params: Dictionary) -> Dictionary:
40
46
  var root_path: String = str(params.get("root", "/root"))
41
47
  var wanted_class: String = str(params.get("class", ""))
42
48
  var wanted_script: String = str(params.get("script", ""))
43
49
  var wanted_name: String = str(params.get("name", ""))
44
50
  var wanted_group: String = str(params.get("group", ""))
51
+ var wanted_property: String = str(params.get("property", ""))
45
52
  var limit: int = clampi(int(params.get("limit", FIND_LIMIT)), 1, FIND_LIMIT_CEILING)
46
53
 
47
54
  if (
@@ -67,7 +74,7 @@ func find_nodes(params: Dictionary) -> Dictionary:
67
74
  if found.size() >= limit:
68
75
  truncated = true
69
76
  break
70
- found.append(_serialize_node(node, false))
77
+ found.append(_found(node, wanted_property))
71
78
  var children: Array[Node] = node.get_children()
72
79
  for index: int in range(children.size() - 1, -1, -1):
73
80
  pending.push_front(children[index])
@@ -75,6 +82,29 @@ func find_nodes(params: Dictionary) -> Dictionary:
75
82
  return {"type": "nodes", "count": found.size(), "truncated": truncated, "nodes": found}
76
83
 
77
84
 
85
+ ## One match, with the named property on it when one was named.
86
+ ##
87
+ ## `has_property` alongside the value, because a node that has not got it and a node holding null
88
+ ## are different answers and a bare null reads as the second. The same distinction the `property`
89
+ ## op makes by refusing, which it cannot do here: a find over a panel matches nodes of several
90
+ ## classes on purpose, and refusing the whole answer because one of them has no `text` would make
91
+ ## the question unaskable.
92
+ func _found(node: Node, wanted_property: String) -> Dictionary:
93
+ var entry: Dictionary = _serialize_node(node, false)
94
+ if wanted_property.is_empty():
95
+ return entry
96
+ var has: bool = false
97
+ for prop: Dictionary in node.get_property_list():
98
+ if str(prop["name"]) == wanted_property:
99
+ has = true
100
+ break
101
+ entry["property"] = wanted_property
102
+ entry["has_property"] = has
103
+ if has:
104
+ entry["value"] = _values.serialize(node.get(wanted_property))
105
+ return entry
106
+
107
+
78
108
  func _matches(
79
109
  node: Node, wanted_class: String, wanted_script: String, wanted_name: String, wanted_group: String
80
110
  ) -> bool:
package/build/index.js CHANGED
@@ -28265,7 +28265,10 @@ var TOOL_SPECS = [
28265
28265
  type: "string",
28266
28266
  description: "tree, find: where to start, default /root. rect: the node to place. property: the node to read."
28267
28267
  },
28268
- property: { type: "string", description: "property: which one to read." },
28268
+ property: {
28269
+ type: "string",
28270
+ description: "property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label."
28271
+ },
28269
28272
  depth: { type: "number", description: "tree: levels to descend. Default 3." },
28270
28273
  includeProperties: {
28271
28274
  type: "boolean",
@@ -28292,7 +28295,7 @@ var TOOL_SPECS = [
28292
28295
  operations: {
28293
28296
  tree: { summary: "the live scene tree", requires: [] },
28294
28297
  find: {
28295
- summary: "the paths of every node matching className, script, namePattern or group",
28298
+ summary: "the paths of every node matching className, script, namePattern or group, with property read off each",
28296
28299
  requires: []
28297
28300
  },
28298
28301
  rect: {
@@ -30024,11 +30027,13 @@ class GodotServer {
30024
30027
  if (Object.keys(filters).length === 0) {
30025
30028
  return this.createErrorResponse("runtime_inspect find needs at least one of className, script, namePattern, group.");
30026
30029
  }
30030
+ const property = readNonEmptyString(args, "property");
30027
30031
  return await this.handleRuntimeCommand("find_nodes", {
30028
30032
  ...filters,
30029
30033
  projectPath: args["projectPath"],
30030
30034
  root: readNonEmptyString(args, "nodePath") ?? "/root",
30031
- limit: readPositiveNumber(args, "limit") ?? 100
30035
+ limit: readPositiveNumber(args, "limit") ?? 100,
30036
+ ...property === undefined ? {} : { property }
30032
30037
  });
30033
30038
  }
30034
30039
  async handleRuntimeWait(op, args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
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",