Repository navigation
feat(pipeline): 支持 JSONC 语法(pipeline 文件与 interface.json) - #41
Conversation
…json MaaFramework 的 pipeline 解析器(meojson json::parsec)接受带注释与尾逗号的 JSONC,MaaMCP 用标准库 json 读同一份文件会报解析失败。 新增 maa_mcp/jsonc.py,在 Python 侧复刻 meojson 的 JSONC 语义: - 允许 // 行注释、/* */ 块注释、对象/数组尾逗号 - 拒绝无引号键、单引号、NaN/Infinity、重复键(与 MaaFramework 一致) 实现走「注释替换为等长空白 → 剥尾逗号 → 标准库 json.loads」, 字节偏移不变,因此标准库自带的行列号报错可直接对到原文件。 接入点: - _read_and_validate_pipelines:覆盖 load_pipeline / run_pipeline / benchmark_node - save_pipeline:输入字符串接受 JSONC,落盘仍是格式化后的标准 JSON - agent_supervisor.load_agent_config:interface.json 允许注释
tests/test_performance_stress.py 此前没有任何 marker,`pytest tests/` 每次都会 跑到它;其 setup_class 直接取 `find_window_list()[0]` 当目标,而桌面窗口 列表的第一项通常就是用户正在使用的编辑器窗口(实测为 VS Code),随后 向该窗口注入 20 轮 click / swipe / input_text / click_key / scroll / double_click —— 这会在跑测试时把用户的 VS Code 关掉。 同时该行为违反 win32.py 里「多个窗口时绝不自动选择」的约定。 改动: - 整个文件标 @pytest.mark.integration,并默认 skip,需 MAA_STRESS=1 开启 - 目标必须显式指定:MAA_STRESS_DEVICE / MAA_STRESS_WINDOW,二者都没给 时直接 skip,不再自动挑第一个设备 / 窗口 - 指定的设备 / 窗口不存在时 skip,并列出可选项 - __main__ 入口同样加开关,避免直接 python 运行时的误伤 - CLAUDE.md / CLAUDE_CN.md 同步说明该约定
code review 发现的两个 HIGH 问题: - `1e999` 解析成 inf,落盘时 json.dump 写出非法的 `Infinity` 字面量, 文件既不是合法 JSON,MaaMCP 自己都读不回来 → 用 parse_float 钩子拦掉 非有限数字 - 落单的代理字符(`"\ud800"`)被标准库接受,但后续 json.dumps(...).encode() 会抛 UnicodeEncodeError,异常直接冲出工具调用 → 解析后递归还原代理对、 拒绝落单代理(与 meojson 行为一致) 同时按 review 意见修正设计文档: - 「实现」一节原写的 `_reject_duplicates` / `_reject_constant` 两个钩子并不存在, 改成实际的 `_make_pairs_hook` / `_make_parse_float` + `_reject_non_json_constants` - 「兼容性」原称「唯一新增的严格性是重复键报错」,漏了 NaN/Infinity 与代理字符 - 「兼容性」原称交给 MaaFramework 的是 `json.dumps` 产出的严格 JSON,实际是 传 dict 给 Resource.override_pipeline,由 maafw 内部 dumps - 补上「有意偏离 meojson 的三处」小节、BOM 偏移说明与新的错误信息 CLAUDE.md / CLAUDE_CN.md 同步:拒绝项补上落单代理字符,有意偏离由「两处」 更正为「三处」(补上尾部垃圾)。
复查(对照 meojson 源码 + MaaFramework 5.14.3 实测)发现: - 文档事实错误:原「有意偏离 meojson 的三处」里的「尾部垃圾」并非偏离—— meojson 的 parse() 在顶层值之后遇到非空白残余是**报错**的 (parser_impl.hpp: `if (skip_whitespace()) return nullopt;`),本模块同样 报错,行为一致。真正需要偏离的只有两处:溢出数字、顶层值类型。 jsonc.py / 设计文档 / CLAUDE.md / CLAUDE_CN.md 同步更正。 - 设计文档「接入点」表把 interface.json 的对齐对象写成了 MaaFramework—— MaaFramework 核心并不读 interface.json,实际读它的是 MFAAvalonia (Newtonsoft.Json,容忍注释)。已更正并补上 MAAGC 的注意事项: MAAGC 的 agent/main.py 用标准库 json.load 读同一文件检测版本, 带注释会让它回退到版本 "unknown"。 另外补一个未处理的异常路径:嵌套层级过深的文件会让标准库 json 抛 RecursionError,原先只有 JSONDecodeError / UnicodeDecodeError 被捕获, 异常会直接冲出工具调用。现在三个接入点都把 RecursionError 转成明确报错。 测试补充:尾部垃圾 / 尾部注释、与标准库的差分对拍(纯 JSON 必须完全一致)、 深嵌套错误路径。
独立复查发现两处与 meojson 的偏差,本次一并修正:
1. `{,}` / `[,]`(空容器里的逗号)此前被当成尾逗号剥掉,MaaMCP 接受、
MaaFramework 拒绝。meojson 的解析循环必须先完成一个键值对才会收尾,
所以这类逗号是语法错误。现在剥尾逗号时会检查前面是否已有成员。
2. 顶层值之后的尾部残余:meojson 的 `skip_whitespace` 把「已到输入末尾」
与「注释起始符非法」混为一谈,会静默接受并丢弃 `/x` 或未闭合的块注释,
MaaFramework 继承了这一行为。本模块按标准库语义报错——方向是更严,
不会接受 MaaFramework 拒绝的文件。已作为第三处有意偏离写进文档。
顺带修掉 `jsonc.py` 里未导入的 `List` 注解。
验证:
- 32 例真 MaaFramework 对照,不一致全部落在 3 处已记录的偏离内,
无新增偏差
- 20000 例随机合法 JSON 与标准库差分 0 处不一致
- pytest 326 passed / 15 skipped
There was a problem hiding this comment.
你好 - 我发现了 5 个问题
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="maa_mcp/jsonc.py" line_range="298-299" />
<code_context>
+ if isinstance(value, list):
+ return [_sanitize_surrogates(item, doc) for item in value]
+ if isinstance(value, dict):
+ return {
+ _decode_surrogates(key, doc): _sanitize_surrogates(item, doc)
+ for key, item in value.items()
+ }
</code_context>
<issue_to_address>
**Duplicate keys silently overwrite values**
When an object contains the same supplementary Unicode key both literally and as an escaped surrogate pair, `_make_pairs_hook` compares the keys before normalization, so both pass as distinct; `_sanitize_surrogates` then converts them to the same key and its dict comprehension silently discards one value. Duplicate-key validation misses the collision, and pipeline or configuration data loses a node or field.
Normalize keys before duplicate detection and reject any collision created by normalization.
Also at `maa_mcp/jsonc.py:230-231`.
</issue_to_address>
### Comment 2
<location path="maa_mcp/jsonc.py" line_range="102" />
<code_context>
+ marker = text[i + 1]
+ if marker == "/":
+ # 行注释:到行尾为止(换行符本身保留)
+ end = text.find("\n", i)
+ end = n if end == -1 else end
+ for k in range(i, end):
</code_context>
<issue_to_address>
**CR-only comments discard following JSON**
When JSONC contains a `//` comment terminated by a bare carriage return, `_strip_comments` searches only for `\n`, so it treats the rest of the document as comment text and blanks it; `loads` then rejects otherwise valid JSONC, preventing callers such as `save_pipeline` from accepting or saving it.
End line comments at either `\r` or `\n` while preserving the line-ending character.
</issue_to_address>
### Comment 3
<location path="maa_mcp/jsonc.py" line_range="76" />
<code_context>
+ 注释内容替换为空格(块注释内的换行保留),返回文本与入参等长,
+ 行列号完全一致,后续步骤的报错位置可以直接沿用。
+ """
+ out = list(text)
+ i = 0
+ n = len(text)
</code_context>
<issue_to_address>
**Large inputs exhaust memory**
When a pipeline file is large enough that the input and intermediate character lists exceed available memory, each preprocessing pass expands the input into a per-character list and joins it into another full string; `_read_and_validate_pipelines` also reads the whole file before parsing. Large but valid pipeline files can consume many times their file size in memory and exhaust the MaaMCP process while loading.
Avoid per-character list copies during preprocessing or impose a documented input-size limit before reading and parsing pipeline files.
Also at `maa_mcp/jsonc.py:132`.
</issue_to_address>
### Comment 4
<location path="maa_mcp/jsonc.py" line_range="331" />
<code_context>
+ cleaned = _strip_trailing_commas(cleaned)
+
+ try:
+ parsed = json.loads(
+ cleaned,
+ object_pairs_hook=_make_pairs_hook(doc),
</code_context>
<issue_to_address>
**Huge integers escape validation**
When a pipeline or interface file contains an integer longer than the interpreter’s configured maximum digit count, the default integer parser raises `ValueError` for an integer exceeding Python’s configured digit limit (on supported Python versions with that limit enabled). `save_pipeline` and `load_agent_config` do not catch this parse-time error, so callers receive an exception instead of a validation error or `None`.
Convert integer-conversion `ValueError` into `JsoncDecodeError`, or catch it at each consumer and return the documented parse failure.
Also at `maa_mcp/pipeline_tools.py:886`, `maa_mcp/agent_supervisor.py:129`.
</issue_to_address>
### Comment 5
<location path="tests/test_performance_stress.py" line_range="273" />
<code_context>
window_list = _call_tool(find_window_list)
- if window_list:
- self.window_name = window_list[0] # 使用第一个窗口
- self.controller_id = _call_tool(connect_window, self.window_name)
- print(
- f" 使用Windows窗口: {self.window_name}, 控制器ID: {self.controller_id}"
</code_context>
<issue_to_address>
**Duplicate titles select the wrong window**
When multiple desktop windows share the configured title, `connect_window` resolves the selected title through the name-keyed registry, where duplicate titles overwrite one another; the stress test can therefore send input to a different same-titled window than intended. The explicit environment setting does not disambiguate the target.
Skip or require an unambiguous target when the configured title occurs more than once, or select the window using a unique identifier such as its handle.
</issue_to_address>关联问题
#38 是否考虑支持jsonc语法 · 已满足 1 项,共 1 项
| # | 要求 | 状态 | 备注 |
|---|---|---|---|
| 1 | 支持包含注释的 Pipeline JSON,避免 load_pipeline 或 save_pipeline 因注释报错。 | ✅ 已满足 | Pipeline 文件读取和 save_pipeline 的输入都改用 JSONC 解析器;run_pipeline 和 benchmark_node 也复用该文件读取路径。 |
Original comment in English
Hey - I've found 5 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="maa_mcp/jsonc.py" line_range="298-299" />
<code_context>
+ if isinstance(value, list):
+ return [_sanitize_surrogates(item, doc) for item in value]
+ if isinstance(value, dict):
+ return {
+ _decode_surrogates(key, doc): _sanitize_surrogates(item, doc)
+ for key, item in value.items()
+ }
</code_context>
<issue_to_address>
**Duplicate keys silently overwrite values**
When an object contains the same supplementary Unicode key both literally and as an escaped surrogate pair, `_make_pairs_hook` compares the keys before normalization, so both pass as distinct; `_sanitize_surrogates` then converts them to the same key and its dict comprehension silently discards one value. Duplicate-key validation misses the collision, and pipeline or configuration data loses a node or field.
Normalize keys before duplicate detection and reject any collision created by normalization.
Also at `maa_mcp/jsonc.py:230-231`.
</issue_to_address>
### Comment 2
<location path="maa_mcp/jsonc.py" line_range="102" />
<code_context>
+ marker = text[i + 1]
+ if marker == "/":
+ # 行注释:到行尾为止(换行符本身保留)
+ end = text.find("\n", i)
+ end = n if end == -1 else end
+ for k in range(i, end):
</code_context>
<issue_to_address>
**CR-only comments discard following JSON**
When JSONC contains a `//` comment terminated by a bare carriage return, `_strip_comments` searches only for `\n`, so it treats the rest of the document as comment text and blanks it; `loads` then rejects otherwise valid JSONC, preventing callers such as `save_pipeline` from accepting or saving it.
End line comments at either `\r` or `\n` while preserving the line-ending character.
</issue_to_address>
### Comment 3
<location path="maa_mcp/jsonc.py" line_range="76" />
<code_context>
+ 注释内容替换为空格(块注释内的换行保留),返回文本与入参等长,
+ 行列号完全一致,后续步骤的报错位置可以直接沿用。
+ """
+ out = list(text)
+ i = 0
+ n = len(text)
</code_context>
<issue_to_address>
**Large inputs exhaust memory**
When a pipeline file is large enough that the input and intermediate character lists exceed available memory, each preprocessing pass expands the input into a per-character list and joins it into another full string; `_read_and_validate_pipelines` also reads the whole file before parsing. Large but valid pipeline files can consume many times their file size in memory and exhaust the MaaMCP process while loading.
Avoid per-character list copies during preprocessing or impose a documented input-size limit before reading and parsing pipeline files.
Also at `maa_mcp/jsonc.py:132`.
</issue_to_address>
### Comment 4
<location path="maa_mcp/jsonc.py" line_range="331" />
<code_context>
+ cleaned = _strip_trailing_commas(cleaned)
+
+ try:
+ parsed = json.loads(
+ cleaned,
+ object_pairs_hook=_make_pairs_hook(doc),
</code_context>
<issue_to_address>
**Huge integers escape validation**
When a pipeline or interface file contains an integer longer than the interpreter’s configured maximum digit count, the default integer parser raises `ValueError` for an integer exceeding Python’s configured digit limit (on supported Python versions with that limit enabled). `save_pipeline` and `load_agent_config` do not catch this parse-time error, so callers receive an exception instead of a validation error or `None`.
Convert integer-conversion `ValueError` into `JsoncDecodeError`, or catch it at each consumer and return the documented parse failure.
Also at `maa_mcp/pipeline_tools.py:886`, `maa_mcp/agent_supervisor.py:129`.
</issue_to_address>
### Comment 5
<location path="tests/test_performance_stress.py" line_range="273" />
<code_context>
window_list = _call_tool(find_window_list)
- if window_list:
- self.window_name = window_list[0] # 使用第一个窗口
- self.controller_id = _call_tool(connect_window, self.window_name)
- print(
- f" 使用Windows窗口: {self.window_name}, 控制器ID: {self.controller_id}"
</code_context>
<issue_to_address>
**Duplicate titles select the wrong window**
When multiple desktop windows share the configured title, `connect_window` resolves the selected title through the name-keyed registry, where duplicate titles overwrite one another; the stress test can therefore send input to a different same-titled window than intended. The explicit environment setting does not disambiguate the target.
Skip or require an unambiguous target when the configured title occurs more than once, or select the window using a unique identifier such as its handle.
</issue_to_address>Linked issues
#38 是否考虑支持jsonc语法 · 1 of 1 met
| # | Requirement | Status | Notes |
|---|---|---|---|
| 1 | 支持包含注释的 Pipeline JSON,避免 load_pipeline 或 save_pipeline 因注释报错。 | ✅ Met | Pipeline 文件读取和 save_pipeline 的输入都改用 JSONC 解析器;run_pipeline 和 benchmark_node 也复用该文件读取路径。 |
| return { | ||
| _decode_surrogates(key, doc): _sanitize_surrogates(item, doc) |
There was a problem hiding this comment.
🟡 Medium · 重复键会静默覆盖值
当对象中以字面形式和转义代理对形式同时包含相同的补充 Unicode 键时,_make_pairs_hook 会在规范化之前比较键,因此二者都会被视为不同的键而通过检查;随后,_sanitize_surrogates 会将它们转换为相同的键,其字典推导式会静默丢弃其中一个值。重复键验证未能发现冲突,导致 Pipeline 或配置数据丢失一个节点或字段。
应先规范化键再检测重复项,并拒绝规范化后产生的任何冲突。
maa_mcp/jsonc.py:230-231 处也有此问题。
给 AI 代理的提示
在 `maa_mcp/jsonc.py` 第 298-299 行:
**重复键会静默覆盖值**
当对象中以字面形式和转义代理对形式同时包含相同的补充 Unicode 键时,`_make_pairs_hook` 会在规范化之前比较键,因此二者都会被视为不同的键而通过检查;随后,`_sanitize_surrogates` 会将它们转换为相同的键,其字典推导式会静默丢弃其中一个值。重复键验证未能发现冲突,导致 Pipeline 或配置数据丢失一个节点或字段。
应先规范化键再检测重复项,并拒绝规范化后产生的任何冲突。
`maa_mcp/jsonc.py:230-231` 处也有此问题。Original comment in English
🟡 Medium · Duplicate keys silently overwrite values
When an object contains the same supplementary Unicode key both literally and as an escaped surrogate pair, _make_pairs_hook compares the keys before normalization, so both pass as distinct; _sanitize_surrogates then converts them to the same key and its dict comprehension silently discards one value. Duplicate-key validation misses the collision, and pipeline or configuration data loses a node or field.
Normalize keys before duplicate detection and reject any collision created by normalization.
Also at maa_mcp/jsonc.py:230-231.
Prompt for AI agents
In `maa_mcp/jsonc.py` at lines 298-299:
**Duplicate keys silently overwrite values**
When an object contains the same supplementary Unicode key both literally and as an escaped surrogate pair, `_make_pairs_hook` compares the keys before normalization, so both pass as distinct; `_sanitize_surrogates` then converts them to the same key and its dict comprehension silently discards one value. Duplicate-key validation misses the collision, and pipeline or configuration data loses a node or field.
Normalize keys before duplicate detection and reject any collision created by normalization.
Also at `maa_mcp/jsonc.py:230-231`.| marker = text[i + 1] | ||
| if marker == "/": | ||
| # 行注释:到行尾为止(换行符本身保留) | ||
| end = text.find("\n", i) |
There was a problem hiding this comment.
🟡 Medium · 仅以 CR 结尾的注释会丢弃后续 JSON
当 JSONC 中的 // 注释以单独的回车符结束时,_strip_comments 只查找 \n,因此会将文档其余部分都视为注释文本并清空;随后 loads 会拒绝原本有效的 JSONC,导致 save_pipeline 等调用方无法接受或保存该内容。
应在 \r 或 \n 处结束行注释,同时保留行结束符。
给 AI 代理的提示
在 `maa_mcp/jsonc.py` 第 102 行:
**仅以 CR 结尾的注释会丢弃后续 JSON**
当 JSONC 中的 `//` 注释以单独的回车符结束时,`_strip_comments` 只查找 `\n`,因此会将文档其余部分都视为注释文本并清空;随后 `loads` 会拒绝原本有效的 JSONC,导致 `save_pipeline` 等调用方无法接受或保存该内容。
应在 `\r` 或 `\n` 处结束行注释,同时保留行结束符。Original comment in English
🟡 Medium · CR-only comments discard following JSON
When JSONC contains a // comment terminated by a bare carriage return, _strip_comments searches only for \n, so it treats the rest of the document as comment text and blanks it; loads then rejects otherwise valid JSONC, preventing callers such as save_pipeline from accepting or saving it.
End line comments at either \r or \n while preserving the line-ending character.
Prompt for AI agents
In `maa_mcp/jsonc.py` at line 102:
**CR-only comments discard following JSON**
When JSONC contains a `//` comment terminated by a bare carriage return, `_strip_comments` searches only for `\n`, so it treats the rest of the document as comment text and blanks it; `loads` then rejects otherwise valid JSONC, preventing callers such as `save_pipeline` from accepting or saving it.
End line comments at either `\r` or `\n` while preserving the line-ending character.| 注释内容替换为空格(块注释内的换行保留),返回文本与入参等长, | ||
| 行列号完全一致,后续步骤的报错位置可以直接沿用。 | ||
| """ | ||
| out = list(text) |
There was a problem hiding this comment.
🟡 Medium · 大型输入会耗尽内存
当 Pipeline 文件大到输入内容和中间字符列表超出可用内存时,每次预处理都会将输入展开为逐字符列表,再将其拼接成另一个完整字符串;_read_and_validate_pipelines 也会在解析前读取整个文件。大型但有效的 Pipeline 文件可能占用数倍于文件本身大小的内存,并在加载期间耗尽 MaaMCP 进程的内存。
应避免在预处理中复制逐字符列表,或在读取和解析 Pipeline 文件前设置并记录输入大小上限。
maa_mcp/jsonc.py:132 处也有此问题。
给 AI 代理的提示
在 `maa_mcp/jsonc.py` 第 76 行:
**大型输入会耗尽内存**
当 Pipeline 文件大到输入内容和中间字符列表超出可用内存时,每次预处理都会将输入展开为逐字符列表,再将其拼接成另一个完整字符串;`_read_and_validate_pipelines` 也会在解析前读取整个文件。大型但有效的 Pipeline 文件可能占用数倍于文件本身大小的内存,并在加载期间耗尽 MaaMCP 进程的内存。
应避免在预处理中复制逐字符列表,或在读取和解析 Pipeline 文件前设置并记录输入大小上限。
`maa_mcp/jsonc.py:132` 处也有此问题。Original comment in English
🟡 Medium · Large inputs exhaust memory
When a pipeline file is large enough that the input and intermediate character lists exceed available memory, each preprocessing pass expands the input into a per-character list and joins it into another full string; _read_and_validate_pipelines also reads the whole file before parsing. Large but valid pipeline files can consume many times their file size in memory and exhaust the MaaMCP process while loading.
Avoid per-character list copies during preprocessing or impose a documented input-size limit before reading and parsing pipeline files.
Also at maa_mcp/jsonc.py:132.
Prompt for AI agents
In `maa_mcp/jsonc.py` at line 76:
**Large inputs exhaust memory**
When a pipeline file is large enough that the input and intermediate character lists exceed available memory, each preprocessing pass expands the input into a per-character list and joins it into another full string; `_read_and_validate_pipelines` also reads the whole file before parsing. Large but valid pipeline files can consume many times their file size in memory and exhaust the MaaMCP process while loading.
Avoid per-character list copies during preprocessing or impose a documented input-size limit before reading and parsing pipeline files.
Also at `maa_mcp/jsonc.py:132`.| cleaned = _strip_trailing_commas(cleaned) | ||
|
|
||
| try: | ||
| parsed = json.loads( |
There was a problem hiding this comment.
🟡 Medium · 超大整数绕过验证
当 Pipeline 或接口文件包含的整数位数超过解释器配置的最大位数时,默认整数解析器会因整数超出 Python 配置的位数限制而引发 ValueError(在启用了该限制的受支持 Python 版本中)。save_pipeline 和 load_agent_config 不会捕获此解析阶段错误,因此调用方收到的是异常,而不是验证错误或 None。
应将整数转换时的 ValueError 转换为 JsoncDecodeError,或在各个调用处捕获该异常并返回文档规定的解析失败结果。
maa_mcp/pipeline_tools.py:886、maa_mcp/agent_supervisor.py:129 处也有此问题。
给 AI 代理的提示
在 `maa_mcp/jsonc.py` 第 331 行:
**超大整数绕过验证**
当 Pipeline 或接口文件包含的整数位数超过解释器配置的最大位数时,默认整数解析器会因整数超出 Python 配置的位数限制而引发 `ValueError`(在启用了该限制的受支持 Python 版本中)。`save_pipeline` 和 `load_agent_config` 不会捕获此解析阶段错误,因此调用方收到的是异常,而不是验证错误或 `None`。
应将整数转换时的 `ValueError` 转换为 `JsoncDecodeError`,或在各个调用处捕获该异常并返回文档规定的解析失败结果。
`maa_mcp/pipeline_tools.py:886`、`maa_mcp/agent_supervisor.py:129` 处也有此问题。Original comment in English
🟡 Medium · Huge integers escape validation
When a pipeline or interface file contains an integer longer than the interpreter’s configured maximum digit count, the default integer parser raises ValueError for an integer exceeding Python’s configured digit limit (on supported Python versions with that limit enabled). save_pipeline and load_agent_config do not catch this parse-time error, so callers receive an exception instead of a validation error or None.
Convert integer-conversion ValueError into JsoncDecodeError, or catch it at each consumer and return the documented parse failure.
Also at maa_mcp/pipeline_tools.py:886, maa_mcp/agent_supervisor.py:129.
Prompt for AI agents
In `maa_mcp/jsonc.py` at line 331:
**Huge integers escape validation**
When a pipeline or interface file contains an integer longer than the interpreter’s configured maximum digit count, the default integer parser raises `ValueError` for an integer exceeding Python’s configured digit limit (on supported Python versions with that limit enabled). `save_pipeline` and `load_agent_config` do not catch this parse-time error, so callers receive an exception instead of a validation error or `None`.
Convert integer-conversion `ValueError` into `JsoncDecodeError`, or catch it at each consumer and return the documented parse failure.
Also at `maa_mcp/pipeline_tools.py:886`, `maa_mcp/agent_supervisor.py:129`.| f"指定的窗口 {window_name!r} 不存在,可用窗口: {window_list}" | ||
| ) | ||
| self.window_name = window_name | ||
| self.controller_id = _call_tool(connect_window, self.window_name) |
There was a problem hiding this comment.
🟡 Medium · 标题重复会选错窗口
当多个桌面窗口具有相同的配置标题时,connect_window 会通过以名称为键的注册表解析所选标题,而重复的标题会相互覆盖;因此,压力测试可能会向另一个标题相同的窗口发送输入,而不是预期的目标窗口。显式环境设置无法消除目标歧义。
当配置标题出现多次时,应跳过该操作或要求目标明确无歧义;或者使用句柄等唯一标识符选择窗口。
给 AI 代理的提示
在 `tests/test_performance_stress.py` 第 273 行:
**标题重复会选错窗口**
当多个桌面窗口具有相同的配置标题时,`connect_window` 会通过以名称为键的注册表解析所选标题,而重复的标题会相互覆盖;因此,压力测试可能会向另一个标题相同的窗口发送输入,而不是预期的目标窗口。显式环境设置无法消除目标歧义。
当配置标题出现多次时,应跳过该操作或要求目标明确无歧义;或者使用句柄等唯一标识符选择窗口。Original comment in English
🟡 Medium · Duplicate titles select the wrong window
When multiple desktop windows share the configured title, connect_window resolves the selected title through the name-keyed registry, where duplicate titles overwrite one another; the stress test can therefore send input to a different same-titled window than intended. The explicit environment setting does not disambiguate the target.
Skip or require an unambiguous target when the configured title occurs more than once, or select the window using a unique identifier such as its handle.
Prompt for AI agents
In `tests/test_performance_stress.py` at line 273:
**Duplicate titles select the wrong window**
When multiple desktop windows share the configured title, `connect_window` resolves the selected title through the name-keyed registry, where duplicate titles overwrite one another; the stress test can therefore send input to a different same-titled window than intended. The explicit environment setting does not disambiguate the target.
Skip or require an unambiguous target when the configured title occurs more than once, or select the window using a unique identifier such as its handle.fix(jsonc): 处理 Sourcery 复查指出的超大整数、内存占用与同名窗口问题 Follow-up to #41,逐条实测 Sourcery 提出的 5 条意见:3 条是真问题并已修复, 另 2 条确认为误报(保留现状,理由记录在 PR 描述与代码注释里)。 - maa_mcp/jsonc.py:把 json C scanner 抛出的 ValueError(超大整数位数超限) 转成 JsoncDecodeError,避免它逃出 save_pipeline / load_agent_config 的 except 分支直接冲到 MCP 工具调用之外 - maa_mcp/jsonc.py:注释剥离与尾逗号剥离改为按片段拼接,不再逐字符复制整个 字符串,7.3 MB 输入的峰值内存从 66 MB 降到 14.7 MB - maa_mcp/jsonc.py:_sanitize_surrogates 补上归一化后键名撞车的显式检查 - tests/test_performance_stress.py:同名窗口直接跳过;连接后再核对一次实际 窗口 hwnd,覆盖查找与连接之间的 TOCTOU - tests/test_jsonc.py:超大整数用例改用 set_int_max_str_digits(640) + 700 位 字面量(原 20 低于解释器最小值 640),并补一条不依赖解释器版本的转换测试; 注释剥离的断言从「长度不变」加强为「内容被替换成空白且可被标准库解析」 - 验证:真 MaaFramework 32 例边界对照,不一致项仍只有已记录的 3 处有意偏离; 20000 例随机合法 JSON 与标准库差分 0 处不一致;pytest 330 passed / 16 skipped Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
背景
MaaFramework 内置的 pipeline 解析器基于 meojson 的
json::parsec,本身接受 JSONC(
//注释、/* */注释、尾逗号)。但 MaaMCP 此前用标准库json.loads读文件,导致同一份 pipeline 在 MaaFramework 能加载、在 MaaMCP 却报解析失败。
Closes #38
实现
新增
maa_mcp/jsonc.py(零新依赖),在 Python 侧复刻 meojsonjson::parsec的语义:(保留换行),再剥掉尾逗号,最后交给标准库
json.loads。JSONDecodeError自带的行列号可直接沿用。NaN/Infinity、溢出成inf的数字(1e999)、落单的代理字符。三个接入点:
load_pipeline/run_pipeline/benchmark_node读 pipeline 文件jsonc.loadssave_pipeline的pipeline_json输入串jsonc.loads;落盘仍是格式化后的标准 JSON(注释丢弃)agent_supervisor读interface.jsonjsonc.loads与 meojson 的三处有意偏离
loads接受任意顶层值,meojson 只接受对象/数组(调用方另行要求顶层为非空对象)。1e999被拒绝——meojson 按字面量保存能原样往返,MaaMCP 要把结果重新序列化成严格 JSON 再交给Resource.override_pipeline,写回去会是非法的Infinity字面量。skip_whitespace把「已到输入末尾」与「注释起始符非法」混为一谈,会静默丢弃尾部/x或未闭合的块注释,MaaFramework 继承了这一行为。本实现按标准库语义报错,方向是更严——不会接受 MaaFramework 拒绝的文件。未闭合注释出现在文档内部时两边都拒绝。验证
post_pipeline+Job.wait()+node_list作判据,不一致项全部落在上述 3 处偏离内,无新增偏差。tests/test_jsonc.py(51 例),全量pytest tests/ -q→ 326 passed / 15 skipped。附带修复
tests/test_performance_stress.py原先无 pytest marker,pytest tests/会直接跑,且
setup_class自动取find_window_list()[0]——在本机那是 VS Code 窗口,随后 20 轮
click/swipe/input_text会把编辑器关掉。现改为显式开启:需设置
MAA_STRESS=1,并通过MAA_STRESS_DEVICE/MAA_STRESS_WINDOW显式指定目标;未指定时跳过,绝不自动挑选窗口。
Sourcery 摘要
在整个流水线和代理配置处理中支持与 MaaFramework 兼容的 JSONC 输入,同时将真实设备压力测试明确设为选择性启用,并要求指定测试目标。
新功能:
save_pipeline输入和interface.json添加 JSONC 解析支持,包括注释和尾随逗号,同时保持与严格 JSON 的兼容性。错误修复:
改进:
文档:
测试:
Original summary in English
Summary by Sourcery
Support MaaFramework-compatible JSONC input throughout pipeline and agent configuration handling while making real-device stress testing explicitly opt-in and target-specific.
New Features:
save_pipelineinputs, andinterface.json, including comments and trailing commas while preserving strict JSON compatibility.Bug Fixes:
Enhancements:
Documentation:
Tests: