- 看板: 907 补注/909~941 登记与销账(含 929 模型停用根因与 4 处次生缺口、931 队列丢失、933/935/937 新需求、939 上下文瓶颈清单、941 C-r0 行为变更待拍板) - 归档: 全量完成态条目/段落 + 只读核验销账 4 段迁 todo归档/2026-10-10.md(机械搬运零措辞改动,未完成项零丢失) - 设计档: 输入框列表预览与自动编号 / 定时消息与待发箱持久化(统一 ai_outbox 底座) / 对话内定时延时任务 / 消息渲染管线重构(P0-P2 分期) / 上下文分层组装器落地方案(记账基点单一真值,与并行写者职责划清) / FileTree 大目录方案(推荐后端分页,否决虚拟滚动) / u-relay 运维手册 - 索引与事实修正: docs/INDEX crate 数 8→13 + 四子树补录 25 条 + 专项设计补录 27 条;纠正「项目无独立 ARCHITECTURE.md」等过时声明;统一 bun 口径 - CLAUDE.md: 新增「命名与代码风格」节(变量/方法名精简+准确;DRY·简洁·扩展性·高内聚低耦合·单一真值来源)
7.2 KiB
u-relay 运维手册(token 轮换 · 一通道一 agent · 心跳约束)
适用:u-relay 跨端中继的日常运维纪律。协议细节见 u-relay Wire 协议规格,本手册只讲运维动作与约束。 来源:协议评估遗留三项(单共享 token 可冒充 / 心跳间隔未定值 / 同 channel 多 agent 全投递)。 本手册不含任何 token 值、主机地址等敏感信息——真值只在部署机
/etc/u-relay.env与各客户端本地配置里。
1. 心跳间隔约束
心跳间隔须显著小于链路最短空闲超时(反代 proxy_read_timeout,配置样例 300s;生产实时值可能更大),
以容忍连续数次心跳丢失仍不断链。约定 30–60s,各方实现见协议规格 §4.4:
- Agent(df-tunnel):
HEARTBEAT_INTERVAL常量,30s(WS 协议层 Ping) - Commander 参考(u-ppt):30s(应用层
control_kind:"ping")
改反代
proxy_read_timeout时须同步评估心跳间隔:若把超时压到 300s 以下,间隔也要相应收窄并保持「超时 / 间隔 ≥ 5」量级的余量。
2. 单共享 token 轮换 Runbook
2.1 现状与风险
u-relay 鉴权是单一共享 token(env U_RELAY_TOKEN,服务端常量时间比较),不做 per-channel 隔离:
任何持有该 token 的客户端可声明任意 channel_id 冒充任一通道。故 token 属敏感凭据,
泄露即等于整条中继失守;轮换是唯一的止损手段。
2.2 涉及方与各自持有位置
token 值散落在「服务端 + 多个客户端」,没有集中下发通道:
| 方 | 角色 | token 持有位置 |
|---|---|---|
| u-relay 服务端 | 鉴权源 | 部署机 /etc/u-relay.env(U_RELAY_TOKEN,权限 600)或 systemd unit 的 Environment= |
| devflow 桌面端 | Agent | relay_token 设置项(运行时读,缺省回退硬编码默认值) |
| devflow 小程序 | Commander | apps/df-miniapp/src/config.ts 缺省 + 设置页可改 |
| devflow ask_uclaw | Commander | 设置页 uclaw_token(与 relay token 同源) |
| u-claw | Agent/Runner | u-claw 侧配置 |
| u-ppt | Commander | relay.ts 配置 |
新增任何接入端都要登记到本表——漏一端的后果见下节。
2.3 锁步成本(务必先读)
因为服务端只认一个 token、客户端各自缓存,轮换天然是一个锁步切换:
- 无重叠窗口:服务端不能同时接受新旧两个 token,故不存在「灰度换端」的过渡期。
- 顺序无解:先改服务端 → 全部旧客户端立即
auth_failed掉线;先改客户端 → 全部客户端在服务端切换前拿新 token 一样auth_failed。两条路都有中断窗口。 - 漏端即静默失联:任一客户端未同步换 token,会持续
auth_failed被拒连,表现为该端静默离线(无业务错误、仅 relay 日志有auth_failed),排查成本高。 - 不可逐端滚动:无法像多 token 系统那样「先切一半、观察、再切一半」。
结论:轮换是一次计划内的协调停机,中断窗口 = 从「首个客户端改值」到「服务端重启加载新值」的间隔。执行要点是把客户端改值的动作压到最短、紧接着重启服务端。
2.4 执行步骤
前置:准备好新 token,确认上表每一端都有人能改、改完可验证。
- 写服务端新值(先不重启):在部署机把
/etc/u-relay.env(或 unitEnvironment=)的U_RELAY_TOKEN改为新值,保持服务仍在跑旧值。此时新值尚未生效。 - 集中改用各客户端:将上表每一端改到新值。此步开始后各端会
auth_failed,中断窗口从这里起算。 - 重启服务端加载新值:
systemctl restart u-relay。重启后已改端的客户端重连即通。 - 验证:见 2.5。
- 未改端收尾:对仍
auth_failed的端补改(此时只有漏改者受影响,不再影响大局)。
若某些端的 token 是编译期硬编码(无运行时配置项),须提前把「新 token + 重新打包」的产物备好,否则第 2 步无法即时完成,窗口会被拉长。
2.5 验证
- 服务端:
systemctl status u-relay正常,ss -tlnp | grep 9180在听。 - 连接:拿新 token 走一次握手冒烟(
/ws/agent或/ws/commander,收hello_ack即通)。 - 日志:确认无
auth_failed残留——有则说明还有端没换到新 token。
2.6 回滚
轮换失败(如某端无法改)时,把服务端 env 回退到旧 token 并再重启一次,回到轮换前状态;客户端已改的端随之 auth_failed,需一并回退。回滚与轮换同样是锁步中断。
2.7 根治方向
根治是去掉单共享 token:服务端改为 per-channel / per-client token(客户端拿自己的凭据只能绑自己的 channel), 届时任一端轮换互不影响,锁步成本消失。在此之前,本 runbook 的锁步纪律是必须遵守的现状约束。
3. 运维纪律:一通道一 agent
3.1 现象与根因
u-relay 路由按 channel_id 配对,且同 channel_id 允许多个 agent 并存、命令全投递(Vec 索引,无去重、无选举)。
即:同 channel_id 上跑两个 agent 实例(如两个 devflow 桌面端、或 devflow + u-claw runner 用了同一通道),
一条 commander 指令会被两端各执行一次——写文件、跑命令、调外部接口全部重复,且两端事件回传互相交错。
3.2 危害
- 指令重复执行:副作用翻倍(重复落库 / 重复发消息 / 重复改文件),且难以察觉。
- 事件串台:commander 收到两份进度/结果,流式 UI 错乱;两 agent 的上下文互相污染。
- 隐性化:两端都「看起来正常工作」,问题只在副作用层面暴露,事后才追溯得到。
3.3 纪律
- 一通道一 agent:每个
channel_id在任一时刻只允许一个 active agent。通道不是「实例池」。 - 启动前先查:起第二个 agent 前,确认目标
channel_id上没有在跑的 agent(换一个独立channel_id,或用不同通道前缀)。 - 换装/重启要收尾:旧 agent 若未干净退出(进程被杀但连接残留),WS 连接可能存活到对端超时,期间新旧两连接同通道并存 = 短暂双投递。换装脚本须确认旧连接已断、或等旧连接超时后再起新实例。
- 命名隔离:不同用途用不同
channel_id(如按机器 / 按项目 / 按用户拆通道),从命名上杜绝复用。
3.4 排查手法
怀疑重复执行时:
- 看 relay 日志:同一
channel_id是否同时有 >1 条 agent 连接。 - 看 commander 侧:同一
request_id是否收到两份result/ 事件重复。 - 看副作用:文件 mtime、DB 记录、外部调用日志是否成对出现。
确认后,断开多余 agent 连接(停掉重复实例)即可恢复单投递。
附:关联
- 协议全貌:u-relay Wire 协议规格(§4 心跳、§5 部署、§3.3 路由)
- u-relay 仓 README / 部署技能:中继二进制与 systemd 发布流程
- 看板登记:
docs/todo.md「新能力引入评估纪律」下三项 P3