godot-cli-control 0.2.4__tar.gz → 0.2.6__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.
Files changed (56) hide show
  1. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/PKG-INFO +1 -1
  2. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/CHANGELOG.md +26 -0
  3. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/README.md +23 -1
  4. godot_cli_control-0.2.6/addons/godot_cli_control/bridge/error_codes.gd +28 -0
  5. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/bridge/game_bridge.gd +29 -0
  6. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/bridge/input_simulation_api.gd +16 -12
  7. godot_cli_control-0.2.6/addons/godot_cli_control/bridge/low_level_api.gd +506 -0
  8. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/tests/gut/test_game_bridge.gd +23 -0
  9. godot_cli_control-0.2.6/addons/godot_cli_control/tests/gut/test_low_level_api.gd +719 -0
  10. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/_version.py +2 -2
  11. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/bridge.py +14 -2
  12. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/cli.py +391 -101
  13. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/client.py +13 -2
  14. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/init_cmd.py +74 -27
  15. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/pytest_plugin.py +3 -3
  16. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/templates/skill/SKILL.md +78 -5
  17. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/tests/test_bridge.py +47 -2
  18. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/tests/test_cli.py +541 -0
  19. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/tests/test_cli_helpers.py +5 -3
  20. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/tests/test_client.py +49 -0
  21. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/tests/test_init.py +121 -0
  22. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/tests/test_pytest_plugin.py +50 -0
  23. godot_cli_control-0.2.4/addons/godot_cli_control/bridge/low_level_api.gd +0 -280
  24. godot_cli_control-0.2.4/addons/godot_cli_control/tests/gut/test_low_level_api.gd +0 -194
  25. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/.gitignore +0 -0
  26. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/LICENSE +0 -0
  27. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/LICENSE +0 -0
  28. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/bin/run_cli_control.ps1 +0 -0
  29. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/bin/run_cli_control.sh +0 -0
  30. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/bridge/game_bridge.gd.uid +0 -0
  31. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/bridge/input_simulation_api.gd.uid +0 -0
  32. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/bridge/low_level_api.gd.uid +0 -0
  33. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/plugin.cfg +0 -0
  34. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/plugin.gd +0 -0
  35. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/plugin.gd.uid +0 -0
  36. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/tests/gut/test_input_simulation_api.gd +0 -0
  37. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/tests/run_gut.sh +0 -0
  38. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/tests/test_init_walkthrough.py +0 -0
  39. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/addons/godot_cli_control/tests/test_walkthrough.sh +0 -0
  40. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/pyproject.toml +0 -0
  41. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/README.md +0 -0
  42. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/__init__.py +0 -0
  43. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/__main__.py +0 -0
  44. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/_duration.py +0 -0
  45. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/daemon.py +0 -0
  46. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/registry.py +0 -0
  47. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/runner.py +0 -0
  48. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/skills_install.py +0 -0
  49. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/templates/__init__.py +0 -0
  50. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/godot_cli_control/templates/skill/__init__.py +0 -0
  51. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/tests/__init__.py +0 -0
  52. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/tests/test_daemon.py +0 -0
  53. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/tests/test_duration.py +0 -0
  54. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/tests/test_registry.py +0 -0
  55. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/python/tests/test_runner.py +0 -0
  56. {godot_cli_control-0.2.4 → godot_cli_control-0.2.6}/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.2.4
3
+ Version: 0.2.6
4
4
  Summary: WebSocket bridge for headless / scripted control of Godot scenes.
5
5
  Author: kesar
6
6
  License: MIT
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ### Fixed
6
+ - **#52 `set` 走 JSON Array 喂 Vector/Color/Rect 时静默失败**:`set zoom '[1.8, 1.8]'` 等价于 `node.set("zoom", [1.8, 1.8])`,Godot 隐式构造失败 → 实际值是 `Vector2(0,0)` 或被 clamp 到 `0.00001`,但服务端仍返 `{success: true}`。`handle_set_property` 现在查 `get_property_list()` 拿声明类型,把 numeric Array 转成对应 Variant。长度不匹配或元素非数字时 fail-loud 返 `-32602 "value type mismatch ..."`,不再 silent corruption。
7
+ - **sub-path 标量赋值现在真的会写入**:之前 `set <node> position:x 1.8` 调的是 `Object.set("position:x", 1.8)`,Godot 4 的 `Object.set` 把整串当字面属性名找不到就 silent no-op(依旧返 `{success: true}` 但 `position.x` 不变)。改用 `Object.set_indexed(NodePath, value)` 才会按 sub-path 写入。是 #54 review 阶段被新加的 `test_set_subpath_scalar_still_works` 捕获的隐藏 silent-fail。
8
+
9
+ ### Added
10
+ - **#54 全部复合 Variant 都支持 Array → Variant coerce**:从 4-float 简单类型到 16-float 矩阵,全部按 axis-vector 顺序的 flat numeric Array 写入(每 N 元素 = 一个 Vector 轴)。覆盖:`Vector2/2i/3/3i/4/4i`、`Rect2/2i`、`Color`(3-element=RGB / 4-element=RGBA)、`Plane(a,b,c,d)`、`Quaternion(x,y,z,w)`、`AABB(pos 3, size 3)`、`Basis(9 axis-vector)`、`Transform2D(xaxis 2, yaxis 2, origin 2)`、`Transform3D(basis 9, origin 3)`、`Projection(16 axis-vector)`。具体 layout 见 SKILL.md。`Plane.normal` 和 `Quaternion` 不会自动归一化(与 Godot ctor 一致)。
11
+ - **#54 防御性 fallback**:未在 coerce 名单也不在 `_ARRAY_PASSTHROUGH_SAFE_TYPES`(基本类型 / Object / 集合 / Packed*Array)白名单的声明类型 + Array 输入会 fail-loud,避免未来 Godot 加新 compound Variant 时 silent-corrupt 回归。
12
+ - **#54 sub-path + Array 现在 fail-loud**:`set <node> transform:origin '[10, 20, 30]'` 在 Godot 里会 silent-corrupt(Vector3 leaf 不会从 Array 隐式构造,origin 仍是 (0,0,0)),server 现在主动返 `-32602 "sub-path + Array is not supported"` 提示走 top-level 形式(如 `set <node> transform '[basis 9, origin 3]'`)。sub-path 标量赋值(`position:x 1.8`)不变。
13
+
5
14
  ### AI-friendly CLI 改造(多个 BREAKING change)
6
15
 
7
16
  把 CLI 重定位成 AI agent 的一等接口:默认结构化输出、补齐读 / 写 / 发现的 shell 命令、明确退出码契约。shell-only 的 agent 现在不需要写 Python 脚本就能完成全部操作。
@@ -44,6 +53,23 @@
44
53
  #### Out of scope (intentional)
45
54
  - `run <script.py>` 子命令保留旧的人类可读 stderr 输出。它是交互式脚本宿主,不是 RPC,新的 JSON 信封契约只覆盖 RPC 子命令 + daemon 三命令。
46
55
 
56
+ ### AI-friendliness review fixes (2026-05-11)
57
+
58
+ #### Fixed
59
+ - **CLI flag position**: `--json` / `--text` / `--no-json` 现在在 RPC 子命令尾部也接受(之前只能写最前面)。argparse 子 parser 用 `default=argparse.SUPPRESS` + 顶层 `set_defaults` 兜底,避免子 parser 默认值覆盖父 parser 解析结果。
60
+ - **Error code 1004 collision**: `low_level_api.gd` 的 scene tree 超限改用新业务码 `1005 "scene tree too large"`,与 input_simulation `1004 "combo in progress"` 解耦。新增 `error_codes.gd` 集中常量。
61
+ - **pytest fixture default port**: `--godot-cli-port` 默认从 9877 改为 0(OS-assigned),与 `daemon start` 默认对齐;多项目并行测试不再撞端口。
62
+ - **Addon README error-code table**: 之前只列到 1003,补全 1004 / 1005 / 客户端 -1xxx 段。
63
+ - **Scene tree hard limit bypass (DoS fix)**: `handle_get_scene_tree` 入口现在把 `max_nodes` clamp 到硬墙 `_BUILD_TREE_NODE_LIMIT` (5000)。修复前客户端传 `max_nodes=999999` 会让服务端先把整棵超大树构造成 Dictionary 再被 1005 错误丢弃(内存浪费 / OOM 路径)。
64
+ - **Error code 1003 semantic split**: screenshot 在 viewport texture 为 null 时不再借用 `1003 METHOD_NOT_FOUND`,改用新业务码 `1006 RESOURCE_UNAVAILABLE`。1003 现在是纯 schema 错(不应 retry),1006 是 transient 错(短重试可能成功),agent 据此分别处置。
65
+
66
+ #### Added
67
+ - `tree --max-nodes <N>`(默认 200):节点数软上限;超出时响应含 `truncated: true` + `total_nodes`,agent 据此决定分子树。硬墙仍是 5000 节点 → `1005`。
68
+ - `set` / `call --text-value`:禁用 JSON 解析、把 value/args 强制按字符串处理,避开 `null` / `true` / `42` 这类字面量被解析成 Variant 类型的 footgun。
69
+
70
+ #### Changed
71
+ - **BREAKING (轻微)**:`daemon start` / `run` 默认 headless 行为改为基于 `sys.stdout.isatty()` 自动判定 —— pipe / CI / agent shell 默认 headless;交互终端默认开窗。新增 `--gui` 强制开窗 flag。`--headless` 仍可显式传,覆盖自动判。脚本里依赖 "默认会开窗" 的需要加 `--gui`。
72
+
47
73
  ## [0.1.6] - Unreleased
48
74
 
49
75
  ### Added
@@ -99,7 +99,29 @@ All methods callable via `godot-cli-control <method>` or `from godot_cli_control
99
99
  | `combo_cancel()` | `await client.combo_cancel()` |
100
100
  | `release_all()` | `await client.release_all()` |
101
101
 
102
- Error codes: `-32600` invalid request, `-32601` unknown method, `-32602` invalid params, `1001` node not found, `1002` property not found, `1003` method not found.
102
+ ### Error codes
103
+
104
+ Three numeric ranges share `error.code`; they never overlap, so a single field is unambiguous.
105
+
106
+ | Code | Source | Meaning |
107
+ |---|---|---|
108
+ | `1001` | server | Node not found at the given path |
109
+ | `1002` | server | Property not found / shape mismatch |
110
+ | `1003` | server | Method not found on the node (schema error, don't retry) |
111
+ | `1004` | server | Combo already in progress (call `combo-cancel` to retry) |
112
+ | `1005` | server | Scene tree too large (lower `depth` or pass `--max-nodes`) |
113
+ | `1006` | server | Resource transiently unavailable (e.g. screenshot during scene transition). Rare under normal use — GameBridge waits for viewport first-frame before listening, and `screenshot` retries internally. Safe to retry if you do hit it. |
114
+ | `-32600` | server | Malformed JSON-RPC request |
115
+ | `-32601` | server | Unknown method name |
116
+ | `-32602` | server | Invalid params (incl. blocked methods/properties from the security blacklist, or `set` value-type mismatch — e.g. `Vector2` property given an array of wrong length / non-numeric elements) |
117
+ | `-1001` | client | Connection failure (daemon not running, port wrong, proxy hijacking localhost) |
118
+ | `-1002` | client | Timeout waiting for response |
119
+ | `-1003` | client | CLI usage error (combo missing steps, malformed `--steps-json`, …) |
120
+ | `-1004` | client | Local file IO error (e.g. screenshot can't write the destination) |
121
+ | `-1005` | client | `run <script>` user script raised an uncaught exception — fix the script |
122
+ | `-1099` | client | Internal CLI bug — please file an issue |
123
+
124
+ For full retry guidance see the SKILL.md shipped by `godot-cli-control init` (`.claude/skills/godot-cli-control/SKILL.md` in the target project).
103
125
 
104
126
  ## Activation Modes
105
127
 
@@ -0,0 +1,28 @@
1
+ class_name CliControlErrorCodes
2
+ extends RefCounted
3
+ ## 集中错误码常量。新加业务码必须在这里登记,
4
+ ## 避免 1004 那种隐式撞码(input_sim 用 "combo in progress",
5
+ ## low_level 又用 "scene tree too large")。
6
+ ##
7
+ ## 三段制(详见 SKILL.md 错误码表):
8
+ ## 1xxx 服务端业务码
9
+ ## -32xxx JSON-RPC 标准
10
+ ## -1xxx 客户端(Python)侧;GDScript 这边不会产出
11
+
12
+ const NODE_NOT_FOUND: int = 1001
13
+ const PROPERTY_NOT_FOUND: int = 1002 # 也用于 "node has no 'text' property"
14
+ const METHOD_NOT_FOUND: int = 1003
15
+ const COMBO_IN_PROGRESS: int = 1004
16
+ const SCENE_TREE_TOO_LARGE: int = 1005
17
+ # 资源 transient 不可用(screenshot viewport texture null 等)。
18
+ # 与 1003 拆开:1003 是 schema 错(永久),1006 是时机错(短重试可能成功)。
19
+ # issue #61 落地后语义:GameBridge 启动 gate(H)保证 client 连上时 viewport
20
+ # 至少画过一帧 + take_screenshot_async 内部循环(D)兜底动态 transient,
21
+ # 所以正常用法下 1006 不应触发。但它仍是 last-resort 合法信号 ——
22
+ # client 必须保留对 1006 的处理(不要假设它消失),未来若改为 fail-loud
23
+ # 会让 scene 切换瞬间的截图变成硬错。
24
+ const RESOURCE_UNAVAILABLE: int = 1006
25
+
26
+ const INVALID_PARAMS: int = -32602
27
+ const INVALID_REQUEST: int = -32600
28
+ const METHOD_UNKNOWN: int = -32601
@@ -6,6 +6,11 @@ const DEFAULT_PORT: int = 9877
6
6
  const SETTING_AUTO_ENABLE: String = "godot_cli_control/auto_enable_in_debug"
7
7
  const SETTING_OUTBOUND_BUFFER_MB: String = "godot_cli_control/outbound_buffer_mb"
8
8
  const DEFAULT_OUTBOUND_BUFFER_MB: int = 10
9
+ # 启动 gate:listen 前等 viewport 首帧 ready 的上限帧数。
10
+ # 60fps 下 ~2s;shader 编译 / 大资源加载超过这个就走 fallback —— 仍开端口,
11
+ # 把后续 screenshot 的 transient 兜底交给 take_screenshot_async 的循环。
12
+ # 设计意图见 issue #61:根因消除(H)+ handler 内动态兜底(D)。
13
+ const FIRST_FRAME_READY_MAX_FRAMES: int = 120
9
14
 
10
15
  var _tcp_server: TCPServer = TCPServer.new()
11
16
  var _active_peer: WebSocketPeer = null
@@ -56,6 +61,13 @@ func _ready() -> void:
56
61
  ProjectSettings.get_setting(SETTING_OUTBOUND_BUFFER_MB, DEFAULT_OUTBOUND_BUFFER_MB)
57
62
  )
58
63
  _outbound_buffer_size = max(1, mb) * 1024 * 1024
64
+ # 启动 gate:等 viewport 首帧 ready 后再开 listen。
65
+ # 根因:issue #61。screenshot 在「viewport 从未画过一次」时拿到 null
66
+ # texture → 报 1006 transient;让 client connect 上来这件事本身就承诺
67
+ # 「至少画过一帧」,把根因消除而不是各 handler 各自打补丁。
68
+ # 超时不阻塞 listen —— 端口冲突 / 启动诊断信号要保留(_tcp_server.listen
69
+ # 失败的 printerr 在端口冲突时是用户唯一的 root cause 提示)。
70
+ await _wait_first_frame_ready()
59
71
  # 启动 TCP 服务器
60
72
  _port = _parse_port_from_args()
61
73
  # 安全:显式绑 127.0.0.1,避免 Godot TCPServer 默认 "*" 暴露到 LAN
@@ -81,6 +93,23 @@ func _ready() -> void:
81
93
  print("GameBridge: idle-timeout %ds enabled" % _idle_timeout_secs)
82
94
 
83
95
 
96
+ func _wait_first_frame_ready() -> void:
97
+ # dummy renderer (--headless) 下 RenderingServer.frame_post_draw 永不发射;
98
+ # 与 take_screenshot_async 用同一套检测,dummy 路径走 process_frame ×2。
99
+ # 上限 FIRST_FRAME_READY_MAX_FRAMES 后无论 viewport 是否 ready 都返回,
100
+ # 让 listen 不被 GPU 卡死 / 大场景首帧拖住,避免 daemon 启动绑架到项目复杂度。
101
+ var dummy: bool = RenderingServer.get_rendering_device() == null
102
+ if dummy:
103
+ await get_tree().process_frame
104
+ await get_tree().process_frame
105
+ return
106
+ for _i in FIRST_FRAME_READY_MAX_FRAMES:
107
+ await RenderingServer.frame_post_draw
108
+ var image: Image = get_viewport().get_texture().get_image()
109
+ if image != null:
110
+ return
111
+
112
+
84
113
  func _process(_delta: float) -> void:
85
114
  if not _tcp_server.is_listening():
86
115
  return
@@ -1,6 +1,10 @@
1
1
  class_name InputSimulationApi
2
2
  extends Node
3
3
  ## 输入模拟 API:动作级按键、持续控制、组合序列
4
+ ##
5
+ ## 错误码常量来自 res://addons/godot_cli_control/bridge/error_codes.gd
6
+ ## (class_name CliControlErrorCodes)。靠 Godot 全局 class 注册解析;若 GUT
7
+ ## 测试跑前遇到 "Class 'CliControlErrorCodes' not found",先 import 一次。
4
8
 
5
9
  # 手动按下的动作(无定时器)
6
10
  var _pressed_actions: Dictionary = {}
@@ -46,10 +50,10 @@ func is_combo_active() -> bool:
46
50
 
47
51
  func handle_action_press(params: Dictionary) -> Dictionary:
48
52
  if _combo_active:
49
- return _err(1004, "combo in progress")
53
+ return _err(CliControlErrorCodes.COMBO_IN_PROGRESS, "combo in progress")
50
54
  var action: String = params.get("action", "") as String
51
55
  if not InputMap.has_action(action):
52
- return _err(1003, "Unknown action: %s" % action)
56
+ return _err(CliControlErrorCodes.METHOD_NOT_FOUND, "Unknown action: %s" % action)
53
57
  _do_press(action)
54
58
  _pressed_actions[action] = true
55
59
  return {"success": true}
@@ -57,10 +61,10 @@ func handle_action_press(params: Dictionary) -> Dictionary:
57
61
 
58
62
  func handle_action_release(params: Dictionary) -> Dictionary:
59
63
  if _combo_active:
60
- return _err(1004, "combo in progress")
64
+ return _err(CliControlErrorCodes.COMBO_IN_PROGRESS, "combo in progress")
61
65
  var action: String = params.get("action", "") as String
62
66
  if not InputMap.has_action(action):
63
- return _err(1003, "Unknown action: %s" % action)
67
+ return _err(CliControlErrorCodes.METHOD_NOT_FOUND, "Unknown action: %s" % action)
64
68
  _do_release(action)
65
69
  _pressed_actions.erase(action)
66
70
  _held_actions.erase(action)
@@ -71,10 +75,10 @@ func handle_action_release(params: Dictionary) -> Dictionary:
71
75
  ## 定时器到期后自动释放
72
76
  func handle_action_tap(params: Dictionary) -> Dictionary:
73
77
  if _combo_active:
74
- return _err(1004, "combo in progress")
78
+ return _err(CliControlErrorCodes.COMBO_IN_PROGRESS, "combo in progress")
75
79
  var action: String = params.get("action", "") as String
76
80
  if not InputMap.has_action(action):
77
- return _err(1003, "Unknown action: %s" % action)
81
+ return _err(CliControlErrorCodes.METHOD_NOT_FOUND, "Unknown action: %s" % action)
78
82
  var duration: float = params.get("duration", 0.1) as float
79
83
  _do_press(action)
80
84
  _held_actions[action] = duration
@@ -104,10 +108,10 @@ func handle_list_input_actions(params: Dictionary) -> Dictionary:
104
108
 
105
109
  func handle_hold(params: Dictionary) -> Dictionary:
106
110
  if _combo_active:
107
- return _err(1004, "combo in progress")
111
+ return _err(CliControlErrorCodes.COMBO_IN_PROGRESS, "combo in progress")
108
112
  var action: String = params.get("action", "") as String
109
113
  if not InputMap.has_action(action):
110
- return _err(1003, "Unknown action: %s" % action)
114
+ return _err(CliControlErrorCodes.METHOD_NOT_FOUND, "Unknown action: %s" % action)
111
115
  var duration: float = params.get("duration", 0.0) as float
112
116
  _do_press(action)
113
117
  _held_actions[action] = duration
@@ -137,7 +141,7 @@ func release_all() -> void:
137
141
  func handle_combo(params: Dictionary, request_id: String) -> void:
138
142
  if _combo_active:
139
143
  if _send_response_callback.is_valid():
140
- _send_response_callback.call(request_id, _err(1004, "combo in progress"))
144
+ _send_response_callback.call(request_id, _err(CliControlErrorCodes.COMBO_IN_PROGRESS, "combo in progress"))
141
145
  return
142
146
  var steps: Array = params.get("steps", []) as Array
143
147
  _combo_request_id = request_id
@@ -188,7 +192,7 @@ func _begin_combo_step() -> void:
188
192
  var raw: Variant = _combo_steps[_combo_index]
189
193
  if not raw is Dictionary:
190
194
  _abort_combo_with_error(
191
- -32602, "combo step must be object at index %d" % _combo_index
195
+ CliControlErrorCodes.INVALID_PARAMS, "combo step must be object at index %d" % _combo_index
192
196
  )
193
197
  return
194
198
  var step: Dictionary = raw as Dictionary
@@ -198,7 +202,7 @@ func _begin_combo_step() -> void:
198
202
  var action: String = step["action"] as String
199
203
  if not InputMap.has_action(action):
200
204
  _abort_combo_with_error(
201
- 1003, "Unknown action at combo step %d: %s" % [_combo_index, action]
205
+ CliControlErrorCodes.METHOD_NOT_FOUND, "Unknown action at combo step %d: %s" % [_combo_index, action]
202
206
  )
203
207
  return
204
208
  var duration: float = step.get("duration", 0.1) as float
@@ -207,7 +211,7 @@ func _begin_combo_step() -> void:
207
211
  _combo_timer = duration
208
212
  else:
209
213
  _abort_combo_with_error(
210
- -32602, "combo step missing 'wait' or 'action' at index %d" % _combo_index
214
+ CliControlErrorCodes.INVALID_PARAMS, "combo step missing 'wait' or 'action' at index %d" % _combo_index
211
215
  )
212
216
 
213
217