fix(ws): WebSocket 加固清单里的两处输入容错与事件构造 - #837
Open
beichen24a1 wants to merge 12 commits into
Open
beichen24a1 wants to merge 12 commits into
beichen24a1 wants to merge 12 commits into
Conversation
同一套「按 ConfigPathMode 在两个目录间拷贝」的逻辑在通用脚本里写了 4 份,只有 ScriptConfig.set_general 做了存在性判断,另外三处源路径不存在时直接抛异常: - AutoProxy.set_general(下发):新建用户后没跑过「脚本设置」,data/<脚本>/<用户>/ ConfigFile 还不存在,自动代理一启动就崩、任务判成异常(Sentry AUTO-MAS-BACKEND-4Q)。 ScriptConfig.set_general 在同样情况下会跳过拷贝、让脚本用自己的配置 —— 两条路径 对同一件事行为不一致,有判断的那份是预期行为。 - AutoProxy.update_config(回写):脚本配置路径不存在时抛异常;File 分支还缺目标目录, copy 不像 copytree 会自建目录,会再抛一次。 - ScriptConfig.final_task(回写):先 rmtree 掉 MAS 侧副本再拷,源不存在时异常发生在 清空之后,用户会以为配置丢了(4Y / 4W)。 三处统一成「先判源存在,不存在就跳过并给一条可读 warning」,与 ScriptConfig.set_general 同口径。 验证: - 三处源不存在路径的前后走查:修复前分别是 swap_in_dir 的 RuntimeError 与 shutil 的 FileNotFoundError,修复后都是 warning + 跳过 - python -m pytest tests/task -q:804 passed / 17 failed,与改前基线逐条一致(既有失败) - python -m pytest tests --collect-only -q:1406 collected,exit 0 - ruff check / ruff format --check:通过
copyToRoot() 对每个条目先 rmSync 掉 appRoot 下的目标、再逐文件 copyFileSync。删完之后、 复制完成之前的任何中断(进程被杀、断电、单个文件撞杀软或占用)都会留下残缺的 app/, 缺哪些模块取决于复制停在哪一步:落在 lifespan 里的让后端完全起不来,落在 initialize_background_services 里的静默失效(Sentry AUTO-MAS-BACKEND-56 / 51)。 改为 replaceItem():先整体复制到 <目标>.new,再改名换入,成功后删掉旧副本;换入失败把 旧目标改名回去 —— 宁可停在旧版本,也不让目标处于缺失或半新半旧的状态。 顺带把 .git 挪到复制列表最后:它是「当前部署的是哪个版本」的依据,先复制会让中断后的 残缺源码看起来已经是最新版,标题栏不再提示更新,用户也就失去重新拉取代码的入口。 验证: - 新增 4 个 vitest 用例(真实临时目录):整目录替换后无 .new/.old 残留、目标不存在时 自建(顺带暴露并修掉 copyFileSync 不自建父目录的边界)、清掉上次中断残留、源不存在 时抛错且目标原样保留 - yarn vitest run electron/services/repositoryService.test.ts:4 passed - yarn tsc -p tsconfig.electron.json --noEmit:通过 - yarn lint:0 errors
list_js_scripts() 逐个读取 {RootPath}/User/JsScript/*/manifest.json 取显示名,任意一个
解析失败都会让函数抛出、接口 500(Sentry AUTO-MAS-BACKEND-9B 的现场是一个尾逗号),
一条龙表单的「JS 脚本」候选列表整体拿不到 —— 而那个坏文件只属于某一个脚本。
manifest 是玩家从社区订阅或手工放置的第三方文件,格式未必严格。解析失败改为记一条
warning、显示名退回目录名、继续列下一个;函数本来就有 display = folder 的兜底,只是
没兜住异常。
验证:
- 新增 4 个纯逻辑用例:坏 manifest 只影响自己(用线上那条事件的尾逗号形态)、manifest
缺 name、目录没有 manifest、JS 脚本目录不存在
- python -m pytest tests/task/test_bettergi_js_script_manifest.py -q:4 passed
- python -m pytest tests --collect-only -q:exit 0
- ruff check:通过
initializationHandlers.ts 里 12 处 event.sender.send(...) 都没有守卫。这些回调活得比 一次 IPC 调用长:窗口关闭、渲染进程被 kill 之后,Runtime 子进程还在往 stdout 吐进度 (Sentry AUTO-MAS-DESKTOP-16),后端子进程随后仍会退出并触发状态回调 (AUTO-MAS-DESKTOP-11),拿着已销毁的 WebContents 去 send 就是主进程的未捕获异常 —— 抛在 Node 的 exit 回调里,没有任何 try/catch 兜住,level 直接是 fatal。 统一走新的 sendToRenderer():sender 已销毁就跳过。main.ts 里一直有这道守卫(12 处), 这个文件是漏的。 验证: - yarn tsc -p tsconfig.electron.json --noEmit:通过 - yarn lint:0 errors - 12 处调用点全部改到统一出口,文件内不再有裸 event.sender.send
雷电的 dnconsole.exe 崩溃时返回码是 3221225480,而 stdout / stderr 都是空的,原来 只回 stdout 的写法让界面上只剩「命令执行失败: 」加一个空串,用户分不清是路径配置错、 实例不存在,还是模拟器自身挂了。 给 ProcessResult 加 failure_detail()(returncode + repr 过的 stdout/stderr),ldplayer 与 mumu 里 8 处 raise RuntimeError(f"命令执行失败: ...") 统一改用它。 验证: - 新增 2 个纯逻辑用例:线上那条事件的形态(返回码非零、两个输出都为空)与多行输出 - python -m pytest tests/tools/test_process_result_failure_detail.py -q:2 passed - ruff check / ruff format --check:通过
dependencyService 只在「Installing collected packages:」与「Successfully installed」两个 节点推进进度:下载阶段解析出的 Collecting 只累加 totalPackages,从不推进 installedPackages。而下载恰恰是耗时最长的一段 —— 非大陆用户实测初始化 7 分 36 秒里有 7 分 27 秒停在 40%,体感是彻底卡死(issue AUTO-MAS-Project#499)。 pip 在这个阶段并不需要知道总包数,所以给出 estimateDownloadProgress():按已见到的包数 渐近推进(40% + 30% × (1 - e^(-n/25))),越接近 70% 越慢、封顶 70%,把 70-80 留给「开始 安装」那一刻的跳动。 验证: - 新增 4 个 vitest 用例:起点 40%、单调不减、封顶 70% 且能到 69% 以上、见到 20 个包时 已经过半 - yarn vitest run electron/services/dependencyService.test.ts:4 passed
initialize_background_services() 把 MCP 挂载、历史清理、适配器导入、显示输出守卫、通知 管理器、Koishi 全放在一个 try 里,前面任何一步抛异常就整体结束,排在其后的 MainTimer.start() 一起没起 —— 界面一切正常、手动运行队列也正常,定时任务却整夜不触发; /api/core/health 虽然回了 backgroundStatus: failed 与 backgroundError,但前端不消费这 两个字段,用户看不到任何提示(issue AUTO-MAS-Project#738)。 改成 _optional_step() 逐个包住:失败只记一条 exception 日志、继续下一步。主定时器仍排在 显示输出守卫之后(原有顺序要求不变),它自己的失败依旧算整体失败。各步骤之间的先后顺序 没有调整。 验证: - 真机启动(把 MainTimer.start() 临时换成一行日志,避免触发用户的计划任务)并在「后台 清理」里注入 RuntimeError:日志依序出现「MCP 服务已挂载」→「后台清理失败, 已跳过: 模拟可选步骤失败」(带完整 traceback)→「主业务定时器启动」→「后端后台初始化完成」, 即可选步骤失败后流程照常走到定时器;修复前同样的注入会让这一步永远不执行 - python -m py_compile main.py:通过 - python -m pytest tests --collect-only -q:1412 collected,exit 0 - ruff format --check main.py:通过(ruff check 报的 7 处 import 排序 / E402 是既有问题, 不在本次改动范围)
来自 AUTO-MAS-Project#443 清单中落点最明确的两条: - app/api/ws_command.py:`params` 不是对象时 `**(params or {})` 直接抛 TypeError,只捕 ValidationError 的写法把它漏到外层变成 500(dev 基线是 400)。改为同时捕 TypeError。 - app/services/update.py:三处 UPDATE_FAILED 用裸 dict 构造事件,绕过 WSUpdateFailedData 模型。字段名本来是对的,但模型是这类事件的唯一契约,统一走它更稳。 清单里其余条目(stop-ALL 等待期间新任务挂死、启动窗口内的连接顶替、倒计时归零后电源 操作静默放弃、「✕ 与托盘退出不武装兜底」、5 个用户编辑页缺 onUnmounted、二进制帧断开 主连接等)要么涉及调度语义、要么涉及生命周期设计,没有一并动。 验证: - python -m py_compile:通过 - python -m pytest tests/services -q:66 passed - python -m pytest tests --collect-only -q:1412 collected,exit 0 - ruff check / ruff format --check:通过
There was a problem hiding this comment.
Sorry @beichen24a1, you've used your own review budget of 250,000 diff characters for the last 7 days.
You can request another review in 3 days and 22 hours by commenting @sourcery-ai review. Upgrade to get a review now.
审查者指南本 PR 集中修复多类边界容错与可靠性问题:隔离后台初始化失败、增强部署替换和 IPC 生命周期安全、改善依赖与模拟器错误反馈,并补强脚本清单、用户配置、WebSocket 参数及更新事件的数据契约;同时新增针对关键行为的 Python/TypeScript 测试和变更日志。 受保护的渲染进程事件传递时序图sequenceDiagram
participant Renderer as Renderer
participant Main as ElectronMain
participant Runtime as RuntimeOrBackend
Runtime->>Main: progress callback
Main->>Main: WebContents.isDestroyed()
alt renderer still exists
Main->>Renderer: WebContents.send(channel, payload)
else renderer destroyed
Main-->>Runtime: omit event
end
容错 WebSocket 命令验证时序图sequenceDiagram
participant Client as WebSocketClient
participant API as execute_ws_command
participant Model as params_model
Client->>API: execute_ws_command(endpoint, params)
API->>Model: params_model(**(params or {}))
alt valid object parameters
Model-->>API: param_instance
API-->>Client: command result
else TypeError or ValidationError
Model-->>API: validation exception
API-->>Client: success=false, 参数错误, HTTP 400
end
故障隔离后台初始化流程图flowchart TD
Start[后台初始化] --> MCP[_mount_mcp]
MCP --> Cleanup[_cleanup]
Cleanup --> Arknight[_init_arknight]
Arknight --> Guard[DesktopGuard.start]
Guard --> Timer[MainTimer.start]
Timer --> Claw[_start_openclaw]
Claw --> Koishi[_init_koishi]
Koishi --> Done[初始化完成]
MCP -.失败.-> Continue1[记录日志并继续]
Cleanup -.失败.-> Continue2[记录日志并继续]
Arknight -.失败.-> Continue3[记录日志并继续]
Guard -.失败.-> Continue4[记录日志并继续]
Claw -.失败.-> Continue5[记录日志并继续]
Koishi -.失败.-> Continue6[记录日志并继续]
Continue1 --> Cleanup
Continue2 --> Arknight
Continue3 --> Guard
Continue4 --> Timer
Continue5 --> Koishi
Continue6 --> Done
原子部署替换流程图flowchart TD
Start[replaceItem] --> Cleanup[清理目标.new和目标.old]
Cleanup --> Copy[复制源到目标.new]
Copy --> RenameOld{目标存在}
RenameOld -->|是| Backup[目标改名为目标.old]
RenameOld -->|否| Swap[目标.new改名为目标]
Backup --> Swap
Swap --> Remove[清理目标.old]
Remove --> Done[替换完成]
Copy -.失败.-> Preserve[保留旧目标并清理目标.new]
Swap -.失败.-> Restore[恢复目标.old]
Restore --> Preserve
文件级变更
可能相关的问题
提示和命令与 Sourcery 交互
自定义体验访问你的控制面板以:
获取帮助Original review guide in EnglishReviewer's Guide本 PR 集中修复多类边界容错与可靠性问题:隔离后台初始化失败、增强部署替换和 IPC 生命周期安全、改善依赖与模拟器错误反馈,并补强脚本清单、用户配置、WebSocket 参数及更新事件的数据契约;同时新增针对关键行为的 Python/TypeScript 测试和变更日志。 Sequence diagram for guarded renderer event deliverysequenceDiagram
participant Renderer as Renderer
participant Main as ElectronMain
participant Runtime as RuntimeOrBackend
Runtime->>Main: progress callback
Main->>Main: WebContents.isDestroyed()
alt renderer still exists
Main->>Renderer: WebContents.send(channel, payload)
else renderer destroyed
Main-->>Runtime: omit event
end
Sequence diagram for tolerant WebSocket command validationsequenceDiagram
participant Client as WebSocketClient
participant API as execute_ws_command
participant Model as params_model
Client->>API: execute_ws_command(endpoint, params)
API->>Model: params_model(**(params or {}))
alt valid object parameters
Model-->>API: param_instance
API-->>Client: command result
else TypeError or ValidationError
Model-->>API: validation exception
API-->>Client: success=false, 参数错误, HTTP 400
end
Flow diagram for fault-isolated backend initializationflowchart TD
Start[后台初始化] --> MCP[_mount_mcp]
MCP --> Cleanup[_cleanup]
Cleanup --> Arknight[_init_arknight]
Arknight --> Guard[DesktopGuard.start]
Guard --> Timer[MainTimer.start]
Timer --> Claw[_start_openclaw]
Claw --> Koishi[_init_koishi]
Koishi --> Done[初始化完成]
MCP -.失败.-> Continue1[记录日志并继续]
Cleanup -.失败.-> Continue2[记录日志并继续]
Arknight -.失败.-> Continue3[记录日志并继续]
Guard -.失败.-> Continue4[记录日志并继续]
Claw -.失败.-> Continue5[记录日志并继续]
Koishi -.失败.-> Continue6[记录日志并继续]
Continue1 --> Cleanup
Continue2 --> Arknight
Continue3 --> Guard
Continue4 --> Timer
Continue5 --> Koishi
Continue6 --> Done
Flow diagram for atomic deployment replacementflowchart TD
Start[replaceItem] --> Cleanup[清理目标.new和目标.old]
Cleanup --> Copy[复制源到目标.new]
Copy --> RenameOld{目标存在}
RenameOld -->|是| Backup[目标改名为目标.old]
RenameOld -->|否| Swap[目标.new改名为目标]
Backup --> Swap
Swap --> Remove[清理目标.old]
Remove --> Done[替换完成]
Copy -.失败.-> Preserve[保留旧目标并清理目标.new]
Swap -.失败.-> Restore[恢复目标.old]
Restore --> Preserve
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
Closes #443(清单中落点最明确的两条)#443是一份 17 条的加固清单,这里挑出边界最清晰、风险最低的两条,其余留给维护者按优先级排。app/api/ws_command.py:params不是对象(列表、字符串)时**(params or {})直接抛TypeError,只捕ValidationError的写法把它漏到外层变成 500——而 dev 基线对外是 400。改为同时捕TypeError,仍按参数错误回 400。app/services/update.py:三处UPDATE_FAILED用裸 dict 构造事件,绕过WSUpdateFailedData模型。字段名本来是对的,但模型是这类事件的唯一契约,统一走它更稳。没动的部分(都在 #443 里,按优先级留给维护者)
✕与托盘「退出」不武装 25s 兜底;relaunchAfterQuit置位后永不复位;断线状态下点关闭空等 30–35 秒;5 个用户编辑页配置会话订阅无卸载释放 —— 都涉及生命周期设计,改动面比本次大一个量级。验证
python -m py_compile app/services/update.py app/api/ws_command.py:通过python -m pytest tests/services -q:66 passedpython -m pytest tests --collect-only -q:1412 collected,exit 0ruff check/ruff format --check:通过Sourcery 总结
增强 WebSocket 处理、应用启动、配置流程、部署更新和模拟器诊断功能,使其能够抵御格式错误的输入、生命周期竞态和部分失败。
错误修复:
增强功能:
部署:
测试:
Original summary in English
Summary by Sourcery
Harden WebSocket handling, application startup, configuration workflows, deployment updates, and emulator diagnostics against malformed input, lifecycle races, and partial failures.
Bug Fixes:
Enhancements:
Deployment:
Tests:
Original summary in English
Original summary in English