25 KiB
name, description, version
| name | description | version |
|---|---|---|
| atomk-three-layer-arch | AtomK三层架构 (Server/Bridge/Desktop) — 鉴权、注册、心跳、断线切换 | 1.0.0 |
AtomK 三层架构 (Server / Bridge / Desktop)
架构概览
Desktop ──登录──→ Server (拿JWT + bridge列表+key)
Desktop ──WS连接──→ Bridge (用Server推下来的key认证)
Bridge ──注册───→ Server (X-API-Key = 共享admin密钥,首次→pending→admin审批→online)
Bridge ──心跳───→ Server (X-Bridge-Key = bridge自身key)
Server ──心跳响应──→ Bridge (下发指令: update_key/drain/kick)
Desktop ──断线──→ 自动切下一个Bridge(或重试)
三层职责
| 层 | 项目 | 域名/路径 | 职责 |
|---|---|---|---|
| Server | Atomlisting_Server | atomlisting.com (1主+1~2 fallback) | 鉴权中心、Bridge注册表、用户管理 |
| Bridge | atomk-page-bridge | VPS上运行,可多实例按region部署 | CDP代理转发、Desktop连接管理 |
| Desktop | AtomK-Desktop | /home/ubuntu/hermes-desktop/ | Electron桌面应用,用户入口 |
安全模型 (v4.0)
- Server = 唯一鉴权中心,签发JWT、分配key、管理bridge注册
- Bridge → Server 注册 =
X-API-Keyheader(共享密钥,匹配 API_KEY_ADMIN 或 BRIDGE_SECRET 或默认值bingo301Tt23456Admin!) - Bridge → Server 心跳 =
X-Bridge-Keyheader(bridge自身的key,注册时获得或自带的--key) - Desktop → Bridge = Server 分配的
key(WS auth)或 Server 签发的JWT(HTTP Bearer) - Bridge = 不存用户数据,只做CDP代理转发
Bridge 生命周期 (v4.0 审批流)
- 启动注册: Bridge 带
X-API-Key调用/register→ Server 创建记录,status=pending - 等待审批: Admin 通过
PUT /bridges/{id}设置status=online审批通过 - 心跳运行: Bridge 定时带
X-Bridge-Key调用/heartbeat上报连接数 - 下线: Admin 设
is_active=false或 Bridge 停机超时 →status=offline - 重新注册: 同一 key 再次
/register→ 自动status=online(无需再审批)
⚠️ Desktop
GET /bridges只返回status=online的 bridge,pending/offline 不可见
Server 端 API
| 端点 | 方向 | 认证 | 说明 |
|---|---|---|---|
POST /api/v1/auth/login |
Desktop → Server | 账号密码 | 登录获取JWT |
POST /api/v1/bridges/register |
Bridge → Server | X-API-Key | 启动时注册,返回 key + jwt_secret,首次 status=pending |
POST /api/v1/bridges/heartbeat |
Bridge → Server | X-Bridge-Key | 心跳上报,响应含 commands + status |
GET /api/v1/bridges |
Desktop → Server | JWT | 获取bridge列表+key |
POST /api/v1/bridges/approve |
Admin → Server | X-API-Key (admin级) | 批准pending bridge,分配用户 |
POST /api/v1/bridges/reject |
Admin → Server | X-API-Key (admin级) | 拒绝/删除pending bridge |
PUT /api/v1/bridges/{bridge_id} |
Admin → Server | JWT(admin) | 更新bridge设置(旧端点,与/approve重复) |
DELETE /api/v1/bridges/{bridge_id} |
Admin → Server | JWT(admin) | 停用bridge |
关键代码文件
Server (Atomlisting_Server)
backend/app/models/bridge.py— Bridge数据库模型backend/app/schemas/bridge.py— Pydantic schemas (含 BridgeCommand),路由文件必须导入此文件而非内联backend/app/api/v1/bridges.py— Bridge API路由backend/app/config.py— BRIDGE_SECRET、API_KEY_READ/WRITE/ADMIN 配置项(全部从环境变量读取,默认空串)backend/app/core/security.py— X-API-Key 校验逻辑,空串 key 会导致""成为一个有效 entrybackend/app/api/v1/auth.py— 登录 endpoint(⚠️ last_used_bridge_id 从 remote_user 读取而非本地 DB,已知 bug)backend/app/models/user.py— User 模型(⚠️ last_used_bridge_id 缺 Alembic 迁移)backend/alembic/versions/add_bridges_table.py— Bridges 迁移- Deploy: bt109atomk.sh3.ikuai7.com, frontend:8091, backend:8092, MCP:8093
- Start scripts:
/root/AtomK_Operation_Tools/start_backend.sh,start_frontend.sh
Bridge (atomk-page-bridge)
cloud-bridge/server.py— 主服务,含注册/心跳/JWT/commands处理- 启动参数:
--server-url,--bridge-secret,--bridge-name,--bridge-region,--key,--jwt-secret(可选,优先从Server获取)
Desktop (hermes-desktop)
src/main/operation-api.ts— fetchBridges() 函数src/main/chrome-bridge.ts— setBridgeList(), tryNextBridge(), 断线自动切换逻辑src/main/index.ts— IPC handlers (operation:fetch-bridges, chrome-bridge:set-bridge-list, chrome-bridge:try-next-bridge)src/preload/index.ts— chromeBridgeSetBridgeList(), chromeBridgeTryNext()src/renderer/src/screens/ChromeBridge/ChromeBridge.tsx— Bridge选择器UI
Bridge v4.0 部署流程
Bridge 是云端服务器组件,部署在独立的 VPS 上(按地域可多实例),不在 Server (atomlisting.com) 上运行。Desktop 通过 WebSocket 远程连 Bridge,Bridge 向 Server 注册。
新建部署步骤
# 1. 克隆
cd /root
GIT_SSL_NO_VERIFY=1 git clone https://gitea9webs.sh3.ikuai7.com/admin9webs/atomk-page-bridge.git
cd atomk-page-bridge/cloud-bridge
# 2. 启动(首次)
python3 server.py \\
--key "<随机生成的bridge自身key>" \\
--api-key "bingo301Tt23456Admin!" \\
--server-url "https://www.atomlisting.com" \\
--bridge-name "US-West-1" \\
--bridge-region "us" \\
--ping-timeout 60
# 3. 生产环境用 systemd
sudo cp templates/atomk-bridge.service /etc/systemd/system/
sudo vim /etc/atomk-bridge.env # 填入 ATOMK_BRIDGE_KEY, ATOMK_BRIDGE_NAME 等
sudo systemctl daemon-reload
sudo systemctl enable --now atomk-bridge.service
# 4. 验证
curl -s http://127.0.0.1:9228/cloud-bridge/health -H "Authorization: Bearer <bridge-key>"
# → {"status":"ok","version":"4.0.0",...}
注册 → 审批 → 心跳流程
Bridge 启动 → POST /api/v1/bridges/register (X-API-Key) → status=pending
管理员登录 atomlisting.com → /bridges 页面 → 批准 + 分配用户
Bridge → POST /api/v1/bridges/heartbeat (X-Bridge-Key) 每30s
Desktop → GET /api/v1/bridges/ (JWT) → 看到自己的 bridge 列表
⚠️ 新 Bridge 必须等 admin 批准后 Desktop 才能看到;也可通过 API 批准:
POST /api/v1/bridges/approve {"bridge_id": <int>, "user_id": <int>}+X-API-Key(admin级别)
启动参数说明
| 参数 | 说明 | 默认值 |
|---|---|---|
--key |
Bridge 自身的桌面认证密钥(随机生成) | 无(必需) |
--api-key |
向 Server 注册的共享密钥 | bingo301Tt23456Admin! |
--server-url |
AtomListing Server URL | https://www.atomlisting.com |
--bridge-name |
显示名称 | Bridge-<hash前6位> |
--bridge-region |
地域标签 | 空 |
--proxy-port |
统一端口 | 9228 |
--ping-timeout |
WS ping timeout (秒), 跨网建议60 | 30 |
--api-key设计意图 = Server 的API_KEY_WRITE(注册只需 write 权限)。 旧参数--bridge-secret已废弃,等价于--api-key。
Heartbeat Commands
Server 通过心跳响应下发指令:
| Command | 说明 | params |
|---|---|---|
update_key |
更换bridge认证key | { key: "new-xxx" } |
drain |
标记draining,不再接受新Desktop连接 | {} |
kick |
踢掉指定Desktop连接 | { desktop_id: "dsk_xxx" } |
set_status |
强制修改bridge状态 | { status: "offline" } |
Desktop 断线逻辑
- 多bridge时: 自动切换列表中下一个bridge(wrap around)
- 单bridge时: 指数退避重试(1s → 2s → 4s → 8s → 15s cap)
- 全部失败后才提示用户
WS 协议版本差异(关键兼容性)
项目中存在三个 Bridge server 实现,WS 协议互不兼容:
| 维度 | relay.js (v1) | atomk-bridge (v3) | cloud-bridge (v4) |
|---|---|---|---|
| WS 认证 | 无认证,extension_connect/desktop_connect 消息标识身份 |
无 WS 认证,直接发 register |
5秒内必须发 auth 消息(含 key 字段),否则 code 4001/4003 踢出 |
| 注册消息 | type: "extension_connect" |
type: "register" + info: {} |
type: "auth" → type: "registered" + slot_id,然后可选 type: "register" 更新元数据 |
| CDP 请求格式 | cdp_command/cdp_response + requestId |
request/response + request_id |
同 v3 |
| 心跳 | 无 | app-level ping/pong JSON |
多层:app ping/pong + TCP keepalive + HTTP POST 心跳 |
| Desktop WS 客户端 | 无(Desktop 只 HTTP poll relay) | 需要 WS 客户端 | 需要带 auth 的 WS 客户端 |
Desktop 兼容策略:先发 auth 消息(含 key + desktop_id),再发 register 消息。v3 和 relay 忽略未知 type,v4 依赖 auth 完成认证。这样 Desktop 代码一套逻辑兼容所有版本。
⚠️ 关键修复(2026-05):Desktop connectCloudBridge() 原来只发 register 不发 auth,连 cloud-bridge v4 会被 5 秒超时踢掉(code 4003)。必须在 WS open handler 中先发 auth。
BridgeInfo Schema 对齐(Desktop ↔ Server)
Server BridgeResponse 字段 |
Desktop BridgeInfo 旧字段 |
状态 |
|---|---|---|
id: int |
id: number |
✅ |
user_id: Optional[int] |
❌ 缺失 | 需添加 |
name: str |
name: string |
✅ |
host: str |
host: string |
✅ |
port: int |
port: number |
✅ |
key: str |
key: string |
✅ |
status: str ("online"/"offline"/"pending"/"draining") |
status: string ("online"/"offline"/"draining") |
需加 "pending" |
region: Optional[str] |
region?: string |
✅ |
last_heartbeat: Optional[str] |
last_heartbeat?: string |
✅ |
created_at: Optional[str] |
❌ 缺失 | 需添加 |
| ❌ 未暴露 | bridge_id: string |
需删除(Server 未暴露) |
| ❌ 未暴露 | active_connections: number |
需删除(Server 未暴露) |
| ❌ 未暴露 | max_connections: number |
需删除(Server 未暴露) |
⚠️ Desktop 需同步修改的文件:
operation-api.ts(类型定义)、preload/index.d.ts(类型声明)、ChromeBridge.tsx(UI 渲染)
Server 下发 Commands 处理
Server 通过心跳响应下发管理命令,Desktop 必须处理:
| Command | 说明 | Desktop 处理 |
|---|---|---|
update_key |
更换 bridge 认证 key | 更新本地 cloudBridgeConfig.apiKey |
drain |
停止接受新请求,优雅断连 | 设 intentionalClose=true,5s 后断开(不重连) |
kick |
立即踢出 | 设 intentionalClose=true,立即断开 |
set_status |
强制修改上报状态 | 记录日志,下次心跳上报此状态 |
命令格式(通过 WS type: "command" 消息传递):
{"type": "command", "action": "update_key", "params": {"key": "new-xxx"}}
或嵌套格式:
{"type": "command", "command": {"action": "drain", "params": {}}}
⚠️ Desktop 原来完全忽略这些命令,需要在 ws.on('message') handler 中添加 msg.type === 'command' 分支。
Production Bridges (as of 2026-05-29)
| ID | Name | Host | Port | Region | Version | Location | Status |
|---|---|---|---|---|---|---|---|
| 9 | SG3-Server | 43.134.190.229 | 9228 | — | v4.0? | Remote VPS | online ✅ |
| 10 | US-West-1 | 43.160.244.93 | 9228 | us | v4.0? | Remote VPS | online ✅ |
| — | Bridge-CN-1 | 49.51.249.171 (public) / 127.0.0.1 (local) | 9228 | cn | v4.4.1 | This machine (/home/ubuntu/atomk-page-bridge/cloud-bridge/) |
online ✅ |
Bridge-CN-1 Local Details
Bridge-CN-1 runs on THIS machine, not a remote VPS:
- Process: PID varies,
/usr/bin/python3 server.py --key *** --proxy-port 9228 --ws-port 9229 --bridge-name Bridge-CN-1 --cdp-timeout 30 --proxy-timeout 60 --ping-timeout 60 --hermes-api-key *** - Working dir:
/home/ubuntu/atomk-page-bridge/cloud-bridge/ - Git remote:
origin→gitea9webs.sh3.ikuai7.com/admin9webs/atomk-page-bridge.git - Code:
server.py= v4.4.1 (current, multi-slot),server_multi.py= v3.1 (legacy, not in use) - Health:
GET http://127.0.0.1:9228/healthwithAuthorization: Bearer ***→{"status":"ok","version":"4.4.1","bridge_protocol":"1.1","connected_slots":1,...} - Models:
GET http://127.0.0.1:9228/v1/models→{"data": [{"id": "hermes-agent"}]} - Chat proxy:
POST http://127.0.0.1:9228/v1/chat/completions→ proxies to local Hermes agent (confirmed working) - No hardcoded single-connection lock: v4.0 uses dynamic
DesktopSlotarchitecture with noMAX_DESKTOPSlimit - Restart: Kill the process and re-run the same command, or use systemd if configured
🔑 Approve via API:
POST /api/v1/bridges/approve {"bridge_id": <int>, "user_id": <int>}+X-API-Key(admin). bridge_id is the DB auto-increment ID, not the name. Invalid user_id → 500.
Pitfalls
- Header 不匹配: Server 和 Bridge 必须用相同的 header 名(注册用
X-API-Key,心跳用X-Bridge-Key)。改一边必须改另一边。详见references/protocol-pitfalls.md。 - API Key 对齐: Bridge 默认
--api-key bingo301Tt23456Admin!硬编码在server.py(设计意图 = Server 的API_KEY_WRITE/ADMIN)。Server 从环境变量读取(默认空串),需在生产服务器.env中设置相同值。注册只需write权限,不是admin。详见references/server-deployment-and-keys.md。 check_permission参数 bug (fixed1f14950):bridges.py的register_bridge原来check_permission("write", x_api_key)把原始 key 字符串当权限级别传入,PERMISSION_LEVELS里找不到 → 永远 False → 401。正确:先verify_api_key(x_api_key)拿到权限级别字符串,再check_permission("write", permission)。- Patch 工具
***红action: 给 secret 类变量赋值的 patch 可能被安全拦截器替换为***,导致语法错误。patch 后务必读回验证。 - 多 subagent 并发 push: 同一 repo 被多个 subagent 同时改会 git reject,需
pull --rebase解决。 - Bridge 审批: 新 bridge 注册后 status=pending,Desktop 看不到。测试时需手动
PUT /bridges/{id}设 status=online。 - WS close code 语义冲突: relay 用 4001 表示 auth timeout,atomk-bridge 用 4001 表示 duplicate connection,cloud-bridge 用 4001 表示 auth timeout、4003 表示 invalid key。Desktop 必须同时处理 4001 和 4003 为认证失败,设
lastError给用户看。 - 🔴 Bridge v3.0
server_multi.pyhas hardcoded single-connection: The legacyserver_multi.py(v3.1) had a hardcodedmax_connections=1not read from DB. Current state: Bridge-CN-1 and all production bridges run v4.0server.pywhich uses dynamicDesktopSlotarchitecture with no hardcoded limit. Theserver_multi.pyfile still exists in the repo but is NOT the running version. If a bridge were still on v3.0,git pullwould bring inserver.pyv4.0; a restart would switch to multi-slot. - 🔴 首次登录后 model 未设置 → chat 卡住 (v3.9.6 fix, verified):
applyAgentonly configured Bridge connection, not model →getModelConfig().modelwas empty → chat used fallbackhermes-agent→ could fail. v3.9.6 fix:applyAgentnow fetches/v1/modelsfrom Bridge HTTP API after connecting and callssetModelConfig("auto", firstModelId, httpUrl). Verified: Bridge-CN-1 returns modelhermes-agentvia/v1/models; chat completions (POST /v1/chat/completions) proxy through Bridge to Hermes backend successfully (tested: ping → pong 🏓). If Bridge doesn't serve/v1/models(e.g. v3.0), user must manually select model from ModelPicker in Chat UI. - 🔴 apiKey 空串 = 静默跳过认证 → 4001 (v3.9.4→v3.9.5 fix):
connectCloudBridge()的 WS open handler 只在if (config.apiKey)时发送auth消息。如果调用方传apiKey: "",条件为 falsy → auth 消息不发送 → 服务端 5s 超时后踢出 (4001/4003)。关键是空串和 undefined 都是 falsy,所以传apiKey: ""等同于没有 key。必须确保bridges[0].key从 API 响应一路传播到connectCloudBridge({ serverUrl, apiKey: b.key }),中间的connectionConfig.apiKey存储、getCloudBridgeConfig()返回、UI 手动连接读取都不能丢掉 key。如果 key 确实为空(如旧版 Bridge 不需要 key),则应不传 apiKey 让代码走 v3/v1 兼容路径。 - Schema 改动需三处同步: 修改
BridgeInfo类型时必须同时更新operation-api.ts、preload/index.d.ts、ChromeBridge.tsx,否则 TypeScript 编译可能在 preload/renderer 侧报类型不匹配。
Desktop V4.0 Connection 架构
v3.9.0+ 的 Desktop Connection 板块以 V4.0 Cloud Bridge 为核心流程:
用户登录 atomlisting.com → Server 返回 bridges + recommended_bridge
→ Desktop 缓存到 desktop.json (userBridges + recommendedBridgeId)
→ Connection 板块显示 Bridge 列表卡片 + 一键连接按钮
→ 点击连接: chromeBridgeCloudConnect(recommendedBridgeUrl) + operationApplyAgent()
→ CloudBridge 状态实时呈现: ● 已注册 / ◌ 注册中 / ↻ 重连 / ○ 断开
→ V3 手动 Remote/SSH 降为高级折叠选项
关键 IPC 通道:
| 通道 | 用途 |
|---|---|
operation:get-bridges-cached |
从 desktop.json 读取缓存的 Bridge 列表(无 API 调用) |
chrome-bridge:set-bridge-list |
登录后推送 Bridge 列表到 chrome-bridge 模块(支持断线自动切换) |
chrome-bridge:cloud-connect |
连接指定 Bridge WS 地址 |
chrome-bridge:cloud-disconnect |
断开当前 CloudBridge 连接 |
chrome-bridge:cloud-get-state |
获取当前连接状态(connected, registered, reconnectAttempt, serverUrl, clientId, lastError) |
CloudBridge 远程模式 API URL 解析 (v3.9.7+)
CloudBridge 模式下 ConnectionConfig 存储的是 cloudBridgeUrl(ws:// 格式)而非 remoteUrl。getApiUrl() 和 getRemoteApiBaseUrl() 必须在回退到 remoteUrl 之前先解析 cloudBridgeUrl:
cloudBridgeUrl = "ws://49.51.249.171:9228/ws"
→ replace wss→https / ws→http
→ strip /ws suffix
→ httpUrl = "http://49.51.249.171:9228"
v3.9.7 fix: 之前 getApiUrl() 只检查 conn.remoteUrl(CloudBridge 模式下为空),导致所有 REST API 调用(sessions、chat completions、model auto-detect)均失败。
Bridge → Gateway Auth Chain (v3.9.7+ 核心认证模型)
Desktop 通过 Bridge 调用 Hermes Gateway API 的认证链:
Desktop ──Authorization: Bearer <b.key>──→ Bridge (check_auth 验证 b.key)
Bridge ──strip all Authorization headers───→ Bridge (hermes_api_proxy_handler)
Bridge ──inject Authorization: Bearer <HERMES_API_KEY>──→ Gateway (8642)
关键决策:Desktop 用 b.key(Bridge key)认证,不用 agent_api_key:
- Server
/api/v1/bridges/返回key(Bridge 认证密钥)和agent_api_key(Gateway 密钥,可选) - Desktop 的
conn.apiKey=b.key,用于 Bridge 的check_auth() - Bridge 的
hermes_api_proxy_handler在转发到 Gateway 前移除所有incoming Authorization headers,再注入自己的HERMES_API_KEY - 因此 Desktop 不需要 Gateway key,Bridge 替它注入
agent_api_key字段仅用于参考/诊断,不应被 Desktop 用于 API 请求
--hermes-api-key 启动参数(v4.0+):
- 默认值为空串,自动 fallback 到
--api-key(同API_KEY) - 单机部署(Bridge + Gateway 同一台机器)通常共享同一个 key
- 启动命令示例:
python3 server.py --key Bing2026Cao$$$ --proxy-port 9228 --bridge-name Bridge-CN-1 --hermes-api-key Bing2026Cao$$$ - 如果省略
--hermes-api-key,Bridge 自动用--key的值作为 Gateway key
BridgeInfo 新字段(v3.9.7+,Server commit 5b49c04 已部署):
agent_api_key?: string— Gateway API key(仅供参考,Desktop 不使用。⚠️ Serverconfig.py的HERMES_GATEWAY_API_KEY必须通过环境变量设置,不能硬编码——之前5b49c04硬编码了错误的5HH551...已修正为空串默认值)default_model?: string— Server 推荐的默认模型(避免/v1/models额外请求)。DesktopapplyAgent优先用此值,fallback 到/v1/modelsfetchdefault_provider?: string— Server 推荐的默认 provider
Model 自动选择 (v3.9.7+)
Desktop 在 CloudBridge 远程模式下首次使用时本地模型配置为空。两处自动选择:
- applyAgent (首次登录): 连接 Bridge 后主动 fetch
/v1/models→setModelConfig() - get-model-config IPC (按需): 如果本地
model为空且cloudBridgeUrl+apiKey可用,自动 fetch/v1/models并缓存结果
两端均需 cloudBridgeUrl → HTTP 转换(同上节)+ Authorization: Bearer ${conn.apiKey} header。
⚠️ getEdgeInstanceConfig() guard condition: 此函数应以 system_code(主键,Server 一定返回)做 null guard,绝不能以 cloud_bridge_ws_url(派生字段)做 guard。当 Server 未配置 INSTANCE_DEFAULT_DOMAIN 时,cloud_bridge_ws_url 为空串但其余字段(system_code, domain, agent_api_url)均有效。以 cloud_bridge_ws_url 做 guard 会导致整个 EdgeInstanceConfig 返回 null,前端完全不显示 Bridge 信息。
- Bridge key 明文暴露: Server
GET /bridges/返回完整key字段,Desktop 客户端可直接获取所有 bridge 的认证密钥。生产环境应考虑脱敏或权限控制。 - jwt_secret 心跳泄露: Server 心跳响应每次都下发
jwt_secret(代码注释说"仅审批后首次",但实际是只要status=online就下发),Bridge 端持续持有完整 JWT 签名密钥。 - Server schema 内联: 路由文件不得内联 Pydantic schema 类,必须从
schemas/bridge.py导入。之前有 subagent 在bridges.py里内联了BridgeResponse,导致与schemas/bridge.py分歧。 - Server 已知 bugs:
User.last_used_bridge_id缺 Alembic 迁移;auth.py从remote_user读此字段永远返回 None;bridges.py有新旧两套审批端点。详见references/server-deployment-and-keys.md。 bridges.pyunshare_bridgeNPE risk: L663 accessesbridge.user_idwithout null-checking the bridge object first. Ifbridge_iddoesn't exist,bridge = session.query(Bridge).filter_by(id=bridge_id).first()returns None →.user_idraisesAttributeError. In practice the admin-only permission guard + UI constraints make hitting this unlikely, but defensive code should checkif not bridge: raise HTTPException(404).- 🔴 CDP Snapshot Handler 缺 slot 解析 → NameError:
cdp_snapshot_handler如果没有调用_resolve_slot_from_request(request),访问slot.ref_map时抛出NameError: name 'slot' is not defined。其他 CDP handler(click-ref, fill-ref, wait, scroll-ref)都正确调用了,仅 snapshot 遗漏。代码审查时应检查所有 Route B handler 是否以slot = _resolve_slot_from_request(request)开头。详见references/cdp-pipeline-debugging.md。 - 🔴 proxy_handler 也缺 slot 解析 → NameError:
proxy_handler在get_primary_client()后直接访问slot.pending,但从未通过slots.get(cid)解析 slot。修复:slot = slots.get(cid); if not slot: return 503。所有 访问slot.*的 handler 都必须先解析 slot。详见references/cdp-pipeline-debugging.md(Bug 3)。 - 🔴 Bridge WS tunnel 缺 auth header → Desktop Relay 401 (v3.9.10+): Desktop v3.9.10 的 Express relay(localhost:3928)加了
relayAuthMiddleware。Bridge 通过 WS tunnel 转发 CDP 请求到 Desktop 时,handleCloudTunnelRequest将 WS 消息中的 headers 原样传给localhost:3928。Bridge 的cdp_send()必须在 WS 消息 headers 中包含Authorization: Bearer <API_KEY>,否则 Desktop relay 返回 401。详见references/cdp-pipeline-debugging.md(Bug 2)。 - 🔴 proxy_handler 过滤掉 Authorization header → 401:
proxy_handler原来用 dict comprehension 构建 tunnel headers 时明确过滤了authorization字段(if k.lower() not in ('host', 'authorization'))。只应过滤host,必须保留authorization并兜底注入API_KEY。详见references/cdp-pipeline-debugging.md(Bug 4)。 - CDP 调试流程: 当 CDP 不工作时,按顺序检查 Bridge diagnostics → Bridge 日志 → 隔离 pipeline 断点 → 直接 curl 测试。完整流程见
references/cdp-pipeline-debugging.md。 - API 代理超时对齐 (
120s→300s, 2026-06-02): Hermes Agent 任务可跑 20-30 分钟,Desktop API 客户端 + Bridge 两侧代理超时都必须从 120s 提升到 300s。改一处漏另一处会导致socket hang up或API request timed out。详见references/cdp-pipeline-debugging.md"API Proxy Timeout Tuning"。
References
references/protocol-pitfalls.md— Server/Bridge header 名不一致等协议陷阱references/ws-protocol-comparison.md— 三个 Bridge server 实现的 WS 协议详细对比references/server-deployment-and-keys.md— 生产服务器部署信息、API Key 架构(不在 Gitea 中)、已知 Server bugsreferences/cdp-pipeline-debugging.md— CDP 命令流水线架构、常见 Bug 及修复、调试流程references/bridge-health-diagnostics.md— 解读 Bridge/health端点输出,诊断多 Desktop 连接状态