godot-cli-control 0.4.1__tar.gz → 0.4.2__tar.gz
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.
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/PKG-INFO +1 -1
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/CHANGELOG.md +6 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/README.md +2 -2
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/low_level_api.gd +38 -3
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/plugin.cfg +1 -1
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/gut/test_low_level_api.gd +80 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/_version.py +2 -2
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/templates/skill/SKILL.md +2 -2
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/.gitignore +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/LICENSE +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/LICENSE +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bin/run_cli_control.ps1 +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bin/run_cli_control.sh +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/capture_logger.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/diagnostics_api.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/error_codes.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/game_bridge.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/game_bridge.gd.uid +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/input_simulation_api.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/input_simulation_api.gd.uid +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/low_level_api.gd.uid +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/render_api.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/scene_api.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/time_api.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/variant_codec.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/wait_api.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/plugin.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/plugin.gd.uid +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/gut/test_diagnostics_api.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/gut/test_game_bridge.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/gut/test_input_simulation_api.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/gut/test_render_api.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/gut/test_scene_api.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/gut/test_time_api.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/gut/test_variant_codec.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/gut/test_wait_api.gd +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/run_gut.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/run_gut.sh +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/test_init_walkthrough.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/test_walkthrough.sh +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/pyproject.toml +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/README.md +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/__init__.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/__main__.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/_duration.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/bridge.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/cli.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/client.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/daemon.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/init_cmd.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/pytest_plugin.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/registry.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/runner.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/skills_install.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/templates/__init__.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/templates/skill/__init__.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/__init__.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/conftest.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_bridge.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_broadcast.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_cli.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_cli_helpers.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_client.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_daemon.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_duration.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_e2e_client_direct.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_e2e_diag.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_e2e_example.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_e2e_input.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_e2e_multi_instance.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_e2e_record.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_e2e_render.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_e2e_scene.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_e2e_screenshot_gui.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_e2e_time.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_init.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_issue_fixes.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_pytest_plugin.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_registry.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_runner.py +0 -0
- {godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/tests/test_skills_install.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: godot-cli-control
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: WebSocket bridge for headless / scripted control of Godot scenes.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ClaymanTwinkle/godot-cli-control
|
|
6
6
|
Project-URL: Repository, https://github.com/ClaymanTwinkle/godot-cli-control
|
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.2] - 2026-06-14
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **`call` 对错类型「标量」实参不再假成功(#167 的标量侧补集)**:#167 只关了 `call` 的 **JSON Array → 复合 Variant** 侧假成功;错类型的标量实参(`set_name 42` 数字→StringName、`set_z_index '"abc"'` String→int 等)此前仍直接喂 `callv`,引擎喷 `Cannot convert argument` 返回 `null`,RPC 却假 `ok:true`/`result:null`——调用方以为方法生效实则没跑(与 #164/#167 同级数据风险,且 README/SKILL 已宣称「typed 方法假成功不再可能」,文档与实现脱节)。现连 daemon 前按方法声明签名对非 Array 实参也做类型守卫:镜像 Godot `Variant::can_convert_strict`,命中安全互转组(数值组 `bool`/`int`/`float`、字符串组 `String`/`StringName`/`NodePath`)透传,跨组(必 `Cannot convert`)`-32602` fail-loud。数值宽化(`float`→`int`)、`bool`→`int`、`String`→`String` 等合法调用零回归;`null` 实参保守透传(不在本 issue 新增拦截)。纯 addon 服务端行为改动,老项目跑一次 `init` 同步。
|
|
13
|
+
- **`plugin.cfg` 版本号不再僵在 `0.1.0`**:此前 addon 的 `plugin.cfg` 一直写死 `version="0.1.0"`,CI 仅在打包 AssetLib zip 时按 tag `sed` 修正,而 pip wheel 在那条 sed 之前就已构建——于是 `pip install godot-cli-control` + `init` 复制进下游项目的 addon、以及直接 clone addon 的用法,都拿到错误的 `0.1.0`(Godot 编辑器插件列表 / AssetLib 元数据里显示)。现把 committed 值对齐到上次发布版本,并让 `release.sh` 在打 tag 前自动同步 `plugin.cfg` 版本号并入库,三条取用路径(git-direct / pip+init / AssetLib)从此一致。纯元数据修正,不影响 CLI 行为(CLI 版本一直来自 hatch-vcs 的 `_version.py`)。
|
|
14
|
+
|
|
9
15
|
## [0.4.1] - 2026-06-14
|
|
10
16
|
|
|
11
17
|
### Fixed
|
|
@@ -89,7 +89,7 @@ All methods callable via `godot-cli-control <method>` or `from godot_cli_control
|
|
|
89
89
|
| `get_property(path, property)` | `await client.get_property("/root/Player", "position")` — returns bare value; CLI `get` returns `{"value": ..., "type"?: ...}` shape |
|
|
90
90
|
| `get_properties(path, properties)` | `await client.get_properties("/root/Player", ["position", "health"])` — multi-property atomic read; returns `{prop: bare_value, ...}` dict |
|
|
91
91
|
| `set_property(path, property, value)` | `await client.set_property("/root/Player", "visible", False)` |
|
|
92
|
-
| `call_method(path, method, args)` | `await client.call_method("/root/Player", "take_damage", [10])`. A JSON array passed for a parameter the method declares as a compound Variant (`Rect2`/`Vector2`/`Color`/…) is coerced from the method signature, same as `set_property` (`call_method("/root/Mob", "enable_wander", [[0,0,640,480]])` → `enable_wander(rect: Rect2)`); arg-count / type mismatches fail loud with `-32602` instead of a silent `ok:true`/`result:null` (#167). |
|
|
92
|
+
| `call_method(path, method, args)` | `await client.call_method("/root/Player", "take_damage", [10])`. A JSON array passed for a parameter the method declares as a compound Variant (`Rect2`/`Vector2`/`Color`/…) is coerced from the method signature, same as `set_property` (`call_method("/root/Mob", "enable_wander", [[0,0,640,480]])` → `enable_wander(rect: Rect2)`); arg-count / type mismatches fail loud with `-32602` instead of a silent `ok:true`/`result:null` (#167); a scalar of the wrong type (a string for an `int` param, a number for a `String`/`StringName`) likewise fails loud rather than passing through (#183). |
|
|
93
93
|
| `emit_signal(path, signal, args)` | `await client.emit_signal("/root/Game", "level_complete", [])` — fire a node signal (test seam). **Disabled by default** (server returns `1015`); daemon must be started with `--allow-emit-signal` (debug-build + localhost + explicit opt-in). `call <node> emit_signal` is always blocked by the method blacklist — use this method. CLI: `emit-signal <path> <signal> [args...]` |
|
|
94
94
|
| `get_text(path)` | `await client.get_text("/root/UI/Label")` |
|
|
95
95
|
| `node_exists(path)` | `await client.node_exists("/root/MyScene/Button")` |
|
|
@@ -157,7 +157,7 @@ Three numeric ranges share `error.code`; they never overlap, so a single field i
|
|
|
157
157
|
| `1016` | server | RESPONSE_TOO_LARGE: a single response exceeded the outbound WebSocket buffer (default 10 MB, `godot_cli_control/outbound_buffer_mb`) and was replaced with this error instead of hanging the call to a `-1002` timeout. Usually a bytes-API `screenshot` of a hiDPI frame — pass a path (daemon writes to disk) or raise the buffer. |
|
|
158
158
|
| `-32600` | server | Malformed JSON-RPC request |
|
|
159
159
|
| `-32601` | server | Unknown method name |
|
|
160
|
-
| `-32602` | server | Invalid params (incl. blocked methods/properties from the security blacklist, `set` value-type mismatch — e.g. `Vector2` property given an array of wrong length / non-numeric elements; `call` typed-arg coercion failure / argument-count mismatch — wrong array length, non-numeric element, a JSON array given to a scalar/Object parameter, or wrong arg count (#167); or `hold` given `duration ≤ 0` — use `press` for an indefinite hold; also out-of-range values like a scene `timeout` outside `(0, 3600]` sent directly via `GameClient`, and `find_nodes` called with no filter at all or with both `text` and `text_contains`) |
|
|
160
|
+
| `-32602` | server | Invalid params (incl. blocked methods/properties from the security blacklist, `set` value-type mismatch — e.g. `Vector2` property given an array of wrong length / non-numeric elements; `call` typed-arg coercion failure / argument-count mismatch — wrong array length, non-numeric element, a JSON array given to a scalar/Object parameter, a scalar of the wrong type (a string for an `int` param, a number for a `String`/`StringName`), or wrong arg count (#167/#183); or `hold` given `duration ≤ 0` — use `press` for an indefinite hold; also out-of-range values like a scene `timeout` outside `(0, 3600]` sent directly via `GameClient`, and `find_nodes` called with no filter at all or with both `text` and `text_contains`) |
|
|
161
161
|
| `-1001` | client | Connection failure (daemon not running, port wrong, proxy hijacking localhost) |
|
|
162
162
|
| `-1002` | client | Timeout waiting for response |
|
|
163
163
|
| `-1003` | client | CLI usage error (combo missing steps, malformed `--steps-json`, a non-numeric `tap`/`wait-time` arg, a `scene-change` path not starting with `res://`/`uid://`, a `scene-reload`/`scene-change` `--timeout` outside `(0, 3600]`, a `set`/`call` value that fails JSON parsing, script path not found, script missing `run(bridge)`, **multi-instance ambiguity** (≥2 instances running and no `--instance`/`--name` given), **named instance not running** (explicit `--instance nope` but that instance isn't up), **instance port not readable yet** (alive but mid-startup — transient, retry), or `--instance` / `--name` conflict). Always exits **64** (#82 / #111). |
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/low_level_api.gd
RENAMED
|
@@ -116,6 +116,13 @@ const _CALL_ARG_ARRAY_OK_TYPES: Array[int] = [
|
|
|
116
116
|
TYPE_PACKED_VECTOR4_ARRAY,
|
|
117
117
|
]
|
|
118
118
|
|
|
119
|
+
# #183:标量 call 实参「callv 能隐式转换、不静默失败」的互转组,镜像 Godot
|
|
120
|
+
# Variant::can_convert_strict 的数值组 / 字符串组(见 _call_arg_convertible)。组内
|
|
121
|
+
# 任意互转 callv 接受(数值宽化 / 字符串类互通);跨组(如 String→int、数字→StringName)
|
|
122
|
+
# callv 必 "Cannot convert" 喷错返 null —— 旧版假 ok:true 的标量侧根因,必须拦。
|
|
123
|
+
const _NUMERIC_ARG_TYPES: Array[int] = [TYPE_BOOL, TYPE_INT, TYPE_FLOAT]
|
|
124
|
+
const _STRINGY_ARG_TYPES: Array[int] = [TYPE_STRING, TYPE_STRING_NAME, TYPE_NODE_PATH]
|
|
125
|
+
|
|
119
126
|
|
|
120
127
|
func _ready() -> void:
|
|
121
128
|
_property_blacklist = _merge_extra(_property_blacklist, SETTING_PROPERTY_BLACKLIST_EXTRA)
|
|
@@ -526,14 +533,21 @@ func _find_method_info(node: Node, method: String) -> Dictionary:
|
|
|
526
533
|
return {}
|
|
527
534
|
|
|
528
535
|
|
|
529
|
-
## 单个 call
|
|
530
|
-
## 非 Array
|
|
536
|
+
## 单个 call 实参的类型守卫 + Array→复合 Variant coerce(#167 / #183)。
|
|
537
|
+
## 非 Array 实参(标量 / Dictionary / Object):按 _call_arg_convertible 判定——可隐式转换
|
|
538
|
+
## 则返 {} 透传,否则 fail-loud(#183,String→int / 数字→StringName 等标量错类型的补集)。
|
|
531
539
|
## Array 实参命中复合声明类型:返复合 coerce 结果({"value":...} 或长度·元素错的 {"error":...})。
|
|
532
540
|
## Array 实参 + 声明类型接受 Array(untyped / Array / Packed*,见 _CALL_ARG_ARRAY_OK_TYPES):返 {} 透传。
|
|
533
541
|
## Array 实参 + 标量 / Object / Dictionary 等:callv 必 "Cannot convert" 静默失败 → fail-loud。
|
|
534
542
|
func _coerce_call_arg(declared_type: int, arg: Variant, label: String) -> Dictionary:
|
|
535
543
|
if not (arg is Array):
|
|
536
|
-
|
|
544
|
+
# #183:callv 对类型不匹配的标量实参只喷 "Cannot convert argument" 并返回 null,
|
|
545
|
+
# RPC 仍假 ok:true / result:null(#167 只关了 Array 侧)。可隐式转换则透传,否则拦。
|
|
546
|
+
if _call_arg_convertible(typeof(arg), declared_type):
|
|
547
|
+
return {}
|
|
548
|
+
return _err(CliControlErrorCodes.INVALID_PARAMS,
|
|
549
|
+
"value type mismatch for '%s': cannot convert %s to declared parameter type %s"
|
|
550
|
+
% [label, type_string(typeof(arg)), type_string(declared_type)])
|
|
537
551
|
var coerced: Dictionary = _coerce_compound_array(declared_type, arg, label)
|
|
538
552
|
if not coerced.is_empty():
|
|
539
553
|
return coerced
|
|
@@ -544,6 +558,27 @@ func _coerce_call_arg(declared_type: int, arg: Variant, label: String) -> Dictio
|
|
|
544
558
|
% [label, declared_type])
|
|
545
559
|
|
|
546
560
|
|
|
561
|
+
## 标量实参 → 声明形参类型的可转换性判定(#183),镜像 Godot Variant::can_convert_strict
|
|
562
|
+
## 中与 JSON 标量实参相关的子集:true = callv 能隐式转换(透传安全),false = callv 必
|
|
563
|
+
## "Cannot convert" 静默失败(须 fail-loud)。白名单刻意 ⊆ can_convert_strict(在 JSON 实参
|
|
564
|
+
## 场景下),宁可漏拦不误杀合法调用。
|
|
565
|
+
## 范围边界:JSON 实参只产 Nil / bool / 数字 / String / Array / Dictionary。null(TYPE_NIL)
|
|
566
|
+
## 实参的可达性随形参 nullable 而异(Object 形参 / 有默认值时 callv 接受),精确判定需更多
|
|
567
|
+
## 签名信息,故保守透传、不在本 issue 新增对 null 的拦截(与改动前一致,零回归)。
|
|
568
|
+
func _call_arg_convertible(from_type: int, declared_type: int) -> bool:
|
|
569
|
+
if declared_type == TYPE_NIL:
|
|
570
|
+
return true # untyped(Variant) 形参接受任何实参
|
|
571
|
+
if from_type == declared_type:
|
|
572
|
+
return true
|
|
573
|
+
if from_type == TYPE_NIL:
|
|
574
|
+
return true # 见上:null 实参保守透传,不新增拦截
|
|
575
|
+
if from_type in _NUMERIC_ARG_TYPES and declared_type in _NUMERIC_ARG_TYPES:
|
|
576
|
+
return true # 数值组 bool/int/float 互转(callv 宽化)
|
|
577
|
+
if from_type in _STRINGY_ARG_TYPES and declared_type in _STRINGY_ARG_TYPES:
|
|
578
|
+
return true # 字符串组 String/StringName/NodePath 互转
|
|
579
|
+
return false
|
|
580
|
+
|
|
581
|
+
|
|
547
582
|
## emit-signal 逃生门(#157 item4):默认禁(1015),daemon 带 --allow-emit-signal 才放行。
|
|
548
583
|
## 门控最先短路(功能没开不解析节点/信号、不泄露存在性)。emit_signal 仍在方法黑名单里,
|
|
549
584
|
## 本 handler 是唯一的、被门控的发信号入口;通用 call 面 emit_signal 始终被拒。
|
|
@@ -78,6 +78,10 @@ class _CallFixture extends Node:
|
|
|
78
78
|
# 标量形参:Array 实参无法转换 → fail-loud。
|
|
79
79
|
func takes_int(n: int) -> int:
|
|
80
80
|
return n * 2
|
|
81
|
+
# String 形参(#183 标量侧):数字 / bool 实参 callv "Cannot convert" → fail-loud;
|
|
82
|
+
# String 实参透传。
|
|
83
|
+
func takes_string(s: String) -> String:
|
|
84
|
+
return s + "!"
|
|
81
85
|
# 带默认值的可选尾参:required=1,给 1 个实参应放行(b 取默认 5)。
|
|
82
86
|
func takes_two(a: int, b: int = 5) -> int:
|
|
83
87
|
return a + b
|
|
@@ -834,6 +838,82 @@ func test_call_method_untyped_param_passes_array_through() -> void:
|
|
|
834
838
|
assert_eq(result.get("result"), [1, 2, 3])
|
|
835
839
|
|
|
836
840
|
|
|
841
|
+
# ── handle_call_method 标量实参 fail-loud / 宽化(issue #183) ──────
|
|
842
|
+
|
|
843
|
+
func test_call_method_string_to_int_param_fails_loud() -> void:
|
|
844
|
+
# 复现 set_z_index '"abc"':String 实参喂 int 形参 → callv "Cannot convert" 静默失败,
|
|
845
|
+
# pre-empt 成 -32602,方法不被调用(result 缺席,不假 ok:true)。
|
|
846
|
+
var fixture: _CallFixture = _CallFixture.new()
|
|
847
|
+
fixture.name = "StrToIntTarget"
|
|
848
|
+
add_child_autofree(fixture)
|
|
849
|
+
var result: Dictionary = _api.handle_call_method({
|
|
850
|
+
"path": str(fixture.get_path()),
|
|
851
|
+
"method": "takes_int",
|
|
852
|
+
"args": ["abc"],
|
|
853
|
+
})
|
|
854
|
+
assert_has(result, "error")
|
|
855
|
+
assert_eq(int(result.error.code), -32602)
|
|
856
|
+
assert_string_contains(str(result.error.message), "value type mismatch")
|
|
857
|
+
assert_does_not_have(result, "result")
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
func test_call_method_number_to_string_param_fails_loud() -> void:
|
|
861
|
+
# 复现 set_name 42:数字实参喂 String 形参 → fail-loud,不假 ok:true。
|
|
862
|
+
var fixture: _CallFixture = _CallFixture.new()
|
|
863
|
+
fixture.name = "NumToStrTarget"
|
|
864
|
+
add_child_autofree(fixture)
|
|
865
|
+
var result: Dictionary = _api.handle_call_method({
|
|
866
|
+
"path": str(fixture.get_path()),
|
|
867
|
+
"method": "takes_string",
|
|
868
|
+
"args": [42],
|
|
869
|
+
})
|
|
870
|
+
assert_has(result, "error")
|
|
871
|
+
assert_eq(int(result.error.code), -32602)
|
|
872
|
+
assert_does_not_have(result, "result")
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
func test_call_method_float_to_int_param_widens_ok() -> void:
|
|
876
|
+
# 防误杀:数值组互转(float→int)是 callv 合法宽化,须透传并真正调用(3.0→3 → 返 6)。
|
|
877
|
+
var fixture: _CallFixture = _CallFixture.new()
|
|
878
|
+
fixture.name = "FloatToIntTarget"
|
|
879
|
+
add_child_autofree(fixture)
|
|
880
|
+
var result: Dictionary = _api.handle_call_method({
|
|
881
|
+
"path": str(fixture.get_path()),
|
|
882
|
+
"method": "takes_int",
|
|
883
|
+
"args": [3.0],
|
|
884
|
+
})
|
|
885
|
+
assert_does_not_have(result, "error")
|
|
886
|
+
assert_eq(int(result.get("result")), 6)
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
func test_call_method_bool_to_int_param_ok() -> void:
|
|
890
|
+
# 防误杀:bool→int 同属数值组,透传(true→1 → 返 2)。
|
|
891
|
+
var fixture: _CallFixture = _CallFixture.new()
|
|
892
|
+
fixture.name = "BoolToIntTarget"
|
|
893
|
+
add_child_autofree(fixture)
|
|
894
|
+
var result: Dictionary = _api.handle_call_method({
|
|
895
|
+
"path": str(fixture.get_path()),
|
|
896
|
+
"method": "takes_int",
|
|
897
|
+
"args": [true],
|
|
898
|
+
})
|
|
899
|
+
assert_does_not_have(result, "error")
|
|
900
|
+
assert_eq(int(result.get("result")), 2)
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
func test_call_method_string_to_string_param_ok() -> void:
|
|
904
|
+
# 防误杀:String→String 精确匹配,透传并调用("hi"→"hi!")。
|
|
905
|
+
var fixture: _CallFixture = _CallFixture.new()
|
|
906
|
+
fixture.name = "StrToStrTarget"
|
|
907
|
+
add_child_autofree(fixture)
|
|
908
|
+
var result: Dictionary = _api.handle_call_method({
|
|
909
|
+
"path": str(fixture.get_path()),
|
|
910
|
+
"method": "takes_string",
|
|
911
|
+
"args": ["hi"],
|
|
912
|
+
})
|
|
913
|
+
assert_does_not_have(result, "error")
|
|
914
|
+
assert_eq(str(result.get("result")), "hi!")
|
|
915
|
+
|
|
916
|
+
|
|
837
917
|
func test_prepare_call_args_vararg_not_count_rejected() -> void:
|
|
838
918
|
# vararg 方法(Node.rpc 是变参)尾部可接任意多实参,数量校验不能误拦。
|
|
839
919
|
# 直接打 _prepare_call_args(不实际 callv,避免 rpc 副作用),断言无 error、实参保留。
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.4.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 4,
|
|
21
|
+
__version__ = version = '0.4.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 4, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -240,7 +240,7 @@ Three numeric ranges cohabit in `error.code`. Knowing which is which lets you de
|
|
|
240
240
|
|---|---|
|
|
241
241
|
| `-32600` | Malformed request (missing / non-string `method`). Bug in client; should never reach an agent. |
|
|
242
242
|
| `-32601` | Unknown method name. Bug; means client + plugin versions drifted. |
|
|
243
|
-
| `-32602` | Invalid params: missing required field, blocked property/method (security blacklist), out-of-range value, value-type mismatch on `set` (e.g. `Vector2` property given an array of wrong length / non-numeric elements), value-type or argument-count mismatch on `call` (typed-parameter coercion failure, a JSON array given to a scalar/Object parameter, or the wrong number of args), node-isn't-clickable (e.g. you `click`'d a `Node2D`), or `hold` given `duration ≤ 0` (use `press` for an indefinite hold). Don't retry; the request shape is wrong. |
|
|
243
|
+
| `-32602` | Invalid params: missing required field, blocked property/method (security blacklist), out-of-range value, value-type mismatch on `set` (e.g. `Vector2` property given an array of wrong length / non-numeric elements), value-type or argument-count mismatch on `call` (typed-parameter coercion failure, a JSON array given to a scalar/Object parameter, a scalar of the wrong type for the declared parameter — e.g. a string for an `int` param or a number for a `String`/`StringName` — or the wrong number of args), node-isn't-clickable (e.g. you `click`'d a `Node2D`), or `hold` given `duration ≤ 0` (use `press` for an indefinite hold). Don't retry; the request shape is wrong. |
|
|
244
244
|
|
|
245
245
|
**Client-side (CLI / GameClient) — `-1xxx`:**
|
|
246
246
|
|
|
@@ -363,7 +363,7 @@ godot-cli-control call /root/Game start_game 1 '"easy"' # int 1, string "easy
|
|
|
363
363
|
|
|
364
364
|
So `position '[100, 200]'` → `Vector2(100, 200)`, `transform '[1,0,0, 0,1,0, 0,0,1, 10,20,30]'` → `Transform3D(IDENTITY, (10,20,30))`. Wrong length or non-numeric elements fail loud with `-32602 "value type mismatch ..."` instead of silently setting `(0, 0)` like pre-0.2.5 versions did.
|
|
365
365
|
|
|
366
|
-
**`call` typed arguments use the same coercion (+ arg-count / type checks).** When you `call <node> <method>` and pass a JSON array for a parameter the method declares as a compound Variant (`Rect2`, `Vector2`, `Color`, …), the server reads the method signature (`get_method_list()`) and coerces it exactly like `set` above — e.g. `call /root/Mob enable_wander '[0, 0, 640, 480]'` reaches `func enable_wander(rect: Rect2)` as a real `Rect2`. Mismatches now **fail loud with `-32602`** instead of the old silent `ok: true / result: null`: wrong array length or a non-numeric element (`"value type mismatch ..."`), a JSON array handed to a scalar / Object parameter (`"cannot convert JSON Array ..."`), or the wrong number of arguments (`"argument count mismatch ..."`, which honors optional/default parameters and varargs). Scalar arguments (
|
|
366
|
+
**`call` typed arguments use the same coercion (+ arg-count / type checks).** When you `call <node> <method>` and pass a JSON array for a parameter the method declares as a compound Variant (`Rect2`, `Vector2`, `Color`, …), the server reads the method signature (`get_method_list()`) and coerces it exactly like `set` above — e.g. `call /root/Mob enable_wander '[0, 0, 640, 480]'` reaches `func enable_wander(rect: Rect2)` as a real `Rect2`. Mismatches now **fail loud with `-32602`** instead of the old silent `ok: true / result: null`: wrong array length or a non-numeric element (`"value type mismatch ..."`), a JSON array handed to a scalar / Object parameter (`"cannot convert JSON Array ..."`), or the wrong number of arguments (`"argument count mismatch ..."`, which honors optional/default parameters and varargs). Scalar arguments are now type-checked too (#183): a scalar passes through when the engine can implicitly convert it to the declared type (numeric group `bool`/`int`/`float`; string group `String`/`StringName`/`NodePath`), but a mismatch the engine would reject — a string handed to an `int` parameter, a number handed to a `String`/`StringName` parameter — **fails loud with `-32602`** (`"value type mismatch ..."`) rather than the old silent success. This closes the footgun where `callv` printed `Cannot convert argument ...` to the engine log but the RPC still reported success.
|
|
367
367
|
|
|
368
368
|
**Sub-path + Array also fails loud.** `set <node> transform:origin '[10, 20, 30]'` is rejected with `-32602 "sub-path + Array is not supported"`: Godot's `Object.set("transform:origin", Array)` silently drops the Array (origin stays at `(0,0,0)`) — same class of footgun as the strict Variant checks above, so the server pre-empts it. Sub-paths are scalar-only (`set <node> position:x 1.8`); to write a whole compound Variant use the top-level Array form above.
|
|
369
369
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bin/run_cli_control.ps1
RENAMED
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bin/run_cli_control.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/error_codes.gd
RENAMED
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/game_bridge.gd
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/render_api.gd
RENAMED
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/scene_api.gd
RENAMED
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/time_api.gd
RENAMED
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/variant_codec.gd
RENAMED
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/bridge/wait_api.gd
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/run_gut.py
RENAMED
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/addons/godot_cli_control/tests/run_gut.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/pytest_plugin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/skills_install.py
RENAMED
|
File without changes
|
{godot_cli_control-0.4.1 → godot_cli_control-0.4.2}/python/godot_cli_control/templates/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|