遥控
这页写给用管理台的管理员:怎么在「遥控」页接上一台员工机器上的 sid-code,替它审批工具调用、给它发消息、中断或强制断开。
遥控是整个企业后端里权限最高的通道。接上之后,你在网页上发的每一句话都会在那台机器上执行,可能读写文件、跑 shell 命令;工具确认也弹在你这边,不在那台机器的终端里。
客户端怎么进入 Bridge 模式、本机准入怎么判(明文 ws:// 拒绝、首次连接要当面确认),写在插件与 Bridge 的「Bridge:远程控制」一节,参数签名见 CLI 参数。这页不重复,只写企业后端这一侧。
不用遥控的组织可以跳过这页,也不用部署中继进程。
配对目前要手工完成
sid-code 不会自己去企业后端申请遥控会话。--bridge 只接受一个现成的地址和令牌,所以每次遥控都要先有人用这台设备的设备凭据调一次签发接口,再把返回的 ws_url 和 session_token 交给 sid-code --bridge。步骤见配对流程。
签发后 10 分钟内管理员必须在管理台点「进入」完成配对,否则会话过期,那台机器上的 sid-code 会被断开并退出。
联动:配了之后员工那边会怎样
| 你在管理台做了什么 | sid-code 什么时候知道 | 失败时 | 员工看到什么 |
|---|---|---|---|
| 点「进入」 | 立即。中继把两侧连上,会话从「等待中」变成「已配对」 | 中继未连接时页面提示「中继断开」,不会自动重连 | 终端里只有启动时那句「Bridge 远程控制已启动」,看不到对话内容 |
| 发一句话 | 立即,进入那台机器的主循环。上一轮没跑完就排队,一次只跑一轮 | 中继断开时页面提示「中继未连接」,消息不发出 | 无 |
| 「允许」/「拒绝」一个待批工具 | 立即 | 60 秒不答等于拒绝,页面显示「审批已过期」 | 无 |
| 「中断本轮」 | 立即,当前这一轮停下 | 同上 | 无 |
| 「离开」,或关掉浏览器标签页 | 立即。中继关掉 CLI 那一侧,会话标为「已断开」 | —— | CLI 尝试重连,被中继以认证失败拒绝,终端打印「Bridge 连接被拒绝:认证失败(token 无效)」后退出 |
| 「断开」(强制断开) | 最多约 2 秒,中继轮询到后关闭两侧 | 令牌同时吊销,即使中继还没关,重连也进不来 | 终端打印「Bridge 连接被拒绝:管理员强制断开」,进程以非 0 退出,不会自动重连 |
| 策略里打开「禁止 Bridge 远程控制」 | 那台机器下次启动 --bridge 时 | 拉不到策略且没有缓存时按「没有远程策略」处理,不拦 | 启动即拒绝:「企业策略已禁用 Bridge 远程控制(settings 中 bridge.enabled = false)」 |
几个细节:
- 鉴权:签发会话走设备凭据(
POST /api/v1/ctl/bridge/sessions);管理台的列表、进入、断开走管理员的登录会话,只有 admin 能用。两条链互不通用。 - 令牌不进 URL:两侧都在 WebSocket 建立后的第一帧里发令牌。URL 上带
token=参数的连接会被直接关闭(关闭码 4001),即使令牌是对的。这是为了不让令牌进 Nginx 访问日志。 - 会话是一次性的:任何一侧正常关闭或掉线,中继都会把会话标为「已断开」,令牌随之作废。只有空闲超时断开时会话保持「已配对」,客户端能用原令牌重连回来。所以客户端虽然对网络抖动会自动重试(最长 10 分钟),实际上多半会在重连时拿到认证失败(4001)然后退出。
- 永久失败不重试:认证失败(4001)、被同一角色的新连接挤掉(4003)、管理员强制断开(1008
admin_disconnect)这几种情况,客户端立刻报错,以非 0 退出。 - 策略只在启动时判一次:会话已经连着时把
bridgeEnabled改成 false,不会断开它。要立刻停,用「断开」。
出处:packages/core/src/bridge/ws-transport.ts、admission.ts、bridge-policy.ts;agent-backend: backend/app/modules/bridge/service/sessions.py、sidecar/relay.py。
配对流程
整个过程有三方:那台机器上的 sid-code(CLI 侧)、管理台(控制端)、中间的中继进程。
确认这台机器已经登录或注册过。 签发要用它的设备凭据。飞书登录(
sid-code login)和注册码(sid-code auth enroll)得到的设备都可以,见设备与注册码。用设备凭据签发一个会话。 在那台机器上执行(凭据在
~/.sid-code/device-credential.json的credential字段):bashcurl -sS -X POST https://agent.example.com/traj/api/v1/ctl/bridge/sessions \ -H "Authorization: Bearer $(jq -r .credential ~/.sid-code/device-credential.json)" \ -H "Content-Type: application/json" \ -d '{"ver": "0.1.610", "cwd_basename": "my-repo"}'请求体可以省略。只接受这两个字段,都是给管理台列表看的展示信息;多写别的字段会 422。设备、组织、团队一律从凭据里取,不看请求体。
cwd_basename即使传了完整路径,也只保留最后一段。成功返回 201:
json{ "session_id": "br_3f9c…", "session_token": "<一次性明文>", "ws_url": "wss://agent.example.com/traj/api/v1/bridge/ws", "expires_at": "2026-10-16T18:00:00+00:00" }session_token只出现这一次,服务端只存哈希。用返回值启动 sid-code。 在同一台机器上:
bashsid-code --bridge wss://agent.example.com/traj/api/v1/bridge/ws --bridge-token <session_token>第一次连某个地址时,终端会弹确认框,要本人确认一次(之后记住,不再问)。所以第一次必须在交互式终端里跑,脚本里首次连接会直接被拒。通过后终端打印:
textBridge 远程控制已启动: wss://agent.example.com/traj/api/v1/bridge/ws 按 Ctrl+C 退出管理员在「遥控」页找到这一行(状态「等待中」),点「进入」。状态变成「已配对」,就可以操作了。
从第 2 步签发算起,10 分钟内要完成第 4 步。
想确认这台设备当前有没有未过期的会话,可以用同一个凭据调 GET /api/v1/ctl/bridge/sessions/whoami。它返回 session_id、state、expires_at,不返回令牌。
签发接口的状态码
| 状态码 | 原因 |
|---|---|
| 201 | 签发成功 |
| 401 | 没带设备凭据,或凭据无效、已吊销、已过期 |
| 422 | 请求体有未知字段 |
| 429 | 这个组织同时在线(等待中 + 已配对)的会话已达 50 个 |
| 500 | BRIDGE_WS_PUBLIC_URL 没配或配错,见部署要求。服务端拒绝签发,不会发出一个连不上的地址 |
在管理台里操作
页面在左侧菜单「遥控」。页首的黄色提示框写着「遥控会在那台机器上跑命令、改文件」。
从「设备」页也能进来:正在被遥控的设备,状态列有「遥控中」标签,点它会跳到本页,只看这台设备(页首出现「只看设备 xxx」,点「清除」恢复全部)。
页首三个数字
| 卡片 | 含义 | 怎么读 |
|---|---|---|
| 在线配对 | 两侧都连上的会话数 | 0 表示没有人在被遥控,通常正常 |
| 等待中 | 已签发、还没配对的会话数 | 长时间大于 0,说明 CLI 没连上或管理员没进入 |
| 近 24h 强制断开 | 过去 24 小时管理员点「断开」的次数 | 只统计强制断开,正常结束不算 |
这三个数字始终按全部会话计算,不随下面的筛选变化。
会话列表
右上角两个筛选:「状态」(等待中 / 已配对 / 已断开 / 已过期)和「组织」。列表最多显示最近 200 条,每 10 秒自动刷新(进入某个会话后暂停刷新)。
| 列 | 含义 |
|---|---|
| 设备 | 设备 ID,点击跳到设备页 |
| 组织 | 签发时设备所在的组织 |
| 版本 | 签发时自报的 sid-code 版本,只作展示 |
| 目录 | 签发时自报的工作目录名,只有最后一段 |
| 状态 | 等待中 / 已配对 / 已断开 / 已过期 |
| 创建 | 签发时间 |
每行「操作」有「进入」和「断开」两个按钮。已断开、已过期的会话两个按钮都置灰。
进入一个会话
- 点「进入」。管理台为这个会话签发一张控制端令牌,用它连上中继。
- 下方出现会话面板,标题旁的标签显示「中继已连接」「正在连接」或「中继断开」。
- 面板里的滚动区按时间显示:模型输出的文本、工具名、状态(例如「本轮已中断」「审批已过期」)。心跳不显示。
- 那台机器要确认工具时,出现「待批 · 工具名」卡片,带「允许」「拒绝」两个按钮和「剩余 N 秒」倒计时。
- 底部输入框(提示文字「发一句话。没有附件,没有斜杠命令。」)回车或点「发送」,把这句话发给那台机器;「中断本轮」让当前这一轮停下。
- 用完点右上角「离开」。
「离开」等于结束会话
离开(包括关掉浏览器标签页)会关掉你这一侧的连接,中继随即关掉 CLI 那一侧,会话标为「已断开」。那台机器上的 sid-code 会退出。要再遥控,回到配对流程重新签发。
和「断开」的区别只在于:「断开」要填原因、进审计,并计入「近 24h 强制断开」。
中继断开时,面板顶部出现红色提示「中继断开」和「重新进入」按钮。管理台不会自动重连。只有连接是因为空闲超时断开的,「重新进入」才能接回去;其他情况会话已经结束,重新进入会提示「session 已结束或过期,不能再签发」(409)。
同一个会话同时只能有一位管理员。 每次「进入」都会重签控制端令牌,旧的立即作废,先进入的那位会被挤掉。按中继的现有逻辑,被挤掉的那条连接关闭时会把会话标为「已断开」,两侧随后都被关闭。所以不要两个人同时点同一个会话的「进入」。
强制断开
- 点这一行的「断开」。
- 弹窗「强制断开」写着「断开后这台机器上的 CLI 不会自动重连。原因进审计。」
- 在「为什么断开」里填原因。必填,空白会被拒绝(提示「原因必填」)。
- 点「断开」。提示「已断开」。
断开后会话状态变成「已断开」,两侧令牌一并吊销,中继在约 2 秒内关掉两侧连接。这个会话不能再进入;要继续遥控,回到配对流程重新签发。
管理台接口的状态码
| 接口 | 状态码 | 原因 |
|---|---|---|
所有 /api/v1/bridge/sessions/** | 401 | 没有管理台登录会话 |
| 403 | 登录了但不是 admin | |
进入(POST …/{id}/controller-token) | 404 | 会话不存在 |
| 409 | 会话已断开、已过期或超过 8 小时,不能再签发 | |
| 500 | BRIDGE_WS_PUBLIC_URL 没配或配错 | |
断开(POST …/{id}/disconnect) | 404 | 会话不存在 |
| 422 | 原因为空 |
字段说明
会话状态
| 状态 | 什么时候进入 | 还能做什么 |
|---|---|---|
等待中(waiting) | 签发后。CLI 连没连上都是这个状态,直到两侧都连上 | 进入、断开 |
已配对(paired) | CLI 和管理台两侧都连上中继 | 进入(会挤掉当前控制者)、断开 |
已断开(disconnected) | 管理员强制断开;管理员「离开」;员工按了 Ctrl+C;任何一侧掉线 | 无 |
已过期(expired) | 签发后 10 分钟还没配对 | 无 |
已配对的会话在一侧因空闲断开时仍保持已配对,客户端可以用原令牌重连回来。
限制
| 项目 | 值 | 可改 | 说明 |
|---|---|---|---|
| 每个组织同时在线的会话 | 50 | 否 | 等待中和已配对都算。超出签发返回 429 |
| 会话有效期 | 8 小时 | 否 | 从签发算起,活跃也不续期。要更久就重新签发 |
| 配对等待 | 10 分钟 | 否 | 签发后这么久没配对就标为已过期,令牌吊销 |
| 工具审批超时 | 60 秒 | 否 | 超时等于拒绝 |
| 首帧鉴权 | 5 秒 | 否 | 连上中继后 5 秒内没发令牌就关闭 |
| 空闲判定 | 60 秒 | 否 | 两侧都没有任何帧(含心跳)才断开。CLI 每 30 秒、管理台每 20 秒发一次心跳,正常情况下不会触发 |
| 单帧大小 | 1 MB | 否 | 超过的帧被丢弃,连接不断 |
出处:agent-backend: backend/app/modules/bridge/service/guard.py。
策略开关
策略里的「禁止 Bridge 远程控制」对应 bridgeEnabled: false:
- 打开:命中这份策略的设备,
sid-code --bridge启动即被本机拒绝; - 不打开(字段省略):不限制。远程策略里只有显式
false才算关,没有「显式打开」的含义; - 远程的
false盖过员工本机 settings 里的bridge.enabled: true;远程没表态时,员工本机的bridge.enabled: false也能关掉自己这台。
这是客户端侧的拦截。服务端签发会话时不看策略,见能力边界第 4 条。
部署要求
遥控需要额外部署一个中继进程,不用遥控就不必起。完整步骤和 Nginx 片段见部署与配置。
| 要求 | 为什么 |
|---|---|
中继单独起一个进程,监听 127.0.0.1:8901,只能 1 个 worker | 配对关系存在进程内存里。开两个 worker,CLI 和管理台可能连到不同的进程,两边都在等,谁也不报错 |
| 不要把中继的路由并进主应用 | 主应用是 --workers 2,同一种静默失败 |
Nginx 单独加一段 location /traj/api/v1/bridge/ws,反代到 127.0.0.1:8901,带 Upgrade / Connection 头、长超时、关缓冲 | 只加在 HTTPS 的 server 块里。明文 Upgrade 等于令牌走明文 |
配置 BRIDGE_WS_PUBLIC_URL=wss://agent.example.com/traj/api/v1/bridge/ws | 签发响应里的地址取自这里,不从请求的 Host 推断。生产没配、或配成非 wss:// 的外部地址,签发一律 500 |
本地试用(STORAGE_BACKEND=local)可以不配 BRIDGE_WS_PUBLIC_URL,签发会返回 ws://127.0.0.1:8901/api/v1/bridge/ws。这时客户端要加 --bridge-insecure 才肯连明文地址。
主应用和中继只通过数据库联系。主应用挂了,已经配上的会话继续转发;中继挂了,列表还在,只是新的连接连不上。中继本机探活:curl http://127.0.0.1:8901/health。
能力边界(如实说)
1. 没有一键配对。 客户端不会自己申请会话,员工或运维要先用 curl 签发,再把令牌贴进命令行。签发后 10 分钟内管理员没进入,会话就过期,CLI 被断开退出。
2. 会话断一次就结束。 离开、掉线、员工 Ctrl+C、另一位管理员进入,都会结束会话,没有「断线续接」。每次都要重新签发、重新启动 sid-code。
3. 你批准的是一个工具名,看不到它要做什么。 待批卡片只显示工具名(例如 Bash),不显示命令或参数;滚动区里工具调用和结果也只显示工具名。这是有意的,参数和输出可能含密钥或文件内容。代价是你在不完全知情的情况下审批,对不确定的请求请点「拒绝」。
4. bridgeEnabled: false 只在客户端、只在启动时生效。 服务端签发时不检查策略;拉不到策略且没有缓存时,客户端按没有远程策略处理(fail-open);已经连着的会话不受影响。要立刻停掉某个会话,用「断开」。
5. 吊销设备不会断开正在进行的遥控。 吊销只在连接中继时检查,已配对的连接不会因此关闭。离职等紧急情况,先在本页「断开」,再去设备页吊销。
6. 8 小时有效期只拦重连。 超过 8 小时,令牌不能再连入中继,管理台也不能再「进入」。但一条一直没断过的连接,不会在 8 小时整被主动关闭。
7. 员工在终端里看不到你在做什么。 Bridge 模式下那台机器不显示对话界面,只有启动时的提示。要让员工知情,需要你们自己约定告知流程。
8. 遥控的管理记录没有页面可查。 签发、管理台进入、强制断开、鉴权失败都写进了 bridge_audit 表,强制断开的原因也在里面,但管理台没有查看页面,也没有列表接口。页首「近 24h 强制断开」是唯一能看到的汇总。需要追溯时,直接查数据库或服务日志。对话内容本身不入库。
9. 令牌出现在命令行里。 --bridge-token 是命令行参数,同一台机器上能看进程列表的人看得到它。它只对这一个会话有效,最长 8 小时。
10. 中继不能横向扩展。 单进程、单 worker,一台机器的容量就是上限。每组织 50 个在线会话的限制是防滥用用的,不代表中继的容量。
常见问题
签发返回 500
BRIDGE_WS_PUBLIC_URL 没配,或者配成了 ws:// 的外部地址。生产必须是 wss:// 开头的对外地址,改完重启主应用。见部署与配置。
签发返回 429
这个组织同时在线的会话已达 50 个。「等待中」也算在内:签发了却一直没配对的会话,要等 10 分钟过期后才释放名额。打开「遥控」页会触发过期标记,必要时把用不上的会话「断开」。
员工那边启动后马上退出,提示「认证失败(token 无效)」
令牌不对、已过期,或会话已经结束。常见原因是令牌复制不完整,或者签发后超过 10 分钟才启动。重新签发一次。
员工那边提示「首次连接该 Bridge 端点需要确认,但当前环境无法交互」
第一次连这个地址必须在交互式终端里跑,本人在确认框里同意一次。之后脚本里就能直接连。
一直是「等待中」,点「进入」后面板显示「中继断开」
先在服务器上 curl http://127.0.0.1:8901/health 看中继进程在不在,再检查 Nginx 有没有那段 WebSocket 反代、是不是加在 HTTPS 的 server 块里。
我正在控制,连接突然断了
常见原因有三个:另一位管理员点了同一个会话的「进入」;员工那边按了 Ctrl+C;任何一侧网络中断。这三种情况会话都已结束,「重新进入」会返回 409,要重新签发。
只有两侧都超过 60 秒没有任何帧的空闲超时,会话才保留,「重新进入」能接回去。正常情况下心跳会防止这种断开。
待批卡片倒计时到 0 了还在
页面显示「本地计时已到,等审批过期」。以那台机器为准,它在 60 秒时已经按拒绝处理,收到过期通知后卡片会消失。