Merged
Conversation
neko-para
reviewed
Mar 2, 2026
Contributor
There was a problem hiding this comment.
你好——我发现了 4 个问题,并留下了一些整体反馈:
- 自定义控制器的额外信息目前是作为 JSON 字符串返回并嵌套在
info字段下(例如 Pythonget_custom_info->_c_get_info_agent->CustomControlUnitMgr::get_info),所以使用方看到的info是一个字符串化的 JSON,而不是合并后的对象;建议在返回前先解析并将自定义信息合并进顶层的json::object,或者更新 API/文档以反映当前的实际结构。 - 在
ControllerAgent::run_action中,control_unit_->get_info()每个动作会调用两次(执行前一次,完成后一次),并且结果会被嵌入到两次通知中;如果get_info的开销比较大或带有副作用,建议在单次动作范围内缓存结果,或者只在确实需要时重新计算。
给 AI Agent 的提示词
Please address the comments from this code review:
## Overall Comments
- The custom controller extra info is currently returned as a JSON string nested under the `info` field (e.g. Python `get_custom_info` -> `_c_get_info_agent` -> `CustomControlUnitMgr::get_info`), so consumers see `info` as a stringified JSON instead of a merged object; consider parsing and merging the custom info into the top-level `json::object` or updating the API/docs to reflect the actual structure.
- In `ControllerAgent::run_action`, `control_unit_->get_info()` is called twice per action (once before execution and once on completion) and the result is embedded in both notifications; if `get_info` can be expensive or has side effects, consider caching the result per action or only recomputing when needed.
## Individual Comments
### Comment 1
<location path="source/MaaCustomControlUnit/Manager/CustomControlUnitMgr.cpp" line_range="238" />
<code_context>
+{
+ json::object info;
+ info["type"] = "custom";
+ info["info"] = get_info_from_controller();
+ info["connected"] = connected();
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Controller-provided JSON is stored as a raw string instead of parsed JSON, which makes `info["info"]` inconsistent with other controllers.
Here `get_info_from_controller()` returns a `std::optional<std::string>` that is assigned directly to `info["info"]`, so consumers will see a JSON string instead of an object, unlike other controllers whose `get_info()` returns structured `json::object`s. This will require special-casing custom controllers.
To keep the API shape consistent, either parse the returned JSON string before assigning (e.g. `info["info"] = json::parse(*opt);`) or, if you intentionally store a string, clearly document that `info["info"]` is JSON-encoded rather than a nested object.
</issue_to_address>
### Comment 2
<location path="test/python/binding_test.py" line_range="396-401" />
<code_context>
assert isinstance(resolution, tuple), "resolution should be a tuple"
assert len(resolution) == 2, "resolution should have 2 elements"
+ # 测试 info
+ info = controller.info
+ print(f" info: {info}")
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for `CustomController.get_custom_info` / custom controller `info` integration
This only exercises the built-in debug controller. Since the PR introduces `CustomController.get_custom_info` that should be merged into the base `info`, please add a test with a simple custom controller overriding `get_custom_info`, and assert that `controller.info` contains both the base fields (like `"type"`) and the custom keys to verify the merge and callback wiring.
Suggested implementation:
```python
# 测试 info
info = dbg_controller.info
print(f" info: {info}")
assert isinstance(info, dict), "info should be a dict"
assert "type" in info, "info should contain 'type'"
assert info["type"].startswith("dbg_"), "dbg controller type should start with 'dbg_'"
# 测试 CustomController.get_custom_info / info 合并逻辑
class _TestCustomController(CustomController):
def get_custom_info(self):
# 使用几个简单的自定义字段,便于在 info 中断言
return {
"custom_key": "custom_value",
"answer": 42,
}
# 注意:这里假设 CustomController 的无参初始化是合法的,
# 如有需要请根据实际构造函数调整参数(见 <additional_changes>)
custom_controller = _TestCustomController()
custom_info = custom_controller.info
print(f" custom info: {custom_info}")
# 1. info 仍然应为 dict
assert isinstance(custom_info, dict), "custom info should be a dict"
# 2. 包含基础字段(例如 type)
assert "type" in custom_info, "custom info should contain base 'type' field"
assert isinstance(custom_info["type"], str), "custom info 'type' should be a string"
# 3. 包含 get_custom_info 返回的自定义字段,验证 merge & callback wiring
assert custom_info.get("custom_key") == "custom_value", "custom info should contain merged 'custom_key'"
assert custom_info.get("answer") == 42, "custom info should contain merged 'answer'"
# 测试输入操作
dbg_controller.post_click(100, 100).wait()
dbg_controller.post_swipe(100, 100, 200, 200, 100).wait()
```
)
custom_controller = _TestCustomController()
custom_info = custom_controller.info
print(f" custom info: {custom_info}")
# 1. info 仍然应为 dict
assert isinstance(custom_info, dict), "custom info should be a dict"
# 2. 包含基础字段(例如 type)
assert "type" in custom_info, "custom info should contain base 'type' field"
assert isinstance(custom_info["type"], str), "custom info 'type' should be a string"
# 3. 包含 get_custom_info 返回的自定义字段,验证 merge & callback wiring
assert custom_info.get("custom_key") == "custom_value", "custom info should contain merged 'custom_key'"
assert custom_info.get("answer") == 42, "custom info should contain merged 'answer'"
# 测试输入操作
dbg_controller.post_click(100, 100).wait()
dbg_controller.post_swipe(100, 100, 200, 200, 100).wait()
>>>>>>> REPLACE
</file_operation>
</file_operations>
<additional_changes>
1. 确保在 `test/python/binding_test.py` 顶部导入 `CustomController`,例如:
`from your_module_path import CustomController`
将 `your_module_path` 替换为实际定义 `CustomController` 的模块路径。
2. 如果 `CustomController` 的构造函数需要参数(例如设备、连接信息等),请将
`custom_controller = _TestCustomController()` 替换为带有正确参数的初始化调用。
3. 如果基础 `info` 字段中除了 `"type"` 还有其他需要保证存在的字段(例如 `"version"` 等),
可以在该测试中追加对应的 `assert` 来进一步验证 merge 逻辑。
</issue_to_address>
### Comment 3
<location path="test/agent/agent_child_test.py" line_range="271-275" />
<code_context>
assert isinstance(resolution, tuple), "resolution should be a tuple"
assert len(resolution) == 2, "resolution should have 2 elements"
+ # 测试 info
+ info = controller.info
+ print(f" info: {info}")
+ assert isinstance(info, dict), "info should be a dict"
+ assert "type" in info, "info should contain 'type'"
+
# 测试基本输入操作
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen remote-controller `info` assertions to validate controller type or key fields
Currently this only checks that `info` is a dict with a `"type"` key. Since this goes through the remote agent path, please also assert something about the actual contents—for example, that `info["type"]` matches the expected debug controller prefix (e.g. starts with `"dbg_"`), or that a controller-specific field like `"image_count"` / `"record_count"` is present. This better verifies that `ControllerGetInfo` is correctly forwarded and that the structured payload survives the round-trip.
```suggestion
# 测试 info
info = controller.info
print(f" info: {info}")
assert isinstance(info, dict), "info should be a dict"
assert "type" in info, "info should contain 'type'"
# info["type"] 应该是调试控制器类型前缀,例如 "dbg_*"
assert isinstance(info["type"], str), "info['type'] should be a str"
assert info["type"].startswith("dbg_"), "info['type'] should start with 'dbg_' for debug controllers"
# 至少要包含一个控制器特定字段,验证结构化 payload 是否完整
assert (
"image_count" in info or "record_count" in info
), "info should contain at least 'image_count' or 'record_count'"
```
</issue_to_address>
### Comment 4
<location path="docs/en_us/2.3-CallbackProtocol.md" line_range="89" />
<code_context>
- `uuid`: Unique identifier (string)
- `action`: Action type (string)
- `param`: Action parameters (object)
+- `info`: Controller information (object), see [MaaControllerGetInfo](2.2-IntegratedInterfaceOverview.md#maacontrollergetinfo)
#### `Controller.Action.Succeeded`
</code_context>
<issue_to_address>
**nitpick (typo):** Consider fixing the comma splice in the `info` field description for better grammar.
"Controller information (object), see ..." is a comma splice. Consider changing to "Controller information (object); see ..." or "Controller information (object). See ..." for correct grammar.
```suggestion
- `info`: Controller information (object). See [MaaControllerGetInfo](2.2-IntegratedInterfaceOverview.md#maacontrollergetinfo)
```
</issue_to_address>请帮我变得更有用!可以在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English
Hey - I've found 4 issues, and left some high level feedback:
- The custom controller extra info is currently returned as a JSON string nested under the
infofield (e.g. Pythonget_custom_info->_c_get_info_agent->CustomControlUnitMgr::get_info), so consumers seeinfoas a stringified JSON instead of a merged object; consider parsing and merging the custom info into the top-leveljson::objector updating the API/docs to reflect the actual structure. - In
ControllerAgent::run_action,control_unit_->get_info()is called twice per action (once before execution and once on completion) and the result is embedded in both notifications; ifget_infocan be expensive or has side effects, consider caching the result per action or only recomputing when needed.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The custom controller extra info is currently returned as a JSON string nested under the `info` field (e.g. Python `get_custom_info` -> `_c_get_info_agent` -> `CustomControlUnitMgr::get_info`), so consumers see `info` as a stringified JSON instead of a merged object; consider parsing and merging the custom info into the top-level `json::object` or updating the API/docs to reflect the actual structure.
- In `ControllerAgent::run_action`, `control_unit_->get_info()` is called twice per action (once before execution and once on completion) and the result is embedded in both notifications; if `get_info` can be expensive or has side effects, consider caching the result per action or only recomputing when needed.
## Individual Comments
### Comment 1
<location path="source/MaaCustomControlUnit/Manager/CustomControlUnitMgr.cpp" line_range="238" />
<code_context>
+{
+ json::object info;
+ info["type"] = "custom";
+ info["info"] = get_info_from_controller();
+ info["connected"] = connected();
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Controller-provided JSON is stored as a raw string instead of parsed JSON, which makes `info["info"]` inconsistent with other controllers.
Here `get_info_from_controller()` returns a `std::optional<std::string>` that is assigned directly to `info["info"]`, so consumers will see a JSON string instead of an object, unlike other controllers whose `get_info()` returns structured `json::object`s. This will require special-casing custom controllers.
To keep the API shape consistent, either parse the returned JSON string before assigning (e.g. `info["info"] = json::parse(*opt);`) or, if you intentionally store a string, clearly document that `info["info"]` is JSON-encoded rather than a nested object.
</issue_to_address>
### Comment 2
<location path="test/python/binding_test.py" line_range="396-401" />
<code_context>
assert isinstance(resolution, tuple), "resolution should be a tuple"
assert len(resolution) == 2, "resolution should have 2 elements"
+ # 测试 info
+ info = controller.info
+ print(f" info: {info}")
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for `CustomController.get_custom_info` / custom controller `info` integration
This only exercises the built-in debug controller. Since the PR introduces `CustomController.get_custom_info` that should be merged into the base `info`, please add a test with a simple custom controller overriding `get_custom_info`, and assert that `controller.info` contains both the base fields (like `"type"`) and the custom keys to verify the merge and callback wiring.
Suggested implementation:
```python
# 测试 info
info = dbg_controller.info
print(f" info: {info}")
assert isinstance(info, dict), "info should be a dict"
assert "type" in info, "info should contain 'type'"
assert info["type"].startswith("dbg_"), "dbg controller type should start with 'dbg_'"
# 测试 CustomController.get_custom_info / info 合并逻辑
class _TestCustomController(CustomController):
def get_custom_info(self):
# 使用几个简单的自定义字段,便于在 info 中断言
return {
"custom_key": "custom_value",
"answer": 42,
}
# 注意:这里假设 CustomController 的无参初始化是合法的,
# 如有需要请根据实际构造函数调整参数(见 <additional_changes>)
custom_controller = _TestCustomController()
custom_info = custom_controller.info
print(f" custom info: {custom_info}")
# 1. info 仍然应为 dict
assert isinstance(custom_info, dict), "custom info should be a dict"
# 2. 包含基础字段(例如 type)
assert "type" in custom_info, "custom info should contain base 'type' field"
assert isinstance(custom_info["type"], str), "custom info 'type' should be a string"
# 3. 包含 get_custom_info 返回的自定义字段,验证 merge & callback wiring
assert custom_info.get("custom_key") == "custom_value", "custom info should contain merged 'custom_key'"
assert custom_info.get("answer") == 42, "custom info should contain merged 'answer'"
# 测试输入操作
dbg_controller.post_click(100, 100).wait()
dbg_controller.post_swipe(100, 100, 200, 200, 100).wait()
```
)
custom_controller = _TestCustomController()
custom_info = custom_controller.info
print(f" custom info: {custom_info}")
# 1. info 仍然应为 dict
assert isinstance(custom_info, dict), "custom info should be a dict"
# 2. 包含基础字段(例如 type)
assert "type" in custom_info, "custom info should contain base 'type' field"
assert isinstance(custom_info["type"], str), "custom info 'type' should be a string"
# 3. 包含 get_custom_info 返回的自定义字段,验证 merge & callback wiring
assert custom_info.get("custom_key") == "custom_value", "custom info should contain merged 'custom_key'"
assert custom_info.get("answer") == 42, "custom info should contain merged 'answer'"
# 测试输入操作
dbg_controller.post_click(100, 100).wait()
dbg_controller.post_swipe(100, 100, 200, 200, 100).wait()
>>>>>>> REPLACE
</file_operation>
</file_operations>
<additional_changes>
1. 确保在 `test/python/binding_test.py` 顶部导入 `CustomController`,例如:
`from your_module_path import CustomController`
将 `your_module_path` 替换为实际定义 `CustomController` 的模块路径。
2. 如果 `CustomController` 的构造函数需要参数(例如设备、连接信息等),请将
`custom_controller = _TestCustomController()` 替换为带有正确参数的初始化调用。
3. 如果基础 `info` 字段中除了 `"type"` 还有其他需要保证存在的字段(例如 `"version"` 等),
可以在该测试中追加对应的 `assert` 来进一步验证 merge 逻辑。
</issue_to_address>
### Comment 3
<location path="test/agent/agent_child_test.py" line_range="271-275" />
<code_context>
assert isinstance(resolution, tuple), "resolution should be a tuple"
assert len(resolution) == 2, "resolution should have 2 elements"
+ # 测试 info
+ info = controller.info
+ print(f" info: {info}")
+ assert isinstance(info, dict), "info should be a dict"
+ assert "type" in info, "info should contain 'type'"
+
# 测试基本输入操作
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen remote-controller `info` assertions to validate controller type or key fields
Currently this only checks that `info` is a dict with a `"type"` key. Since this goes through the remote agent path, please also assert something about the actual contents—for example, that `info["type"]` matches the expected debug controller prefix (e.g. starts with `"dbg_"`), or that a controller-specific field like `"image_count"` / `"record_count"` is present. This better verifies that `ControllerGetInfo` is correctly forwarded and that the structured payload survives the round-trip.
```suggestion
# 测试 info
info = controller.info
print(f" info: {info}")
assert isinstance(info, dict), "info should be a dict"
assert "type" in info, "info should contain 'type'"
# info["type"] 应该是调试控制器类型前缀,例如 "dbg_*"
assert isinstance(info["type"], str), "info['type'] should be a str"
assert info["type"].startswith("dbg_"), "info['type'] should start with 'dbg_' for debug controllers"
# 至少要包含一个控制器特定字段,验证结构化 payload 是否完整
assert (
"image_count" in info or "record_count" in info
), "info should contain at least 'image_count' or 'record_count'"
```
</issue_to_address>
### Comment 4
<location path="docs/en_us/2.3-CallbackProtocol.md" line_range="89" />
<code_context>
- `uuid`: Unique identifier (string)
- `action`: Action type (string)
- `param`: Action parameters (object)
+- `info`: Controller information (object), see [MaaControllerGetInfo](2.2-IntegratedInterfaceOverview.md#maacontrollergetinfo)
#### `Controller.Action.Succeeded`
</code_context>
<issue_to_address>
**nitpick (typo):** Consider fixing the comma splice in the `info` field description for better grammar.
"Controller information (object), see ..." is a comma splice. Consider changing to "Controller information (object); see ..." or "Controller information (object). See ..." for correct grammar.
```suggestion
- `info`: Controller information (object). See [MaaControllerGetInfo](2.2-IntegratedInterfaceOverview.md#maacontrollergetinfo)
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a unified controller metadata query API (MaaControllerGetInfo) that returns structured JSON, threads it through core + remote controller implementations, exposes it in Python/NodeJS bindings, and includes the info object in controller action callback notifications.
Changes:
- Introduces
MaaControllerGetInfoC API andget_info()virtuals on controller/control-unit interfaces. - Implements
get_info()across concrete control units and wires it throughControllerAgentandRemoteController(including reverse request/response via Agent). - Exposes
controller.infoin Python/NodeJS bindings, updates callback payload docs, and extends Python tests.
Reviewed changes
Copilot reviewed 42 out of 43 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| test/python/binding_test.py | Extends Python binding tests to exercise controller.info. |
| test/agent/agent_child_test.py | Extends agent-child tests to assert controller.info shape. |
| source/modules/MaaFramework.cppm | Exports MaaControllerGetInfo from the C++ module. |
| source/include/MaaAgent/Message.hpp | Adds reverse request/response message types for controller info. |
| source/include/ControlUnit/ControlUnitAPI.h | Adds ControlUnitAPI::get_info() returning json::object. |
| source/include/Common/MaaTypes.h | Adds MaaController::get_info() returning json::object. |
| source/binding/Python/maa/define.py | Extends custom controller callback struct with get_info. |
| source/binding/Python/maa/controller.py | Adds Python Controller.info property, custom controller info hook, and callback detail includes info. |
| source/binding/NodeJS/src/apis/controller.h | Adds ControllerImpl::get_info() getter. |
| source/binding/NodeJS/src/apis/controller.d.ts | Updates TS typings for notification info and controller/custom-controller get_info. |
| source/binding/NodeJS/src/apis/controller.cpp | Implements controller.info getter + binds custom get_info. |
| source/binding/NodeJS/src/apis/callback.h | Adds Node custom-controller callback function declaration + callback table wiring (incl. get_info). |
| source/binding/NodeJS/src/apis/callback.cpp | Implements Node custom-controller get_info callback bridge. |
| source/MaaWlRootsControlUnit/Manager/WlRootsControlUnitMgr.h | Declares wlroots get_info(). |
| source/MaaWlRootsControlUnit/Manager/WlRootsControlUnitMgr.cpp | Implements wlroots get_info() payload. |
| source/MaaWin32ControlUnit/Manager/Win32ControlUnitMgr.h | Declares win32 get_info(). |
| source/MaaWin32ControlUnit/Manager/Win32ControlUnitMgr.cpp | Implements win32 get_info() payload. |
| source/MaaPlayCoverControlUnit/Manager/PlayCoverControlUnitMgr.h | Declares playcover get_info(). |
| source/MaaPlayCoverControlUnit/Manager/PlayCoverControlUnitMgr.cpp | Implements playcover get_info() payload. |
| source/MaaGamepadControlUnit/Manager/GamepadControlUnitMgr.h | Declares gamepad get_info(). |
| source/MaaGamepadControlUnit/Manager/GamepadControlUnitMgr.cpp | Implements gamepad get_info() payload. |
| source/MaaFramework/Controller/ControllerAgent.h | Declares controller-agent get_info(). |
| source/MaaFramework/Controller/ControllerAgent.cpp | Implements controller-agent get_info() and injects info into action callback details. |
| source/MaaDbgControlUnit/ReplayRecording/ReplayRecording.h | Declares debug replay-recording get_info(). |
| source/MaaDbgControlUnit/ReplayRecording/ReplayRecording.cpp | Implements debug replay-recording get_info() payload. |
| source/MaaDbgControlUnit/CarouselImage/CarouselImage.h | Declares debug carousel-image get_info(). |
| source/MaaDbgControlUnit/CarouselImage/CarouselImage.cpp | Implements debug carousel-image get_info() payload. |
| source/MaaCustomControlUnit/Manager/CustomControlUnitMgr.h | Declares custom control-unit get_info() and helper. |
| source/MaaCustomControlUnit/Manager/CustomControlUnitMgr.cpp | Implements custom control-unit get_info() and invokes custom callback. |
| source/MaaAgentServer/RemoteInstance/RemoteController.h | Declares remote-controller get_info(). |
| source/MaaAgentServer/RemoteInstance/RemoteController.cpp | Implements remote-controller get_info() via reverse messaging. |
| source/MaaAgentClient/Client/AgentClient.h | Adds handler declaration for controller-info reverse request. |
| source/MaaAgentClient/Client/AgentClient.cpp | Routes and handles controller-info reverse requests. |
| source/MaaAdbControlUnit/Manager/AdbControlUnitMgr.h | Declares adb get_info(). |
| source/MaaAdbControlUnit/Manager/AdbControlUnitMgr.cpp | Implements adb get_info() payload (paths/config/methods). |
| source/Common/MaaController.cpp | Adds C API entry point MaaControllerGetInfo. |
| include/MaaFramework/MaaMsg.h | Documents new info field in controller action callback detail schema. |
| include/MaaFramework/Instance/MaaCustomController.h | Extends public custom-controller callbacks with optional get_info. |
| include/MaaFramework/Instance/MaaController.h | Documents/declares public C API MaaControllerGetInfo. |
| docs/en_us/2.3-CallbackProtocol.md | Documents info field in controller action notifications. |
| docs/en_us/2.2-IntegratedInterfaceOverview.md | Documents MaaControllerGetInfo and per-controller returned fields. |
moomiji
added a commit
to MaaXYZ/MaaFramework.Binding.CSharp
that referenced
this pull request
Mar 8, 2026
moomiji
added a commit
to MaaXYZ/MaaFramework.Binding.CSharp
that referenced
this pull request
Mar 8, 2026
moomiji
added a commit
to MaaXYZ/MaaFramework.Binding.CSharp
that referenced
this pull request
Mar 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary by Sourcery
添加统一的控制器信息查询 API,并通过绑定和回调暴露其数据。
新增功能:
MaaControllerGetInfoAPI,以 JSON 格式获取结构化的控制器信息,包括类型和构造参数。增强:
get_info,以返回后端特定的详细信息。info字段。测试:
Original summary in English
Summary by Sourcery
Add a unified controller information query API and surface its data through bindings and callbacks.
New Features:
Enhancements:
Tests:
Original summary in English
Summary by Sourcery
添加统一的控制器信息查询 API,并通过绑定和回调暴露其数据。
新增功能:
MaaControllerGetInfoAPI,以 JSON 格式获取结构化的控制器信息,包括类型和构造参数。增强:
get_info,以返回后端特定的详细信息。info字段。测试:
Original summary in English
Summary by Sourcery
Add a unified controller information query API and surface its data through bindings and callbacks.
New Features:
Enhancements:
Tests: