# 手机面板功能详解 这份文档讲的是**手机端 `/bridge` 面板**上这批较新的能力:怎么看输出、怎么插入消息、怎么分支对话、怎么回答 agent 的提问。 安装、令牌、网络这些在 [README.md](README.md);快速上手在 [GUIDE.zh.md](GUIDE.zh.md);这份文档只讲功能本身,比那两份细一点。 ## 怎么读 - **怎么用** —— 可以直接照做的步骤 - **边界** —— 我明确没做、或者做不到的事 - **⚠️** —— **还没在真机上验证过**的。我不想把「测试是绿的」说成「你手机上一定行」 --- ## 1. 看它输出:思考 / 复制 / 停止 ### 1.1 思考(折叠 + 流式) **怎么用**:什么都不用做。模型这一轮如果思考了,回复**上方**会出现一个折叠块,标题写着「思考(正在输出)」,内容是**边想边流进来的**。点标题展开;这一轮结束,标题变回「思考」。 **行为细节** | 情况 | 表现 | |---|---| | 模型思考了 | 折叠块出现在回复**上方**,默认收起,实时增长 | | 模型没思考(大多数简单问题) | **不出现空块** —— 不会假装它想了 | | 这一轮结束 | 标题去掉「(正在输出)」 | **边界** - 思考块**只是给你看**:不参与对话内容,不会回传给模型。 - **Chatbox 里永远看不到** —— OpenAI 协议没有这个字段。这不是我偷懒,是协议层没有位置放它。 - ⚠️ 真机上「边想边长」这个效果我**没看过**,只验过服务端会把 `reasoning-delta` 单独转发、以及只有面板这个调用点接它。 ### 1.2 复制 **怎么用** - **整条消息**:消息右上角的「复制」 - **一段代码**:代码块右上角的「复制」 **为什么你的手机上可能显示「长按选择」** 这个面板是 **http** 的,而浏览器**只在 https 或 localhost** 才提供现代的剪贴板 API。所以手机走的是**旧式复制**(临时 textarea + `execCommand`),而且必须在**你按下按钮的同一个同步节拍**里执行 —— 程序化复制需要你那一下手势。 **如果没复制成功,按钮会写「长按选择」而不是「已复制」。** 这是故意的:按钮谎报成功的话,你粘出来是上一次的内容,然后会去怀疑模型。 **边界**:复制的是**文字**。图片、附件不能复制。 ### 1.3 停止正在输出的那一轮 **怎么用**:输出中时,发送按钮下面会出现「停止」。点它。 **行为细节** - **默认保留你排队的话**(就是你用「排队 / 插话」插进去的那些)。停的是「这次的输出」,跟「扔掉你已经打好的字」是两件事。 - 空闲时点它是**无效的** —— 所以它只在你真的在输出时出现。后端也不会谎报「我停了」:`agent.cancel` 在没有活动时是官方文档写明的 no-op,所以上报的是**调用之前**观察到的状态。 - ⚠️ 真机上没验过。 ### 1.4 回复里的链接和文件路径 **链接**:只有**明确写了协议**的才可点 —— `http://` / `https://` / `mailto:`。 > 之前**任何**相对路径都会被解析成面板自己域名下的一个 http 链接,所以点开是 404,地址栏里是一堆百分号。路径不是 URL。 **文件路径**:回复里出现的**本地路径**会渲染成一个可点的**小标签**(显示文件名,长按/悬停看全路径): | 形式 | 例子 | |---|---| | 裸路径 | `C:\Users\...\a.txt` | | 引号路径(可以有空格) | `"D:\我的文档\第1卷 奇点.txt"` | | DSH 引用形式 | `@"C:\Users\...\a.txt"` | | 内联代码里的路径 | `` `C:\Users\...\a.txt` `` | | UNC 共享 | `\\server\share\a.txt` | **点它做什么**:打开**面板自己的文件查看器**(有扩展名 → 直接预览;没有 → 当目录列出来)。 **为什么不是「用系统打开」**:这个页面**没法**把路径交给操作系统 —— 浏览器的安全边界就在那里。所以它不假装能:不给你一个必然 404 的链接,而是给你一个**真的能打开**的查看器。 **边界** - 只认 Windows 盘符路径和 UNC。**POSIX 绝对路径**(`/home/x/a.txt`)**不**会被做成标签 —— 因为 `/foo/bar` 和 URL 的路径长得一样,认错会把正常链接弄坏。 - **URL 不会被误伤**:`http://example.com/a/b` 里面确实含有 `p://example.com/a/b` 这种「长得像盘符路径」的东西,所以裸路径的匹配会检查**它前面那个字符** —— 是字母数字就说明这是 URL 的尾巴,不当路径。这一条有专门的测试(认错成标签比原来的 bug 更糟)。 - 路径的判定是**位置猜测**(有扩展名当文件,否则当目录);两个方向都是一步之遥。 --- ## 2. 插入消息:排队 / 插话 / 修改 / 撤销 / 立即发送 ### 2.1 两个边界的区别(这是 harness 自己的区分,不是我编的) | 按钮 | 落到哪 | 作用 | 什么时候用 | |---|---|---|---| | **排队** | `next-turn` | 等**下一轮**再发 | 你在它跑的时候想到别的,但不想打断 | | **插话** | `next-step` | 插进**正在跑的这轮**,影响它接下来的动作 | 你看它跑偏了,要马上拉回来 | 两个都是**先放进去、不当场发** —— 不会唤醒模型。 ### 2.2 怎么用 1. 在输入框打字 2. 点 **排队** 或 **插话** —— 文字从输入框移进上方的「**待发送(N)**」托盘 3. 托盘里每条有三个按钮: | 按钮 | 做什么 | |---|---| | **立即发送** | 把它从队列拿出来,以「唤醒」的方式马上发出去 | | **编辑** | 就地改,**保留它在队列里的位置**,保存 / 取消 | | **撤销** | 从队列删掉 | ### 2.3 为什么它和电脑端是同步的 **因为我没有实现同步 —— 本来就不需要。** 那个队列就是 harness 的 `agent.inbox`: - **电脑端 GUI 读的是同一个东西** - 它的每一次改动(插入 / 修改 / 删除)都是**持久的 session 事件**(`agent/inbox/spliced`) 所以手机和电脑读写的是**同一份列表**,会话日志是它们达成一致的方式。如果我在插件里自己存一份队列再手动同步,那它第一次遇到「电脑端改了别的东西、手机看不见」时就会开始漂。有一条测试专门断言**插件里没有自己的队列副本**。 ### 2.4 边界 - 会话**不在内存里**时,排队会明确报错(409),而不是悄悄塞进一个你看不见的队列。 - 只能编辑**文本**消息。 - ⚠️ 双端同步的真机表现(在手机上插一条,然后去电脑端看)我**没看过**。服务端读的确实是 harness 的 inbox,这一点有测试;但「电脑端界面会不会立刻显示」我无法从这个环境验证。 --- ## 3. 分支对话(fork) **怎么用**:抽屉(☰)里每段会话下面有一个「**分支**」按钮。点了之后它会: 1. 从那段会话的**最后一个完整回合**切一刀 2. 建一段新的会话,带着切点之前的历史 3. 刷新抽屉,然后**直接打开新的那段** **切点规则(重要的部分)** - 切点**只会落在「跑完的回合」上**。不会把半个来回塞进新会话 —— 那会让新会话一开头就有「有提问没回复」或者「有工具调用没结果」,而且症状要很久以后才以「这个 agent 好像记错了自己的历史」的形式出现。 - 最后一个回合**之后**记录的东西(标题、投递标记、压缩检查点)**留在原来那段**,不会跟着漂过去。 **它继承什么、不继承什么** | | 是否继承 | 为什么 | |---|---|---| | **agent preset**(思考/工具的组成) | ✅ 跟着源会话 | 让分出来的对话还是原来那套组合 | | **沙箱档位**(只读 / 可写) | ❌ **不继承**,按配置里的默认(通常只读) | 分支是**本插件新建的会话**,所以按新会话的规矩钉。继承的话,一个更宽的权限就能靠「复制」悄悄传播出去。真的需要,在手机上对**新的那段**单独申请 | **边界** - 源会话**从头到尾没跑完过一轮** → 明确报错,而不是造一个没有历史的新会话。 - 分支副本和原来那段**之后各走各的**(这是分支的意义)。 - ⚠️ 真机没验过。 --- ## 4. 问答卡片(agent 反问你的时候) **怎么用**:agent 用了「问你一个问题」这个工具时,**那一轮会停下来等你**。答案会出现在**输入框的位置**(输入框被卡片顶掉)。 - **选项**点一下选中;多选的题变成开关,能选好几个 - **每题都能自己写**:底下「也可以自己写」直接打字 - **提交**:一次请求里所有题**都答完**按钮才亮;没答完会告诉你还剩几道 **几个刻意的行为** - **就算问题不属于你正在看的这段会话,它也会冒出来** —— 卡片上写「来自会话 xxx」,点一下跳过去。这是故意的:审批不显示只是让你去电脑上答,而**问题不显示会让一整轮停在那里**,手机上却没有任何提示。 - **和电脑端是「抢」的关系**:谁先答就算谁的,另一边自动消失。**电脑端那个「跳过」是一次真实的回答**,会正常结束那一轮。 - 打字的时候**卡片不会被刷新**,键盘不会自己收回去(后台每 2 秒的轮询只在内容真的变了时才重画)。 **边界**:**Chatbox 里永远看不到** —— OpenAI 协议里根本没有「问一个问题」这种东西,那一轮会一直卡着。要用问答就用面板。 --- ## 5. 上下文占用与压缩(顺带) 点 📊:显示占用百分比、`~已用 / 窗口`、以及系统提示词 / 工具定义 / 对话消息的拆分。 - 这段会话**没载入内存**时,点「读取占用」才会载入它 —— 光看一眼不该有副作用 - **压缩上下文**调的是电脑端 `/compact` 同一个后端,会在电脑端显示成一条检查点 - 明细三项之和**可能和上面的占用对不上**,这跟电脑端一样(占用是实测值,明细是估算) --- ## 6. 我自己验证到哪一步(诚实清单) | 功能 | 服务端 / 逻辑测试 | 真机验证 | |---|---|---| | 思考折叠 + 流式 | ✅ | ⚠️ 没有 | | 复制消息 / 代码块 | ✅(含失败路径) | ⚠️ 没有 | | 停止输出 | ✅ | ⚠️ 没有 | | 插入 / 编辑 / 撤销 / 立即发送 | ✅(含「插件没有自己的队列」) | ⚠️ 没有 | | 分支对话 | ✅(含切点规则 7 条) | ⚠️ 没有 | | 问答卡片 | ✅ | ✅ **有**(手机端答过两次) | | 上下文面板 | ✅ | ✅ **有** | **今天暴露的三个 bug 全部只有真机能发现**(卡片被按会话过滤埋掉、轮询每 2 秒踢掉键盘、空白答案批次),服务端测试一直是绿的。所以上表里那些 ⚠️ 请当成**真的还没验**,不要当成「应该没问题」。 --- ## 7. 出问题时怎么查 | 探针 | 看什么 | |---|---| | `http://127.0.0.1:19387/setup` | 电脑上的设置页:令牌、局域网地址、设备白名单、配对验证码 | | `/bridge/api/questions?session=` | `youAre`(你手机说自己在看哪个会话)+ `recent`(最近的问题请求走到哪一步了) | | `/bridge/api/queue?session=` | 当前的待发送队列(两份边界各有什么) | | 页面顶部的**红色条** | 未捕获的 JS 错误。手机上的静默失败和「按钮不存在」长得一模一样,所以有了它 | | 主机日志里 `openai-bridge:` 开头的行 | 挂载、面板失败、审批、配对等 | 排查顺序建议:**先看红条**(有没有 JS 报错)→ 再看对应的 `api` 探针(后端到底返回了什么)。 --- ## 8. 涉及的文件与提交 **服务端(宿主插件)** | 文件 | 负责 | |---|---| | `lib/index.js` | OpenAI 端点、agent 获取、模型选择、上下文、队列、分支 | | `lib/panel.js` | 面板路由 + 整个前端页面(在一个模板字符串里)+ 审批/问答/队列 | | `lib/permission-gate.js` / `permission-ui.js` | 权限档位与验证码 | | `lib/model-control.js` / `model-ui.js` | 模型与思考强度 | | `lib/context-control.js` / `context-ui.js` | 上下文占用与压缩 | | `lib/client-gate.js` | 设备白名单与配对 | | `lib/setup.js` / `setup-page.html` | 只对本机开放的设置页 | | `lib/panel-fab.js` | 右下角按钮簇 | **提交**(这一批) | 提交 | 内容 | |---|---| | `362968f` | 思考折叠 + 流式、复制、停止 | | `73ecb48` | 插入 / 编辑 / 撤销 / 立即发送(与电脑端同一份队列) | | `60e0f44` | 分支对话 | **测试**:`test/` 下十三套,**639 条断言**。新增的是 `output.test.mjs`(23)、`queue.test.mjs`(28)、`fork.test.mjs`(23)。 --- ## 9. 已知限制(一句话版) 1. **Chatbox 看不到思考、问答、审批** —— OpenAI 协议里没有这些通道,要用就用面板。 2. **复制在 http 下走旧式路径**,失败会明说。 3. **分支出来的会话不继承沙箱档位**(只继承 agent preset)。 4. **设备白名单按 IP** —— 手机换网段或 DHCP 重新分配就要重新配对。ARP 欺骗可以冒充已批准的地址,所以它只是「让泄漏的 token 单独不够用」,不是防火墙。 5. **`read-only` 保护的是完整性,不是机密性** —— 它不阻止读文件、执行命令、访问网络。