fix(desktop): 处理已存在其它 Hermes Agent 时桌面端无法启动飞书网关 (#168) - #26
Merged
Merged
Conversation
当 Windows 上已存在另一个 Hermes Agent(已安装的网关服务,或运行在 WSL/第二个安装中、通过共享 localhost 可达的实例)时,桌面端完成飞书扫码、 保存参数后点击「保存并启动飞书消息服务」会失败:真正(没)跑起来的网关并不 是桌面端托管的那个,用户停留在“参数已存但服务起不来”的状态。 根因(两种触发源,本质相同): - Windows 已装网关服务:`hermes gateway restart` 会优先 `gateway_windows.restart()` 重启那个外部服务网关,它跑在自己的 HERMES_HOME 下,永远看不到桌面端刚存的参数。 - WSL/第二安装:重复实例守卫按 HERMES_HOME 隔离看不到对方,冲突通过共享 localhost 暴露(飞书 webhook 8765 端口 EADDRINUSE 被吞成模糊错误)。 方案:检测 + 清晰提示 + 一键接管 - 桌面端 spawn dashboard 时注入 HERMES_DESKTOP_MANAGED=1,让 Python 侧能判定 “应由桌面端托管网关”。 - 桌面端发起的网关重启不再静默重启外部 Windows 服务:未带 force 时记录结构化冲突 (gateway_conflict_service)并拒绝;带 force(用户点击「强制接管」)时停掉外部服务/ 其它本机网关并以 --replace 跑桌面端托管网关。desktop-managed 的手动重启统一用 --replace。 - 飞书 connect 对 EADDRINUSE 分类为 gateway_conflict_port,并用 psutil 解析占用者 是否为本机可接管进程(WSL/跨 VM 不可接管)。 - 经 gateway_state.json -> /api/messaging/platforms 透出结构化 error_detail; 桌面消息页渲染冲突面板:「强制由桌面端接管并重启」(同机可点)/「查看如何停止其他实例」。 - POST /api/gateway/restart 新增 force 参数透传一次性 HERMES_GATEWAY_FORCE_TAKEOVER。 非 desktop-managed 行为完全不变(零回归);跨 VM(WSL)不做破坏性操作,仅给指引。 新增 tests/gateway/test_gateway_conflict.py 覆盖状态往返、端口冲突分类、EADDRINUSE 识别与重启分支三种路径。 Ref: Eynzof/Hermes-CN-Desktop#168 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🔎 Lint report:
|
| Rule | Count |
|---|---|
unsupported-operator |
1 |
unresolved-import |
1 |
not-subscriptable |
1 |
First entries
tests/gateway/test_gateway_conflict.py:63: [unsupported-operator] unsupported-operator: Operator `not in` is not supported between objects of type `Literal["gateway_conflict"]` and `dict[str, Any] | None`
tests/gateway/test_gateway_conflict.py:19: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_gateway_conflict.py:184: [not-subscriptable] not-subscriptable: Cannot subscript object of type `None` with no `__getitem__` method
✅ Fixed issues: none
Unchanged: 5135 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
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.
背景
Ref: Eynzof/Hermes-CN-Desktop#168
较多 Windows 用户反馈:以前在 Windows 上装过 Hermes Agent 的情况下,用中文社区桌面版完成飞书扫码、保存参数后点击「保存并启动飞书消息服务」,网关拒绝启动,提示类似“当前 dashboard 不是桌面端托管进程”。用户停留在“参数已存但飞书消息服务起不来”的状态。
根因(已在代码中核实,两种触发源、本质相同)
桌面端(Electron)自己拉起并托管一个 dashboard,点击保存后调用
POST /api/gateway/restart→ spawnhermes gateway restart。失败的本质都是「真正(没)跑起来的网关不是桌面端托管的那个」:hermes gateway restart在 Windows 会优先gateway_windows.restart()(hermes_cli/gateway.py),去重启那个外部服务网关——它跑在自己的HERMES_HOME下,永远看不到桌面端刚保存的飞书参数。桌面端从不安装 Windows 网关服务,所以“已安装服务”对桌面端即外部。HERMES_HOME隔离,跨 Windows↔WSL 看不到对方;冲突通过共享 localhost 暴露(WSL2 转发 127.0.0.1),飞书 webhook 端口8765被占 → 被吞成模糊的"Feishu startup failed: …"。方案:检测 + 清晰提示 + 一键接管(混合)
HERMES_DESKTOP_MANAGED=1,让 Python 侧能判定“应由桌面端托管网关”。force→ 记录结构化冲突gateway_conflict_service并拒绝(exit 2);带force(用户点「强制接管」)→ 停掉外部服务 / 其它本机网关并以--replace跑桌面端托管网关。desktop-managed 的手动重启统一--replace。connect对EADDRINUSE(含 WindowsWSAEADDRINUSE)分类为gateway_conflict_port,用 psutil 解析占用者是否为本机可接管进程(WSL/跨 VM → 不可接管)。gateway_state.json→/api/messaging/platforms透出结构化error_detail;桌面消息页渲染冲突面板:「强制由桌面端接管并重启」(同机可点)/ 「查看如何停止其他实例」(WSL 等不可接管时给指引)。POST /api/gateway/restart新增force参数,透传一次性HERMES_GATEWAY_FORCE_TAKEOVER。改动文件
Python
gateway/status.py—write_runtime_status增加error_detail;新增set_gateway_conflict、classify_port_conflict。gateway/platforms/base.py—_set_fatal_error支持error_detail。gateway/platforms/feishu.py—_classify_address_in_use,端口冲突归类。hermes_cli/gateway.py— restart 的 desktop-managed 分支 + 强制接管 + 冲突标记/清除。hermes_cli/web_server.py—/api/gateway/restart?force=1;payload 透出error_detail与网关级冲突。Desktop
apps/desktop/electron/main.cjs— 两处 spawn 注入HERMES_DESKTOP_MANAGED=1。apps/desktop/src/hermes.ts—restartGateway(force)。apps/desktop/src/types/hermes.ts—MessagingConflictDetail类型 +error_detail。apps/desktop/src/app/messaging/index.tsx— 冲突面板与一键接管。apps/desktop/src/i18n/{types,en,zh}.ts— 新增文案。测试 / 文档
tests/gateway/test_gateway_conflict.py— 状态往返、端口冲突分类、EADDRINUSE 识别、重启分支三路径。docs/issue-168-gateway-conflict-plan.md— 根因与设计说明。验证
py_compile通过。unittest.mock在本地 runtime 验证了重启分支 4 个场景:外部服务拒绝(exit 2)+记录冲突 / 强制接管(停服务+run_gateway(replace=True)) / 非 desktop-managed 维持原行为 / desktop-managed 无服务正常--replace启动并清除陈旧冲突。node_modules)未跑tsc/构建;已做人工类型核对(类型/导入/Button 变体/i18n key 齐全)。建议 CI 跑一遍type-check与单测。范围与边界
🤖 Generated with Claude Code