Files

25 KiB
Raw Permalink Blame History

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-Key header(共享密钥,匹配 API_KEY_ADMIN 或 BRIDGE_SECRET 或默认值 bingo301Tt23456Admin!
  • Bridge → Server 心跳 = X-Bridge-Key headerbridge自身的key,注册时获得或自带的 --key
  • Desktop → Bridge = Server 分配的 keyWS auth)或 Server 签发的 JWTHTTP Bearer
  • Bridge = 不存用户数据,只做CDP代理转发

Bridge 生命周期 (v4.0 审批流)

  1. 启动注册: Bridge 带 X-API-Key 调用 /register → Server 创建记录,status=pending
  2. 等待审批: Admin 通过 PUT /bridges/{id} 设置 status=online 审批通过
  3. 心跳运行: Bridge 定时带 X-Bridge-Key 调用 /heartbeat 上报连接数
  4. 下线: Admin 设 is_active=false 或 Bridge 停机超时 → status=offline
  5. 重新注册: 同一 key 再次 /register → 自动 status=online(无需再审批)

⚠️ Desktop GET /bridges 只返回 status=online 的 bridgepending/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 会导致 "" 成为一个有效 entry
  • backend/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 远程连 BridgeBridge 向 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时: 自动切换列表中下一个bridgewrap 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 忽略未知 typev4 依赖 auth 完成认证。这样 Desktop 代码一套逻辑兼容所有版本。

⚠️ 关键修复(2026-05Desktop 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.tsxUI 渲染)

Server 下发 Commands 处理

Server 通过心跳响应下发管理命令,Desktop 必须处理:

Command 说明 Desktop 处理
update_key 更换 bridge 认证 key 更新本地 cloudBridgeConfig.apiKey
drain 停止接受新请求,优雅断连 intentionalClose=true5s 后断开(不重连)
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: origingitea9webs.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/health with Authorization: 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 DesktopSlot architecture with no MAX_DESKTOPS limit
  • 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 (fixed 1f14950): bridges.pyregister_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=pendingDesktop 看不到。测试时需手动 PUT /bridges/{id} 设 status=online。
  • WS close code 语义冲突: relay 用 4001 表示 auth timeoutatomk-bridge 用 4001 表示 duplicate connectioncloud-bridge 用 4001 表示 auth timeout、4003 表示 invalid key。Desktop 必须同时处理 4001 和 4003 为认证失败,设 lastError 给用户看。
  • 🔴 Bridge v3.0 server_multi.py has hardcoded single-connection: The legacy server_multi.py (v3.1) had a hardcoded max_connections=1 not read from DB. Current state: Bridge-CN-1 and all production bridges run v4.0 server.py which uses dynamic DesktopSlot architecture with no hardcoded limit. The server_multi.py file still exists in the repo but is NOT the running version. If a bridge were still on v3.0, git pull would bring in server.py v4.0; a restart would switch to multi-slot.
  • 🔴 首次登录后 model 未设置 → chat 卡住 (v3.9.6 fix, verified): applyAgent only configured Bridge connection, not model → getModelConfig().model was empty → chat used fallback hermes-agent → could fail. v3.9.6 fix: applyAgent now fetches /v1/models from Bridge HTTP API after connecting and calls setModelConfig("auto", firstModelId, httpUrl). Verified: Bridge-CN-1 returns model hermes-agent via /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.tspreload/index.d.tsChromeBridge.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 存储的是 cloudBridgeUrlws:// 格式)而非 remoteUrlgetApiUrl()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.remoteUrlCloudBridge 模式下为空),导致所有 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.keyBridge key)认证,不用 agent_api_key

  • Server /api/v1/bridges/ 返回 keyBridge 认证密钥)和 agent_api_keyGateway 密钥,可选)
  • Desktop 的 conn.apiKey = b.key,用于 Bridge 的 check_auth()
  • Bridge 的 hermes_api_proxy_handler 在转发到 Gateway 前移除所有incoming Authorization headers,再注入自己的 HERMES_API_KEY
  • 因此 Desktop 不需要 Gateway keyBridge 替它注入
  • 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-keyBridge 自动用 --key 的值作为 Gateway key

BridgeInfo 新字段(v3.9.7+Server commit 5b49c04 已部署)

  • agent_api_key?: string — Gateway API key(仅供参考,Desktop 不使用。⚠️ Server config.pyHERMES_GATEWAY_API_KEY 必须通过环境变量设置,不能硬编码——之前 5b49c04 硬编码了错误的 5HH551... 已修正为空串默认值)
  • default_model?: string — Server 推荐的默认模型(避免 /v1/models 额外请求)。Desktop applyAgent 优先用此值,fallback 到 /v1/models fetch
  • default_provider?: string — Server 推荐的默认 provider

Model 自动选择 (v3.9.7+)

Desktop 在 CloudBridge 远程模式下首次使用时本地模型配置为空。两处自动选择:

  1. applyAgent (首次登录): 连接 Bridge 后主动 fetch /v1/modelssetModelConfig()
  2. 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.pyremote_user 读此字段永远返回 Nonebridges.py 有新旧两套审批端点。详见 references/server-deployment-and-keys.md
  • bridges.py unshare_bridge NPE risk: L663 accesses bridge.user_id without null-checking the bridge object first. If bridge_id doesn't exist, bridge = session.query(Bridge).filter_by(id=bridge_id).first() returns None → .user_id raises AttributeError. In practice the admin-only permission guard + UI constraints make hitting this unlikely, but defensive code should check if 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 handlerclick-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_handlerget_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 relaylocalhost: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 upAPI 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 bugs
  • references/cdp-pipeline-debugging.md — CDP 命令流水线架构、常见 Bug 及修复、调试流程
  • references/bridge-health-diagnostics.md — 解读 Bridge /health 端点输出,诊断多 Desktop 连接状态