# 设备中心与跨平台协作（内测）

手机原生客户端可使用 Hub 账号列出、连接和切换多台设备，集中接收设备通知；native 登录、capability、中继边界和构建验收见 [`mobile.md`](mobile.md)。

设备中心是独立的账号、设备连接和群聊服务。每台 pi-web-ui 主动连接中心，无需每台电脑具有公网地址。设备中心可自行部署；仓库没有配置一个已上线的官方公共入口。Windows、macOS、Linux 使用相同连接协议，真实平台测试仍须在对应机器上执行。

主程序的 **设备连接与跨平台协作** 是统一入口，按 **设备与连接 / 群组与工单 / 外部渠道** 分区。设备分区保留配对、Bot、远程桌面与执行许可；群组分区可创建或管理群组，并直接配置独立 AI Router、查看工单和重试失败运行；外部渠道内嵌随包 Superagent，配置 GitHub / 飞书凭据与群组映射，无需单独安装或独立顶栏入口。实际聊天仍沿用左侧群组主对话流。

独立部署步骤见 [设备中心部署指南](device-center-deployment.md)：从服务器连接、默认 IP + HTTP，到端口放行、可选域名与 HTTPS、验证及故障恢复，无需项目上下文。

绑定设备中心后，左上角品牌图标可点击打开设备快捷列表，直接访问同账号设备或打开远程桌面。快捷列表复用设备中心的设备目录与绑定授权入口，支持在线状态、手动刷新和窄屏展示；点击外部或按 Escape 关闭。尚未绑定时保留普通品牌展示，临时连接失败不撤销已知绑定入口。

## 首次使用与安装引导

未绑定中心时，本机即可创建 Bot 和群组，无需连接其他设备、注册账号或安装独立 Hub。群组入口按需启动进程内的私有 loopback Hub，复用现有 Bot、群聊、任务及文件协议；数据保存在 `<pi-web-ui data-dir>/local-device-hub/`，凭据只在服务端使用并在重启时轮换。本机任务允许执行，仍受 Bot 能力白名单和本机审批限制；外部 Hub 的执行许可仍默认关闭。本机 Bot 使用原有 unpaired 作用域，群组和消息重启保留；绑定外部 Hub 后切换到其独立作用域，断开后恢复本机数据，切换前必须停止旧运行时。外部 Hub 离线时保留其绑定和已知群组，不自动切换本机数据。

设备中心概览始终提供 Bot 和群组管理入口。需要跨设备时，可选择「连接已有中心」或「创建我的中心」；创建时可以使用承载 pi-web-ui 服务的当前机器，也可以在已有服务器部署。尚无服务器可先使用当前机器体验，无需购买服务器才能开始。这里的「当前机器」指 pi-web-ui 服务所在机器，不是打开浏览器的设备。

本机一键安装默认仅允许通过 localhost/127.0.0.1 访问；反向代理部署可用 `PI_WEB_ALLOW_HOSTS` 声明该域名，声明后同样可以访问（未声明时行为不变）。写请求必须同源。页面先展示平台、Node 版本、独立数据目录和安装方案；点击安装后以独立子进程调用当前包的 Hub CLI，以固定参数执行，不经过 shell。默认监听 `127.0.0.1:8790`，数据保存在 `<pi-web-ui data-dir>/device-hub/`，与客户端配置分离。页面可修改监听 IP、端口及公开访问 origin；其他设备要连接时，必须使用其他设备可达的地址，并配置相应网络访问。

安装成功表示服务已安装并通过启动验证，不代表已登录或完成配对。通过 `pi-web-ui hub status --json` 可检查已安装服务，通过 `pi-web-ui hub restart --allow-registration=false` 关闭注册。接着打开中心创建账号，再回到配对流程，绑定后创建 Bot 和群组。安装会显式开放账号注册，创建账号后应使用服务管理命令关闭开放注册。已经绑定的中心暂时离线或列表请求失败，不应被当作未配置，群组入口和已加载列表仍保留。

「让 AI 帮我部署」会新建独立对话，携带选定部署目标、监听地址、公开地址及可用的本机环境信息。AI 先检测环境，再通过同一套 CLI 执行部署并验证；远程部署需要用户提供明确目标和已有的可信连接方式，不能默认对当前机器执行远程方案。密码、私钥与配对凭据不应进入部署提示词。提示词携带独立部署指南的 GitHub 公网原文及网页备用链接，要求先读取文档；默认 IP + HTTP，域名及 HTTPS 可选。AI 须区分系统防火墙与云安全组，明确用户需放行的实际 TCP 端口、来源范围及操作位置；不能操作云控制台时标记等待用户处理，之后重新验证外部可达性。此入口不会自动购买服务器或完成账号登录。

`GET /api/device-center/setup` 返回本机部署方案及 CLI `hub status --json` 查询到的已有安装、归属和健康状态；读取失败不得当作尚未安装。刷新或离开页面后，可恢复实际安装地址并继续配对，页面离开不会撤销已经提交的系统服务安装。`POST /api/device-center/setup` 接受 `host`、`port`、`publicUrl` 和显式 `allowRegistration: true`。安装请求互斥，重复请求返回 409；校验失败返回 400，安装失败返回 500。关闭设备中心运行时会取消并等待安装子进程退出，避免旧操作继续发布成功状态。

## 使用流程

1. 从设备中心首次引导连接已有中心，或使用本机一键安装、AI 辅助或下方 CLI 部署自己的中心。可以直接使用 HTTP 与公网 IP；设备网页与远程桌面默认共用 Hub 端口，入口为 `/device/<设备ID>/`，无需额外开放设备端口。
2. 启动各电脑上的 pi-web-ui，在顶栏切换到「设备中心」应用内标签页；界面随当前主题和语言显示，不另开浏览器页面。设备绑定管理默认仍须通过 localhost/127.0.0.1 访问（`PI_WEB_ALLOW_HOSTS` 里声明的域名同样可用，便于反向代理部署）。旧 `/devices` 地址会转到主应用的设备标签页。
3. 输入中心地址与本机名称，获取十分钟有效的配对码。
4. 在中心登录自己的账号，输入配对码确认。本机页面轮询领取设备凭据，凭据仅保存在本机数据目录，不显示给浏览器。
5. 在承载设备的「设备中心」创建 Bot，填写名称和身份提示词，通过全局路径组件浏览固定工作目录、通过通用模型选择器选择模型或继承本机默认模型。工具、Skill 和 MCP 使用可搜索的勾选列表；新建 Bot 首次加载目录时按当前已保存的工具开关默认勾选，后续刷新目录或切换项目不覆盖手动选择，编辑已有 Bot 保留其授权；内置工具完整展示，高权限和平台不支持的工具仅提示，不按 Bot 专用黑名单隐藏或拒绝，执行授权以 Bot 显式工具白名单为准（平台本身的可用性及工具自身校验仍有效）；切换项目目录会刷新该项目的能力目录，已有但当前不可用的授权保留显示并可移除。一个设备可创建多个 Bot。左侧「群组」点击 ＋，选择本账号的 Bot 创建群组。点击群名复用主对话流聊天；输入 `@`，搜索并选择 Bot 后发送任务。候选插入含稳定 Bot ID 的明确目标标记，单独输入 `@名字` 不会派单。Hub 网站也可建群和聊天。
6. 需要执行任务和接收文件的电脑，在本机绑定页显式启用执行权限。默认关闭。该权限允许智能体修改文件、执行命令和应用明确提交的文件同步请求，仍受本机审批限制。

群成员只有 Bot，设备仅负责承载、在线连接和执行许可。同一 Bot 可以加入多个群；不同账号的设备、Bot 与群隔离。每台设备的 pi-web-ui 都可从左侧「群组」折叠区建群、打开群聊，并通过右键重命名、管理成员或删除群组；设备中心标签页保留群组管理，实际聊天统一进入现有主对话流。Hub 统一负责账号、设备连接、群组与消息存储转发、任务路由和文件中继，不要求各设备用户另开 Hub 网站操作。Hub 网站保留独立访问入口。当前不支持跨账号邀请或团队成员管理。

本机用户操作与 AI 接力使用不同权限：新配对时分别生成设备执行凭据和本机交互凭据，后者仅由本机受限 HTTP 接口持有，不下发浏览器或模型。本机人类可发起新的任务链，AI 凭据仍需可信父任务；旧绑定缺少交互凭据时需重新配对。删除群组或移除成员遇到相关未结束任务时拒绝，避免丢失执行归属。

## 部署中心

已安装 npm 包可直接管理服务；源码目录需要先构建服务端：

```sh
npm run build:server
```

推荐用独立服务管理命令安装并配置登录自启：

```sh
pi-web-ui hub install --host 127.0.0.1 --port 8790 --public-url http://127.0.0.1:8790 --allow-registration
pi-web-ui hub status --json
pi-web-ui hub restart --allow-registration=false
pi-web-ui hub stop
pi-web-ui hub start
pi-web-ui hub uninstall
```

`install` 保存配置并启动，`start/stop/restart/status/uninstall` 使用已保存的配置；`restart --allow-registration=false` 仅变更注册设置并保留其他配置。`uninstall` 移除自启动配置，保留账号、设备和群组数据。安装拒绝覆盖已有或无法确认归属的服务；端口占用、权限不足、健康检查失败会明确报错。安装中断后先运行 `status --json` 检查，再用 `start` 或 `restart` 恢复；不要删除数据目录来重试。

Linux 使用 `systemd --user`，macOS 使用 launchd 用户服务，Windows 使用登录计划任务。原生服务管理器不可用或权限不足时，安装会报告失败；Linux 在无人登录时开机启动需管理员为该用户启用 linger。CLI 配置中的锁只有在确认持有进程已退出时才回收，未知或仍存活的持有者不会被覆盖。

不带子命令的 `pi-web-ui hub` 保留前台运行方式，可配置以下环境变量；源码目录也可以运行 `node bin/pi-device-hub.mjs`。

| 环境变量                           | 说明                                                                    |
| ---------------------------------- | ----------------------------------------------------------------------- |
| `PI_DEVICE_HUB_PUBLIC_URL`         | 中心的 HTTP 或 HTTPS origin，如 `http://203.0.113.10:50001`，不可含路径 |
| `PI_DEVICE_HUB_PATH_ACCESS`        | 默认开启；`0` 恢复旧版独立域名/端口模式                                 |
| `PI_DEVICE_HUB_DEVICE_DOMAIN`      | 兼容旧版独立设备域配置；默认通过 Hub 单端口路径访问设备                 |
| `PI_DEVICE_HUB_HOST`               | 默认 `127.0.0.1`                                                        |
| `PI_DEVICE_HUB_PORT`               | 默认 `8790`                                                             |
| `PI_DEVICE_HUB_DATA_DIR`           | 默认 `~/.pi-device-hub`，必须独立于客户端数据目录                       |
| `PI_DEVICE_HUB_ALLOW_REGISTRATION` | `1` 开放注册，默认关闭；首次创建账号时开启，完成后重启关闭              |

### Hub 在线升级

独立 Hub 的「一键升级」和 `pi-web-ui hub update` 会将 npm 当前 `latest` 包安装到服务配置目录下的 `releases/<随机 ID>/`，再切换服务入口并重启。安装、构建检查失败时旧服务继续运行；新版本启动失败时恢复 `service.previous` 保存的旧服务定义并重新启动。数据目录、实例 ID、账号、设备凭据和群组保持原位。回滚恢复运行程序，不回滚业务数据；发布的新版本必须保持数据向后兼容。确认已有上个受管版本后，成功升级保留当前与上个运行版本，清理更早的受管目录；无法证明目录归属时保留供人工诊断。

Hub 的首个账号是部署所有者，只有该账号可升级或查询升级任务。网页 Cookie 操作继续检查同源；设备端人工入口使用独立 `uiCredential`，通过 `/api/device-user/system/upgrade` 请求，执行凭据不能调用系统维护。旧设备缺少人工凭据时需要重新配对。前台或内嵌 Hub 不支持自动服务升级，会明确报错。

升级请求返回 HTTP 202 和持久任务 ID，受理不表示成功。`<Hub 数据目录>/hub-upgrade.json` 记录 `queued/running/succeeded/failed`、阶段、版本、启动标识和失败原因；`hub-upgrade.log` 保存本次 npm/预检日志。页面按任务 ID 查询结果，只有目标版本与健康检查版本一致、实例和注册配置匹配、启动标识改变时显示成功。旧进程仍健康、升级 API 报错或查询超时均不算成功。任务和生命周期分别使用跨进程锁，阻止网页、命令行同时提交或执行升级。

执行器由独立服务管理任务启动：Linux 使用 `systemd-run --user`，macOS 使用 GUI 域中的一次性 launchd 作业，Windows 使用单独的临时计划任务，避免 Hub 停止时结束自己的升级进程。升级任务沿用安装服务时已验证的配置位置，并再次核对实例和数据目录，不依赖后台进程重新推断 HOME 或 APPDATA；配置路径不通过升级 API 公开。Windows 会等待计划任务及其实例停止，并核对配置监听地址的 TCP 资源已释放，再启动新服务；资源释放检查最多等待四分钟。若截止后端口仍未释放，或无法核实资源状态，升级明确失败，不会终止端口占用者。macOS 会确认旧 launchd 作业已移除，再启动新服务。Node 旁必须带有 npm。升级依赖安装关闭生命周期脚本及可选依赖，独立 Hub 不加载 Agent SDK 或本机终端模块。服务安装时可通过可信环境变量 `PI_DEVICE_HUB_UPDATE_SOURCE` 保存内网 registry 包标识或本地候选 `.tgz`；HTTP 请求不能指定包来源。`PI_DEVICE_HUB_SERVICE_NAME=pi-device-hub-<名称>` 可安装隔离命名实例，默认名称兼容既有部署。

旧公开版本没有版本化健康信息时，可以安装新的全局 CLI 后执行 `pi-web-ui hub update` 完成首次引导；后续可直接在 Hub 网页操作。发布前的真实服务验证、候选包校验和与跨平台矩阵见 [Hub 发布包升级验收](hub-upgrade-release-testing.md)。

### 直接使用 HTTP 与公网 IP

```sh
PI_DEVICE_HUB_PUBLIC_URL=http://203.0.113.10:50001 \
PI_DEVICE_HUB_HOST=0.0.0.0 \
PI_DEVICE_HUB_PORT=50001 \
PI_DEVICE_HUB_ALLOW_REGISTRATION=1 \
pi-web-ui hub
```

在安全组开放对应 TCP 端口，然后浏览器打开中心地址，在各设备中填写同一地址即可绑定。此模式不需要域名、证书或反向代理，使用 HTTP 和 WebSocket；账号、设备凭据、消息和文件均为明文传输。注册完成后建议关闭开放注册。设备网页使用同一端口的 `/device/<设备ID>/` 路径，不要求额外端口、域名或证书。入口先验证账号归属或兑换本机交互凭据签发的一次性票据，再在隔离框架中加载设备页面。设备页面没有 Hub 的同源权限；资源、API 和 WebSocket 都通过绑定设备的随机能力路径访问。群组栏和设备菜单使用只读 `/api/device-center/chat-status` 查询 `hubUrl` 与 `deviceId`；Hub 的能力入口直接提供该最小绑定信息，并对旧设备 UI 的 `/status` 查询返回相同的两个字段；本机管理用的 `/status` 及设置接口仍禁止转发到设备。通过能力校验后的策略拒绝、离线等错误响应也携带沙箱 CORS 头，便于页面读取实际错误状态。

### 单端口路径与旧版独立来源兼容

默认设备入口与 Hub 共用端口，例如 `http://203.0.113.10:50001/device/<设备ID>/`；自定义别名仍指向同一入口。不再按设备消耗额外端口。设备页面由 Hub 强制施加 CSP sandbox，并且不启用 `allow-same-origin`。随机能力路径只允许访问对应设备，不能作为 Hub 账号凭据使用；撤销设备会废止其访问权限。隔离页面的浏览器存储不可用时使用页面内存回退。主题选择（`pi-theme`）通过受限消息桥保存在 Hub 入口页的浏览器存储中，按账号与设备隔离，并在首屏渲染前恢复；刷新、重新打开或能力凭据轮换不重置主题。消息桥只接受当前隔离 iframe 的主题读写，不能指定其他存储键，也不会持久化设备页面的令牌。主题仍属于当前浏览器，不跨浏览器或设备同步；其他本地偏好仍随页面刷新重置。旧版 Hub 未提供该桥时，页面等待至多 500ms 后继续启动。

以下为旧版独立来源的兼容配置：

中心域与设备域不得互为父子域，例如使用 `console.example.com` 与 `devices.example.net`。配置设备域的通配 DNS、通配证书，将两个域名的 HTTP 与 WebSocket 流量均转发至同一中心端口，并保留原始 Host、Origin。代理不应缓冲 SSE。可使用 HTTP 或 HTTPS；HTTPS 提供传输加密，HTTP 页面会显示明文传输提示。

中心账号 cookie 和设备访问 cookie 都是 host-only。设备访问先在中心校验账号归属，再签发短时一次性 ticket，进入独立设备域后兑换访问 cookie。撤销设备会关闭现有中继连接并清理设备访问凭据。

当前中继会解密转发流量，未实现端到端加密。模型密钥和项目文件继续保存在各电脑；访问页面、终端流、文件同步包和任务报告会经过中心。仅部署到可信服务器。单进程独占一个 data-dir，不支持多实例共享写同一目录。

群组菜单提供「清空消息」：确认后删除该群全部消息、历史任务和消息回执，保留群组名称与 Bot 成员，且无法撤销。存在排队、运行或停止中的任务时会拒绝清空，需等待任务结束。操作按账号校验，在 Hub 中原子持久化；本机群组与已绑定设备使用同一接口规则。

## Bot 身份与执行边界

Bot 的稳定 ID 与承载设备分离。群使用 `botIds`，可选 `primaryBotId` 必须指向群成员；创建群组及管理成员时可选择或清除主 Bot。未指定目标的人类消息交给主 Bot，并续接它最近未关闭且配置有效的群会话；显式目标及继续会话优先。Bot 公开消息不会触发默认派发；移除主 Bot 或撤销其设备时清除设置，不自动选替代者。未设置主 Bot 时保持原有 @ 派发行为。派发上下文冻结当时的主 Bot ID，执行器只对同群且自身身份匹配的主 Bot 注入协调职责：普通问答直接回应，用户要求执行的工作可查询成员能力并委派，后续补充先查询持久会话再继续明确匹配的任务，多义时询问用户。协调身份不扩张工具或审批权限，不根据历史消息自行派单；委派结果复用现有相关请求回复和自动继续机制，状态完成仍须核对交付证据。消息使用 `targetBotIds`，任务记录目标 `botId`、内部路由 `deviceId` 与配置版本。设备认证同步只能更新自身 Bot，群消息与任务回报校验 Bot 归属。Hub 存储使用新版结构，不迁移旧设备成员群数据。

每个群任务在派发前捕获同一份共享上下文：群名、Bot 成员和按时间排序的近期公开消息，JSON 总预算 24 KiB。当前任务正文单独传入，其他成员的历史回复是参考数据而非指令或派发授权；同批目标的上下文在任何成员执行前冻结。普通聊天上下文只随任务传输；显式契约任务将实际输入封存为不可变 ContextManifest 并持久化到任务记录。成员的内部思考和本机工具输出不会进入群历史。继续会话保留原执行上下文，当前仍附带一份最新的有界群消息快照，尚未对群消息做跨轮增量去重。

获得 `device_collab` 授权的成员可用 `read_history`（`roomId`、自身 `sourceBotId`、可选 `before` 游标）读取更早的原文，不会唤醒其他成员。每页 JSON 上限 24 KiB，超长消息按 `textStart` / `textEnd` 分段，沿 `nextBefore` 可继续向前获取完整文本；游标使用稳定消息 ID，追加不打乱翻页，清空或消息淘汰后旧游标明确失效。Hub 再次验证账号、设备、启用状态和群成员身份。每群最多保留 500 条公开消息，超出保留范围的内容不可回查；不承诺无限历史，也不额外调用模型生成可能丢失事实的摘要。工具未授权时仍会带入近期上下文，但无法回查更早内容。

群输入框通过 `@` 菜单统一选择目标：每个 Bot 提供「新对话」和带名称的「继续对话」入口，输入框保留选择的含义，不再单独展示任务目标下拉框。提及引用绑定稳定 Bot/会话 ID，发送前转换为 Bot 标记并路由至所选会话；修改或删除提及会清除其引用。单条消息继续一个已有会话，混入其他新建或继续目标会拒绝发送；新对话仍支持同时 @ 多个 Bot。列表更新失败仅在 @ 菜单中提示，并阻止继续旧会话。所选会话被删除、关闭或配置失效时会明确报错，不会悄悄改成新任务。\n\n普通聊天任务支持新建与继续：新任务创建共享的标准持久会话（`independent: false`）；继续会话通过 Hub 稳定会话 ID 恢复该 Bot 的原转录，工作目录和身份取自 Bot 的启动配置；未指定模型时捕获本机 pi 默认模型，不继承界面当前会话模型；工具、Skill 与 MCP 采用显式白名单并在执行入口应用。MCP 使用任务专属 Bridge，只启动白名单服务，其工具还须匹配工具白名单。Skill 不授予额外工具权限。工作目录是默认操作位置，严格限制目录外访问仍需沙盒隔离。设备执行许可及审批保持上层约束。同设备最多并发执行 4 个任务，最多 32 个等待任务；同 Bot 或真实路径重叠的工作区保持串行，调度按排队顺序选择当前可执行项，独立工作区可越过冲突等待项。目录别名通过 realpath 统一，停止未确认的 Bot/目录仍保持隔离；等待取消立即释放队列位置，超出容量明确拒绝。继续消息不会中断正在运行的工具，而是在前一轮结束后恢复同一转录执行。Bot 对话可在本机直接查看和处理交互，无需过户；查看不迁移执行归属，不影响群任务运行。Bot 转录仍禁止由普通历史恢复入口创建另一执行实例；恢复只允许设备本机持久登记的精确路径，且校验 Hub、设备、群、Bot 配置版本与工作目录。凭据轮换不改变会话身份，目录或 Bot 版本变化则拒绝继续，不静默新建。转录在执行失败后仍可保留供恢复和本机检查。

会话容量按「未停止会话」计数：已停止会话进入有界归档（每群普通归档保留 200 条，无契约/交付/审查/集成记录的可淘汰），归档不占用新建会话名额；`POST /api/rooms/:id/conversations/:conversationId/delete` 仅允许无在途任务、无契约/交付/审查/集成记录的会话，用于释放容量，禁止用清空群组代替。撤销承载 Bot 的设备会关闭其会话并标记 `closedReason=device_revoked`；Bot 版本、设备或成员变化后会话以 `stale` 返回，界面禁止继续并提示新建。群消息支持 `replyToMessageId`（必须指向同群消息）作为回复锚点，并随群上下文与 `read_history` 投影给 Bot。人类消息未显式给出 `targetBotIds` 时由 Hub 解析正文 `@Name` / `@Name(<id>)`：同名多成员不猜、非成员不派发，正文提及不再因客户端未解析而静默回落到主 Bot。幂等收据与消息保留解耦（每群 2000 条、7 天），消息淘汰不再削弱重放保护。

显式代码交付通过现有任务创建 API 或 `device_collab` 提交 `executionMode: "code"` 与版本化契约；界面普通消息仍为聊天模式。代码每轮新建隔离执行会话，以固定仓库基线及封存 Manifest 执行，确认停止后捕获携带补丁与 Git Tree 的不可变产物；执行结束与产物生成分开记录，不自动构成验证或接受。契约任务及其会话额外保留到显式清空或删除群，防止历史裁剪重写版本权威。产物可通过 `task_evidence evidenceKind=artifact` 按摘要和字节范围读取。请求预算、权限和当前限制见 [版本化契约与代码任务](device-collaboration.md#21-版本化契约与代码任务)。

Hub 状态分两层持久化：`device-hub.json` 仍是唯一的**索引**（`version: 2`，同一 schema，账号/设备/Bot/群/会话/交付/收据都在其中，`messages`、`tasks`、`conversationMessages` 固定为空数组），大集合落入 `hub-log/` 下的追加日志：`hub-log/messages/<roomId>.jsonl`、`hub-log/tasks/<roomId>.jsonl`、`hub-log/conversation-messages/<conversationId>.jsonl`。每行是 `{"v":1,"op":"upsert","value":…}` 或 `{"v":1,"op":"del","ids":[…]}`；协同事务以 `hub-wal.jsonl` 为单一提交点：将索引与三个集合的增删差量、递增序号和校验和写入并 fsync 后才确认提交；日志与索引随后作为可恢复投影更新，索引最后保存 `checkpointSeq`。投影失败仍保留已经提交的内存状态与 WAL，下一笔写入或重启重放后再补 checkpoint，不能用相同序号覆盖旧事务或返回普通提交失败。只有投影完成才清理 WAL。删除以墓碑追加，墓碑数达到 200 或行数超过保留上限两倍时按存活行重写文件（临时文件 + fsync + rename）；加载时按群保留 500 条普通历史，额外保留未结任务与契约证据，并修复崩溃留下的半行（截断到最后一个换行）。旧版本内联的 `messages`/`tasks`/`conversationMessages` 在 `open()` 时先写入日志再清空索引（可重跑、不丢行）；`readPersistedHub(dataDir)` 供工具与测试在不触发中断标记、不创建目录或修写文件的前提下，在内存重放 WAL 并重建完整状态。**兼容边界**：索引 schema 与旧数据文件均可加载，但旧版 Hub 直接读 `device-hub.json` 只能看到空集合，回退旧版本前必须先导出完整状态。

设备调度器的房间公平历史（最近派发时间与次数）持久化在数据目录 `task-scheduler-state.json`，250ms 防抖 + 原子写入，重启后恢复；状态损坏或缺失时按中性公平性启动，不阻塞派发。

「停止所选会话」先持久关闭新消息准入，再请求设备取消该会话的执行和排队轮次；仅收到停止确认才算成功。确认缺失时显示待确认，并继续阻止群清空/删除，失败或断线事件不能替代停止确认。停止仅作用于所选会话，不自动停止它已派发的其他成员任务；已关闭的发起会话不会被子任务结果再次唤醒。用户可停止同账号群会话，Bot 只可停止自身会话。\n\n## 群任务进度与人机交互

群聊通过账号与群绑定的 SSE 接收任务进度，执行器只上报受限的助手正文片段、阶段和工具名称；内部思考、工具参数与原始工具结果不会进入公开进度。Hub 校验 Bot、任务、attempt 与递增序号，进度仅保存在内存，持久任务状态在事务成功后通知刷新。正常连接时停用固定轮询，连接失败回退，重连重新获取权威状态。隐藏页面和切换群组关闭旧连接，慢消费者断开后重新同步。

原生 `ask_user_question` 与工具审批通过独立交互桥展示在群内；回复绑定当前任务与 attempt，审批仅支持本次批准或拒绝，不允许远端修改持久授权规则。群专属 `conversation_attention` 可在当前任务仍运行时接收人类回答；回答先持久保存，后续轮次等待原执行结束再进入同一会话，避免并发写入转录。

执行超时使用 20 分钟无实际活动窗口，人工等待期间暂停。真实模型输出和工具活动续约；状态查询与网络心跳不代表执行进展。网络断开保留已获租约的本机执行，重连后用 task/attempt/lease 认领，旧代次或已撤销权限不能恢复。Hub 重启提供有界认领窗口；设备进程重启不能证明旧执行仍在时，记录中断并要求显式继续，不自动重放副作用。活跃租约与终态结果统一写本机 `task-recovery-state.json`，在同一次原子替换中保存结果并移除租约；旧的两个恢复文件在迁移成功后清理。Hub 按 attempt 与租约代次接纳不可变结果，ACK 必须携带已提交的匹配摘要后才清除设备 outbox；永久终态冲突或已失效 attempt 使用绑定原结果的拒收回执，将完整结果原子移入持久隔离区而不重连循环。恢复状态写入 version 2（兼容读取 version 1），隔离区最多 128 条 / 2 MiB，总文件上限 8 MiB，迁移与回退见 [`device-collaboration.md`](device-collaboration.md#21-版本化契约与代码任务)；完整报告另有独立 outbox，终态确认不会误删尚未上传的报告。旧版不能读取新的统一恢复文件，回退前须备份并转换恢复状态，不能直接删除新文件。

消息内报告仍保持 16 KiB 摘录预算，完整原文在执行设备持久保存并分块上传 Hub，每报告上限 4 MiB，超限明确标记 rejected；设备报告 outbox 总预算 64 MiB、最多 128 条，并清理已确认的有限历史。任务详情与 `task_evidence` 按分片、完整哈希和版本读取，上传未完成、缺失或拒绝不冒充完整报告。上下文摘要保留约束来源、决策和证据引用，截断处明确提示查询完整记录。

## Bot 执行上下文与提示词分层

群任务的固定执行规则由本机执行能力绑定，作为 SDK 会话系统指令提供；主 Bot 协调职责取自当前派发时冻结的成员身份，不从历史消息恢复权限。Bot 自定义身份指令和执行规则在默认提示词、组合模板与恢复会话路径保持有效。每轮用户消息只携带任务数据与本机测量信息，不再追加完整的固定规则。系统层仍会随底层模型请求提供必要上下文；这项调整消除的是转录中的重复规则，不承诺供应商的 token 计费或缓存命中率。

`device_collab` 从当前 SDK 会话自动绑定来源 Bot、群和父任务；显式参数与绑定冲突时拒绝。`current_task` 可查询当前关联信息；`conversation_attention` 与 `conversation_deliver` 可省略自身会话 ID。接收 `kind=request` 的执行轮次使用 `reply_to_request` 回复，服务端从当前请求绑定回复目标与请求 ID，不能借此选择其他目的地。普通本机对话没有这些默认绑定，仍需提供原接口所需字段并通过原有授权校验。继续其他工作会话仍须明确目标，避免将任务关联误认为派发授权。

群历史通过独立的 `device-group-context` 自定义消息提供，首次为有界快照，继续会话时为增量。投递发生在 SDK 自动压缩之后，以活动分支的实际上下文为依据：只省略仍可见且内容、发送者、文本范围完全相同的片段；编辑或范围变化的片段会重新提供。历史衔接点不可见时回退到当前有界快照并标记 `gap=true`。自定义消息随 SDK 转录持久化，恢复会话后重新计算覆盖范围，不依赖“已经发送”的进程内游标；被压缩或上下文编辑移除的原文可再次提供。普通消息、工具输出及压缩摘要中的相似标记不能作为去重证据。每次模型请求还会根据最终请求上下文复核覆盖范围；如果执行过程中再次压缩，临时补回缺失片段，避免增量引用已被移除的原文。临时补全不冒充已持久化的投递记录。当前任务正文始终完整保留。

Hub 在每次原子事务提交前，统一从现有任务、消息、等待决策和交付记录投影结构化任务状态与事件。状态保留本会话初始目标、当前执行阶段、待回答问题、最近结果、人工决策证据与交付引用；追问和工作 Bot 结果不会覆盖初始目标。事件记录有序递增的 sequence，最多保留 100 条且总计不超过 64 KiB；这是当前快照加有限事件窗口，不承诺从序号零完整回放。旧数据建立无虚构历史事件的初始快照，重启后的中断按真实转换记录。

`device_collab task_state` 读取当前状态和保留事件；`task_update` 只能由本会话当前运行中的 Bot 上报进度、约束、阻塞和下一步，需提供 `expectedRevision`，版本冲突后重新读取。上报带有 `bot_reported` 来源，不能改变服务端目标、执行阶段或人工决策。派发携带任务状态快照，`current_task` 返回的也是派发时观察值；查询实时状态使用 `task_state`。这些读取不会自动派发或重试任务。

派发给模型的任务状态参考快照默认使用 12 KiB UTF-8 JSON 预算，包含选择元数据本身；初始目标、当前任务、阶段和待回答问题保留，人工决策优先于 Bot 上报和历史结果。可选记录按完整条目选择，不截断当前用户任务或 handoff。省略数量与已存在的证据截断标记显式提供；依赖被省略的约束或决策前需读取 `task_state`。极小预算装不下必要状态时保留必要字段并标记 overflow，不声称预算满足。群历史继续使用独立的 24 KiB 有界快照及增量机制，此预算不是完整供应商请求的 token 上限。

固定执行规则仅保留权限、工作区、历史信任和结果投递等长期原则。`device_collab execution_guide` 按 `coordination`、`delivery`、`review` 读取本机固定流程；首次委派或请求交付、评审前按需加载。工具权限仍由原有白名单和服务端校验，尚未按阶段动态隐藏或启用工具。

`device_collab context_metrics` 读取当前绑定 SDK 会话的上下文计数。指标区分来源历史文本、新增文本、去重文本、注入 JSON 与临时修复字节数；字节均为 UTF-8，不等同 token、缓存命中或计费。自定义消息 details 保存对应计数，模型正文不包含诊断元数据。进程内采样最多 50 个会话、每会话最近 100 条，工具返回最近 10 条及保留窗口计数；重启和淘汰会丢失采样，不影响任务或投递依据。

本阶段尚未实现模型生成的任务摘要、按阶段切换工具或供应商成本与缓存统计。去重、身份校验与请求关联由服务端负责，群消息正文及其他 Bot 产出继续作为不可信参考数据处理。

## Windows → Mac → Linux 接力

群消息支持明确目标、任务状态和结果报告。任务卡的接力入口可以把仓库、提交、分支、待验证项带给下一台设备。建议先生成 Git 提交，再让对应平台核对提交并执行真实构建与测试。

执行器为新任务创建共享、持久的标准本机对话，继续任务恢复原 Bot 会话，不切换用户正在使用的对话。普通新建对话默认独立（`independent: true`），跨页面打开保留原有过户流程；群组和微信、QQ 等渠道创建的会话默认共享，直接查看且保持原执行归属。它通过本机现有 WebSocket 协议调用智能体，不会自动批准权限，也不会替用户回答问题；问卷和审批进入现有全局弹窗，任务保持等待，人工等待期间暂停执行超时计时，回答后继续。结果包含本机系统、会话标识与实际助手报告。测试通过与否仍以命令证据为准。

AI 可调用 `device_collab` 列 Bot、列群。获得工具授权的 Bot 执行会话还可发消息、向指定 Bot 派单和传文件。持久协作通过 `conversation_create` / `conversation_list` / `conversation_read` / `conversation_send` 操作；会话创建时保存不可变的 `sourceBotId`、`sourceConversationId` 和 `parentTaskId` 委派来源，列表和工具投影保留这些字段，后续继续不改写原始归属。新建与继续均支持 `kind: request` / `requestId`，新建不允许 `reply`；`kind: request` 产生相关请求，接收者用 `kind: reply`、原 `requestId` 和请求中的 `sourceConversationId` 回复原会话，不能仅凭 Bot 名字猜测目标。Hub 持久化消息和执行轮次后再派发；子任务的终态报告可创建一轮原发起会话的继续任务，重复报告不会重复唤醒，回复或结果不会自动形成互相唤醒循环。所有写操作明确指定本机 `sourceBotId`，任务和文件另指定 `targetBotId`；普通会话不能按相同工作目录冒用 Bot，执行会话不能冒用其他 Bot。设备派单需要可信的当前父任务，上下文由执行器注入；同一根任务最多累计派发 16 个任务，接力深度最多 8。当前允许执行中的父任务主动派出下一任务，因此接力可能并行；结果回传唤醒也计入同一自动接力预算。新的人工输入可为原会话开启新的有限任务链，Bot 自己的继续消息不能刷新预算。没有正在接收的群任务时，本地 AI 不能凭空伪造父任务发起机器人任务链；可由用户先在群里 @ 发起。

### 开发交付与验收

群组对话的执行状态与开发交付分开记录：执行 `completed` 只表示本轮结束，不代表代码已交付或验收通过。Bot 使用 `device_collab conversation_deliver` 向自己的稳定对话追加交付记录：旧提交方式提供仓库、基线与完整提交 SHA；代码产物方式由当前运行任务用 `artifactTaskId` 引用同一作者先前完成的产物，Hub 派生契约、Manifest、树身份和验收条件，不能把树 SHA 当提交。检查来源固定为 `bot_reported`，不会据此执行命令或生成验证凭证。普通讨论仍可直接回复，无需交付记录。

人类可在折叠交付面板按确切版本验收，其他 Bot 使用 `conversation_review` 提交 `accepted` 或 `changes_requested`，不能自审。提交方式绑定同一提交 SHA；产物验收必须先有真实成功的 Integration 验证，并绑定 artifactDigest、integrationId、verificationDigest，按封存 acceptanceCriteria 逐项记录结果与证据。旧版本、另一产物或两个缺失的 commit 不能匹配当前交付。返工沿原群组会话继续，保留历史；验收本身不自动派发返工或执行 Git 合并。

协议定义位于 `server/protocol.ts`；Hub 在事务内验证账号、群成员、Bot 和运行任务归属，并先持久化再返回。事务回调保持同步，外部 RPC 在事务外执行；返回数据在 WAL 提交前验证可克隆性并生成独立副本，函数等不可分离结果不会造成“已经提交却返回失败”，异步期间的外部别名也不能改写返回事实。交付与审查按幂等键去重，相同键不同内容拒绝；历史有容量预算，满后明确拒绝追加。HTTP 入口为 `POST /rooms/:id/conversations/:conversationId/deliveries` 与 `/reviews`，沿用本机代理、Agent 及跨设备账号边界。

完整开发回归：`node tests/device-project-collaboration-test.mjs`（或通过冒烟跑器）。测试启动独立 Hub 与两个独立设备服务，使用真实 Git 仓库和本地确定性模型，覆盖交付、跨设备拉取、失败返工、再次验收与集成检查。同机双服务验证设备间协议和流程，不替代 Windows/macOS 实机适配验收。

继续 Bot 转录前，服务端会等待已确认停止且已释放注册的旧执行实例完成清理，即使旧 WebSocket 的关闭事件还未到达。仍注册或仍执行的持有者不会被抢占，恢复将明确失败；恢复阶段的警告同样结束本次任务，避免群组永久显示运行中。真实链路回归运行 `node --import tsx tests/device-bot-live-integration-test.mjs`（先构建服务端）：独立端口和临时数据目录启动 Hub、设备服务及本地 HTTP 模型，验证三轮不同消息进入同一个转录，并检查后续 SDK 请求携带前文。

新会话的 SDK 转录路径可能先于文件分配：运行态在归属校验后先原子登记路径，允许第一条消息随后创建文件，并在任务成功结束前验证文件存在。首轮失败后保留登记以便恢复；继续会话仍须验证已登记文件存在，不会通过创建空文件伪造可恢复转录。

网络闪断与进程崩溃分开处理：连接器退避重连时保留原本机执行与冲突锁，获得当前任务租约认领确认后恢复进度和结果回报；Hub 不因网络关闭直接终结持有租约的任务。Hub 重启允许原持有者在恢复窗口内认领；超时后统一结算中断、父结果通知与会话排队。设备进程重启发现孤儿租约只补报中断，不盲目重跑；用户可显式继续已登记的转录。撤销设备或明确停止会取消任务，停止未确认仍保持隔离。该协议避免测试覆盖的重连重复执行，不承诺所有崩溃时刻的全局恰好一次副作用。

## 群内原生问卷与一次性工具审批

`human-interaction.ts` 将当前 executor 连接的原生问卷与工具审批映射为短期随机交互 ID；原生请求 ID 留在执行设备。投影绑定任务、attempt、群、Bot 和设备，Hub 再以持久任务校验账号与群成员。每任务最多 8 项、24 KiB，回答最多 16 KiB，Hub 最多保留 128 个任务的内存投影，不持久化。每执行最多消费 256 项，达到上限后继续在执行设备本地处理。

群端 `GET/POST /api/rooms/:id/interactions` 仅接受人类用户会话（含经过既有 device-user 认证的本机 UI）；Bot 凭据不可回答。问卷支持选项、自由文本、依赖题和取消；审批仅支持批准本次或拒绝，参数和原因原文保留在执行设备，群内提示先检查原生请求。任何答案都不能携带任意 WS 帧、编辑工具参数、修改信任或记忆允许规则。executor 根据当前本地会话与待答项重新验证后，只能发送 `question_answer` 或 `tool_approval_response`（`scope: once`）。本机桥消费待答请求后，按当前 Bot 执行能力登记完整答复帧；WebSocket 控制闸门只放行这一帧并立即消费授权，拒绝跨任务、不同请求 ID、篡改答案及扩大审批范围，能力撤销时清除未消费授权。

Hub 等待绑定当前设备 WS 的 executor ACK 才移除交互；拒绝或 5 秒未确认返回 409，并保留待答项。撤回、超时、任务结束或连接失效后不能重放回答。界面随群 SSE invalidate 刷新，连接失败时退回可见页面的 2 秒轮询；切群与停用取消未完成的回答请求。原生 WS 写入后的最终工具执行结果仍由任务状态报告，不以 HTTP 转发成功替代。

回归：`tests/unit/device-center-human-interaction.test.ts`、`device-center-human-interaction-e2e.test.ts` 覆盖桥边界及真实 Hub 用户代理、relay、connector 问卷与审批回路。

## 调度、任务详情与群工作区

设备中心提供执行调度设置：并发默认 4（1–16），等待默认 32（0–128，0 表示不排队）。设置先原子保存再应用；非法或损坏的设置不会静默扩大资源上限。调度在同 Bot 与真实重叠工作区串行约束下按群公平选择，等待过久的可运行任务优先。队列显示位置和受控阻塞原因，取消绑定 task 与 attempt，停止未确认继续占用隔离槽。

群任务条可打开详情，查看持久事件、工具开始/结束状态、报告、交互和交付引用。事件采用有界异步分段日志，最多 20 段，每段约 50 条/64 KiB；淘汰、损坏、写入失败和过期游标明确反馈。工具参数仅公开结构化操作摘要，内部思考与敏感参数不进入公开事件。日志和报告读取严格校验账号、群、任务与执行代次。

群输入框支持只规划。计划任务只允许已授权工具的只读动作，未知工具与写入/派发动作硬拒绝；真实计划步骤持久就绪后，由人类点击「开始实施」创建后续执行轮次，Bot 不能自批。群工作区可绑定当前设备的已有画布，引用本身不启动；显式启动复用原画布服务并校验 graphId 与 cwd。Bot 预设读取真实本机配置，按白名单字段和 revision 部分保存；未携带的权限不清空，在途 Bot 使用权阻止热修改。群扩展插槽走宿主注册与渲染；搜索明确覆盖群名、Bot 和任务标题/ID，不宣称全文日志索引。

## 交付验证与集成流水线

提交交付沿用先审查再准备的流程；产物交付可先准备并真实验证，之后再独立验收。人类选择目标 Bot 与分支，查看隔离工作树中的差异或冲突。产物方式核对仓库、基线、补丁和 outputTree 后物化真实提交，当前要求目标 HEAD 等于产物基线，防止验证与交付对象不一致。Hub 负责授权和持久协调，Git 在目标设备受控 RPC 执行；记录绑定准确交付、契约、有效审查、Bot revision、目标基线及操作代次。

验证命令由已鉴权的账户所有者首次明确提交并冻结成计划；Bot 身份不能在代理入口丢失，也不能自行创建或修改授权计划。真实运行结果逐条核对命令、退出码、输出摘要及执行前后树，Hub 按白名单事实计算不可变凭证；成功后更换计划或重验需新建 Integration。目标 Bot 的命令权限和根占用仍生效，隔离工作树不等同操作系统安全沙盒。

最终确认必须已有真实成功验证凭证，再核对交付、审查、契约版本、目标 SHA 和工作树完整性；prepared / failed 或旧记录中的单独 verified 标志不能直接发布。为避免覆盖正在编辑的文件，目标分支必须在所有工作树中均未检出，否则明确拒绝并提示先切离；实际更新只使用比较旧 SHA 的原子分支更新，不执行 hard reset、不自动推送。取消与确认不能互相覆盖终态；清理失败记录待清理，不伪装未应用。自定义 Git merge/filter 驱动与 hooks 受到限制，工作树会话的私有路径不下发浏览器。

确认前，Hub 先持久保存绑定交付、Review、验证凭证、原设备 / Bot 身份、目标 ref 与旧 SHA 的 `publishIntent`；固定 commit 内容产生唯一候选 SHA。设备独立发布日志在 CAS 前落盘，成功回执在清理会话前保存，候选保留 ref 与回执均不随临时工作树删除。日志损坏、容量不足或写入失败在新的 CAS 前关闭准入。历史发布回执与当前分支观察分开显示，发布后的权限变更或分支移动不会抹去已观察到的效果。

启动与重连只回查日志、原仓库身份和目标 ref，不自动重发 CAS；已有意图的重复确认也只返回当前记录。界面提供手动回查，只有账号所有者可在设备明确证明 CAS 未开始或已拒绝且停止后，按同一意图恢复；Bot 不能自批恢复。已开始但无回执的 CAS 保持未知，即使目标重新等于旧 SHA，也不能据此安全重试。取消持久封闭迟到写入，但未知的过去效果仍保持未知。旧记录不补造发布凭证。文件原子保存和刷新覆盖进程崩溃恢复，Windows 目录元数据与 Git 对象在突然掉电后的完整事务持久性不作保证。详见 [`device-collaboration.md`](device-collaboration.md#23-发布意图与崩溃后回查)。

## 大文件分块传输

小包同步之外，新增确定性用户传输入口及分块引擎：每传输最多 256 MiB、2048 文件，接收端总暂存预算 512 MiB。清单固定文件哈希、大小与分块布局，分块在写入前校验序号、偏移、字节数和哈希；重复块仅允许完全相同内容，损坏块可重传。源文件版本不符必须拒绝，不能将旧清单与新内容混合。

群内可选择源/目标 Bot 和路径直接启动传输，不依赖模型理解自然语言。Hub 后台任务有界、持久化并显示进度、差异和错误；断线后显式恢复同一清单，接收器与 Hub 登记重建后继续已确认块。暂存目录绑定 Hub、设备、群、Bot、revision 和真实工作区，关闭服务保留未完成暂存，改根或换 Bot 不能复用旧包。

导入仍须预览并按 revision 确认，冲突路径逐项明确覆盖，不删除目标文件。取消先停止发送及目标在途操作，等待实际 I/O 收尾和双端 fence 确认后才释放额度；停止未确认期间阻止继续、导入及冲突任务。链接/联接、敏感路径和忽略项沿用共享路径规则。`sync_files` 复用分块链路，工具回执仅保留有界摘要。

## AI 文件同步

`device_collab` 的文件流程：

1. `sync_files`：指定群、本机来源 Bot、目标 Bot、相对项目路径；`recursive: true` 可递归选择目录。发送端校验文件并制作 SHA-256 清单，中心转发，目标暂存，返回 transferId 与差异预览。递归发现的忽略或敏感路径会跳过并通过 `exclusions` / `totalExcluded` 报告，显式选择这些路径则拒绝。
2. `transfer_preview`：重新获取目标现状与 revision。
3. `transfer_apply`：携带确认的 revision；已有不同文件默认拒绝，必须通过 overwritePaths 显式指定覆盖。

传输与任务使用目标 Bot 的固定项目根，不再维护群级设备目录绑定。Bot 配置按 Hub 地址与设备身份隔离并原子持久化；完整提示词、模型与能力配置、绝对路径保留在本机，Hub 只同步公开身份与配置版本。任务或文件操作持有 Bot 使用权期间拒绝修改或删除；暂存文件绑定 Bot 和工作区版本，切换后不能把旧文件包导入新目录。其他 cwd 的对话不能借用该 Bot 的项目权限。拒绝路径穿越、软链接/目录联接、`.git`、`.env*`、常见凭据路径与 `node_modules`；目录递归须显式启用，扫描最多 4096 个条目；不会删除目标文件。单包最多 8 MiB、128 个文件，目标暂存总预算 32 MiB。数据目录必须位于项目根之外。

覆盖前重新核对目标基线，预览之后发生修改时拒绝写入。多文件导入不是跨文件事务，会返回实际 applied、unchanged、rejected；失败不自动重试已进入应用阶段的传输。暂存文件生命周期绑定本机服务，重新连接或重启后可能失效，请重新发送。同步不自动运行脚本、安装依赖或重启服务。

该传输提供类似 SFTP 的按需文件/目录传递，但使用现有设备连接，无需配置 SSH/SFTP；不是持续目录镜像或删除同步。详见 [文件传递边界](device-file-sync.md)。本轮适合源码和小型测试素材，大型构建产物建议使用 Git LFS 或专用制品存储。Git 仓库传递和文件包同步不会把其他机器的绝对路径直接用于本机。

## 无域名设备网页入口

默认只开放 Hub 主端口即可。每台设备卡都有固定入口 `/device/<设备ID>/`；可选别名（例如 `mac`、`office`、`linux`）仍先校验登录与设备归属，再进入相同单端口访问链路，链接可带 `#s=对话ID`。路径模式不消耗设备端口池，即使旧部署仍设置 `PI_DEVICE_HUB_PORTAL_PORTS`，也不会监听这些额外端口；已有别名和端口记录保留。

需要恢复旧版独立来源时，设置 `PI_DEVICE_HUB_PATH_ACCESS=0`，再配置 `PI_DEVICE_HUB_PORTAL_PORTS=50002,50003,50004,50005` 或设备域名。旧端口模式要求开放并成功监听端口池中所有端口。无端口的路径分配记录切回旧模式时会按需分配端口，不重用其他设备的历史端口。

## 局域网优先访问

从本机 pi-web-ui 的设备卡选择「打开设备」或「远程桌面」时，两者共用局域网优先协商。Hub 先校验账号与设备归属，再通过已认证连接器的 `device_lan` RPC 签发短期设备通道授权。被访问设备按需监听一个随机 TCP 端口，发布最多 16 个 RFC1918 IPv4 候选地址；本机服务并行执行有总时限的真实 TLS 握手及健康请求，选择首个通过身份验证的地址。是否直连以实际可达和密钥验证为准，不按 IP 文本前缀或同网段猜测。

直连成功后，页面资源、对话 WebSocket、上传和桌面 JPEG/输入均经局域网传输，不再绕 Hub，也不经中继 JSON/base64 分片。Hub 仍负责授权。通道使用 TLS 1.2 PSK 与每次授权的 32 字节随机密钥，密钥只存在于 Hub 和两端服务进程，不传给浏览器。随机监听端口仅接受有效 PSK，并复用连接器的路径白名单、本机 Host/Origin 重写和凭据注入；本机配置与系统权限申请接口仍不能从远端访问。无需把主 Web 服务改为局域网监听。

浏览器打开的是本机 `/device-lan/<设备ID>/__open` 一次性票据包装页，远端内容仍处于不含 `allow-same-origin` 的沙箱，通过随机能力路径加载；复用 Hub 路径入口的 HTML 重写、CORS、CSP 和头部净化，支持本机反代路径前缀。授权到期按需续签，连接器断开或解除绑定时撤销相关通道和连接；取消单次授权只撤销该次密钥，不影响其他连接。浏览器独立标签页与 Electron 独立窗口也使用相同选择逻辑。

没有可达私网地址、端口受阻或对端版本尚不支持时，自动使用原 Hub 票据。已打开页面断线后先重新协商局域网，仍不可达才申请新 Hub 票据并回退。直接在独立 Hub 页面打开的固定设备入口仍沿用 Hub 中继；局域网探测由已配对的本机 pi-web-ui 服务发起。事实源为 `server/device-center/device-lan.ts`（设备加密通道与并行探测）、`device-lan-access.ts`（本机隔离入口）和 `web/src/device-access.ts`（统一打开入口）。

## 浏览器远程桌面

设备卡提供独立「远程桌面」入口，复用账号归属校验、一次性票据和设备访问链路。实现参考 [krd](https://github.com/zzjcool/krd) 的原生捕获与键鼠注入方式，画面和输入优先复用上述局域网通道，不可达时使用既有 WebSocket 中继，不进入聊天快照或模型上下文。无需安装 noVNC 或 VNC 服务，也无需固定桌面端口。

远程桌面默认关闭；本机启用偏好持久化到 `<dataDir>/desktop-settings.json`，写入成功后才更新运行态。设置 `PI_DEVICE_DESKTOP=1` 会强制启用，本机关闭偏好不能覆盖该环境变量。只读权限由后端强制执行，只读连接的键鼠输入不会注入被控电脑。打开远程桌面页面会自动以可控制模式连接已启用且可用的设备；用户可在顶部切换只读。顶部使用紧凑单行工具栏，设置、macOS 权限指引和文字输入默认折叠，画面占用剩余高度；全屏保留工具栏。设备未启用或不可用时展开设置，启用成功后自动连接，连接失败显示具体原因。远程桌面票据兑换的 Hub 包装页允许跨来源嵌入，内部能力页继续使用不含 `allow-same-origin` 的沙箱和随机能力路径；普通设备网页包装页仍仅允许同源嵌入。旧独立域/端口桌面票据换为 Hub 路径票据后加载，避免浮窗依赖第三方 Cookie。

对话 Web UI 的设备目录和群成员远程桌面入口统一在 App 共享悬浮窗内打开，支持拖动、右下角缩放、最小化与全屏；点击聊天区域或切换视图不会关闭窗口，最小化保留连接。窗口内 iframe 仅授予 scripts/forms/pointer-lock，禁止 `allow-same-origin`，沿用服务端签发的隔离能力 URL 与既有授权流程。点击「独立标签页」会重新申请一次性票据，成功打开后关闭悬浮窗；弹窗被拦截或授权失败则保留窗口并显示错误。Electron 使用已有 `openHub` 桥打开独立窗口，同样仅在桥返回成功后关闭悬浮窗。Hub 独立页面的入口行为保持不变。

| 平台          | 捕获与输入                                                                      | 运行条件                                                   |
| ------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| Windows       | GDI 捕获 + SendInput 键鼠注入                                                   | 可访问的本机图形桌面会话                                   |
| macOS         | Swift 原生助手：ScreenCaptureKit `SCScreenshotManager` 捕获 + CoreGraphics 输入 | macOS 14+、Swift CLI；屏幕录制权限，控制时还需辅助功能权限 |
| Linux X11     | ffmpeg 捕获 + xdotool 键鼠注入                                                  | 可访问的 X11 会话与 ffmpeg；仅查看时可不安装 xdotool       |
| Linux Wayland | 暂不支持                                                                        | 后续需接入 Desktop Portal 的屏幕共享与远程控制能力         |

旧 `PI_DEVICE_VNC_*` 环境变量已退役，不再用于地址、端口、密码、路径或浏览器组件配置。

## 验证与边界

群聊的交付面板以「需要我处理」汇总待验收、审查要求修改、执行失败和中断，默认只显示这些事项，可切换全部对话。分类由 `web/src/group-attention.ts` 根据执行状态和精确交付 ID/提交 SHA 对应的最新审查派生；无显式人工问题时，执行中、已关闭和无交付的普通问答不生成待办，不解析报告文字猜测阻塞。Bot 可通过 `conversation_attention` 登记 `answer` / `decision` 和问题，必须绑定自身当前运行任务与原会话；问题保存在会话中，刷新后仍可展示。群成员通过「回复并继续」提交带 `attentionId` 的人类答复，Bot 停止运行后才能答复；答复、继续任务和清除待办在同一事务提交。重试复用幂等键，旧问题 ID 拒绝，其他 Bot 不能代答或设置他人问题。现有验收操作继续绑定原交付版本。

`tests/device-bot-live-integration-test.mjs` 经真实 Hub、设备运行时、SDK 与本地确定性 HTTP 模型验证主 Bot 免 @ 接收、工具委派关联请求、执行结果回到原会话，以及补充消息继续同一执行者会话；同时验证 SDK 工具提问落盘、人类答复清除待办并恢复原会话、重复请求只创建一次继续任务及旧问题拒绝；它验证传输与执行链路，不证明真实模型的任务理解与选择质量。产物通过 `tests/lib/ensure-build.mjs` 统一准备。

相关测试位于 `tests/unit/device-center-*.test.ts`，覆盖账号隔离、配对、真实回环 HTTP/WS 中继、任务协议、@ 选择、文件完整性及冲突等。测试不调用真实模型，不能替代真实三平台端到端验收。公共运营还需部署监控、备份、账号恢复与服务容量验证；本次代码未自动创建域名、购买服务器或开放公共注册。
