Releases: xing-shuyin/pi-web-ui
Release list
v0.82.0
Added
- page-picker 扩展:「发送什么」改成逐项多选(另配 6 个预设)——原来只有三档详细度(精简/标准/完整),要么一起多、要么一起少;实际用起来常是「这次只要源码位置」「这次只要样式,别的别发」。现在设置页可以逐项勾:页面上下文 / 定位信息(选择器+尺寸)/ XPath 与 DOM 路径 / 源码位置(React/Vue 文件:行号 + 组件链)/ 文本 / 命中的 CSS 规则 / 计算样式 / HTML 骨架(元素截图仍是单独一项)。没勾的在采集层就不采,不只是渲染时丢掉——生成 HTML 骨架、读 CSSOM 这些本身就有开销,顺手也把这点省掉。预设覆盖常见组合:精简 / 标准(默认)/ 完整 / 只要能改对地方(选择器+源码)/ 只排查样式(命中 CSS+计算样式)/ 只看文案结构(文本+骨架),一键勾好之后还可以手动增减(预设同时决定采集深浅:文本长度、骨架深度、选择器深度)。老设置(只有
detail)升级后按原档位预勾,行为不变。- 回归:
sectionsForDepth/normalizeSections/presetForSections单测、采集层「没勾就不采」单测(jsdom)、渲染层「只输出勾选项」单测、设置页多选 UI 单测(勾选真的落盘 / 预设联动 / 全不勾会提示并回落标准组合)、E2E 用自定义组合真投递一遍。
- 回归:
暂无其他未发布内容。
i18n
- 本版无文案增量(相对 v0.81.2,已核查)。
v0.81.2
Fixed
- page-picker 扩展:修「pi-web-ui 页面明明开着,却报『没找到打开的 pi-web-ui 页面』」——0.2.0 查找目标标签页时传的过滤条件是
["<地址>/*", "<地址>"],而裸地址(没有路径的 origin)不是合法 match pattern:真 Chrome/Edge 的chrome.tabs.query会直接抛Invalid url pattern 'http://localhost:8787',那个异常被 catch 成了「没找到页面」,于是拾取结果只能退化成「复制到剪贴板」(选项页「测试连接」里的同名查询也一并修)。现在查询只用 origin 级模式(http://localhost:8787/*),路径前缀仍由tabMatchesBase严格复核(子路径反代、前缀相似的站点都不受影响)。- 这个 bug 能活着发布,是因为单测/E2E 用的是假 chrome,它不校验 match pattern:现在假 chrome 也按真 Chrome 的规则校验入参(
isValidMatchPattern),这类坑会直接挂在单测上。 - 另加一条装真扩展的 E2E(
tests/page-picker-edge-ext-test.mjs):实测 Edge(152,headless)仍接受--load-extension,所以能在真chrome.*上把「拾取 → 投递 → Markdown 真的落进 pi-web-ui 输入框」跑一遍(没装 Edge 自动 SKIP)。
- 这个 bug 能活着发布,是因为单测/E2E 用的是假 chrome,它不校验 match pattern:现在假 chrome 也按真 Chrome 的规则校验入参(
- page-picker 扩展:修「在 pi-web-ui 页面上点图标没任何反应」——绑定浮条原来完全依赖 background 的 MAIN world 探测(
__piWebUiHost//api/health),那个注入一旦被 CSP/权限/环境挡住,就会静默回落到拾取器,用户看到的就是「新功能没出现」。现在:探测不可用时也照旧注入浮条,浮条自己再认一次页面(同源/api/health+ 标题/输入框 DOM 兵形),认出是 pi-web-ui 就正常问「要不要绑成服务地址」,不是就自己退场并请 worker 补注入拾取器——「点了图标什么都没发生」在三条路上都不可能发生;路由决策同时打进 service worker 控制台,方便排障。
暂无其他未发布内容。
i18n
- 本版无文案增量(相对 v0.81.1,已核查)。
v0.81.1
Added
- page-picker 扩展:在 pi-web-ui 页面上点一下图标就能绑定服务地址——远程/局域网部署时地址不固定,原来只能去选项页手打地址再点「授权该地址」。现在点扩展图标会先认当前页:页面上有宿主动作桥
__piWebUiHost即认定,老版本则退一步探一次同源/api/health({ok, piVersion}才算数,所以任何「所有路径都回 200」的站点都不会被误认);认出是 pi-web-ui 就在页面底部弹浮条问「要不要把它设为拾取服务地址」,点一下即可(地址/端口/子路径全部按当前页面算,?token=、hash、尾斜杠都会归一掉),已经是当前地址时只说明现状不再多问,浮条上另有「在本页拾取元素」(开发 pi-web-ui 自己时用得上)。缺那一个 origin 的授权时,浮条会提示并给一个「打开设置页授权」按钮 ——chrome.permissions.request必须在扩展自己的页面里点(网页上的按钮给不了浏览器要的手势),那个页面带?bind=预填地址、一键授权 + 绑定。普通页面点图标的行为一点没变(仍是进入拾取模式),也绝不静默改地址(改前一定在页面上问一次)。回归:detectPiWebUi/bindView单测 + service worker 分流单测 +?bind=面板单测(真 options.html)+ E2E(真 pi-web-ui 页 / 真夹具页各自认定 + 浮条绑定后照常投递)。
i18n
- 本版无文案增量(相对 v0.81.0,已核查)。
v0.81.0
Added
-
宿主动作桥新增
compose():把内容放进输入框草稿(宿主 API v1 → v2)——startChat()是「新建对话并把一段话直接发出去」(脚本化,prompt立刻发),但「元素拾取」这类场景需要的是人在环中:内容先落进输入框,用户补一句「这三处间距不一致」再自己发。现在window.__piWebUiHost.compose({ text?, attachments? })干这件事,与startChat的差别是不要求连接就绪(草稿是本地状态,断线也能先攒着)且输入框没挂载时明确拒收(返回 false,不静默丢)。合并语义复用「撤回消息放回输入框」的同一个纯函数(空则填入、非空追加、绝不覆盖用户正在打的内容);附件按 path+mode+行区间去重,与手动 attach 的口径一致。定义见web/src/plugin-host.ts+web/src/composer-bridge.ts(草稿在 ChatInput、附件在 App,两处各自注册自己那一半)。 -
浏览器扩展「网页元素拾取」(
plugins/page-picker):在开发中的网页上点选元素,整理成 AI 能直接动手的上下文,一键注入 pi-web-ui 输入框(Alt+Shift+P/ 扩展图标 → hover 高亮 → 点击拾取,Shift+点击多选,Esc退出,Ctrl+Enter直接发送)。采集的不是截图而是能让 AI 一次改对的东西:React fiber 里的组件源码位置(Card.tsx:18:5+ 调用链)、Vue SFC 文件、命中的 CSS 规则源文件与行号(Vite dev 的<style data-vite-dev-id>的 textContent 与源文件逐字对应,行号可精确反推)、计算样式里只保留与默认值/继承值不同的项(现场造同 tag 空元素当探针比对,一个真实卡片通常只剩 3~5 行而不是 300 个属性)、短且唯一的定位串(#card>section.card> 兜底全:nth-of-type,兄弟冲突会在父级内补:nth-of-type收窄)、HTML 骨架、可选元素截图(走对话附件,不是把 base64 塞进正文)。详细度三档(精简/标准/完整)在采集层就生效。失败一律有兜底:没开 pi-web-ui 页面 / 版本过旧 / 输入框未就绪 / 截屏失败,都会把 Markdown 复制到剪贴板并说明原因,绝不出现「点了添加什么都没发生」。- 装法:下载
page-picker-extension.zip(打 tag 由.github/workflows/extension-release.yml自动出包,含 CRC 自校验;zip 打包器是自写的零依赖实现,Windows 上也能出同样的包)→ 解压 →chrome://extensions开发者模式「加载已解压的扩展程序」。也可以从源码npm run build:extension后加载plugins/page-picker/extension/。远程/局域网部署只需在选项页多点一下「授权该地址」。
- 装法:下载
-
pi-web-ui命令行/插件市场不适用于浏览器扩展:那条通道装的是服务端插件(<dataDir>/plugins/<id>/),装不了浏览器扩展 —— 这一点在根 README 与插件 README 里都写明了,免得有人对着pi-web-ui install找半天。 -
legado-web 插件:阅读页章末导航(读到底就能翻章)——阅读页原来只有顶部工具栏有「上一章 / 下一章」,正文读到页面底部什么也没有,这一章看完想接着读必须滚回顶部。现在正文末尾多一条「← 上一章 / 目录 / 下一章 →」(跟在正文下面,带《书名》· 第 n/总 章),换章后自动回到页面顶部;第一章「上一章」、最后一章「下一章」置灰并写明「已是最后一章」(顶栏同名按钮同规则,不再点了没反应),章末「目录」展开目录并回到顶部。回归:
tests/unit/legado-chapnav.test.ts(禁用态与文案边界:首章/中间章/末章/单章/空目录)+tests/legado-web-reader-test.mjs(真浏览器 + 3 章假书源,钉住导航条长在正文末尾、换章回顶、末章置灰、目录展开)。
Fixed
-
输入框里自动折行的长草稿,按
↑会误触历史回溯、打断正在进行的编辑(issue #127)——历史回溯的边界判定原先只看逻辑行(value 里有没有\n),可输入框是按宽度自动折行的:一段没有换行符的长草稿在界面上明明是多行,却被当成「只有一行」,光标停在第三行按↑也直接切到上一条历史(↓能切回来、草稿没丢,但编辑被打断,想改上一行只能动鼠标)。现在改按视觉行判定:新增web/src/caret-visual-line.ts,把与折行相关的样式(字体 / 行高 / 字距 /white-space/overflow-wrap)拷到一个隐藏镜像 div 上,塞入「光标前的文本 + 一个零宽标记」,量标记的offsetTop—— 与 textarea 自身的折行规则一致(pre-wrap+break-word),于是「光标上方 / 下方还有没有可见行」直接比像素:首视觉行 ⇔ 标记贴顶,末视觉行 ⇔ 与文末标记同高。拿不到布局的宿主(SSR / jsdom / 未挂载 /display:none)回落到旧的逻辑行判定,宁可少一次精确判定也不误判成「可以翻历史」;有选区、输入法组合中一律不碰历史。功能本身没退化:光标真的走到首 / 末视觉行后照旧翻历史,Esc/↓仍能回到草稿。回归:tests/unit/caret-visual-line.test.ts(像素折算 + 无布局回落 + 选区 / 空输入框边界)+tests/composer-history-test.mjs(真浏览器:折成 4 行的无换行草稿要按满 4 次↑才切历史、前 3 次逐行上移且内容不变、↓切回草稿、换行草稿与单行草稿的老边界行为不变、测量节点不残留草稿正文)。 -
MCP 桥把非文本内容块静默丢掉:截图 / 图像生成 / 图表类工具一律返回空串——
server/mcp-bridge.ts的McpClient.call()以前只拼type === "text"的块,image与resource块被直接丢弃,模型既不报错也拿不到任何东西,工具形同虚设(同一browser_screenshot调用:桥内得到"",桥外直连 stdio 是 22840 字符的image/png)。现在按块类型保序映射:image原样透传成 SDK 的ImageContent({type,data,mimeType},进会话后由 SDK 的normalizeToolResultImages统一缩放,超大图不会再让 provider 整段报错);文本型resource(resource.text)当文本透传——MCP 的EmbeddedResource分 TextResourceContents 与 BlobResourceContents 两种,前者是真实正文(filesystem 类 MCP 的 read_text_file 就走这条),退化成「已跳过」等于把文件内容吞掉;PDF 这类 blob 与 audio 退化为「mimeType + 约 N 字节,无法内联」的提示(SDK 内容联合只有 text/image/thinking/toolCall,没有 blob 载体);纯文本结果仍返回拼接字符串(老形状不变,不破坏既有调用方)。回归:tests/unit/mcp-bridge.test.ts(image 逐字保真 / 文本资源不丢正文 / blob 退化提示 / 混合保序)+tests/mcp-bridge-test.mjs(e2e 握手 8 tools)。限定:Web UI 的工具卡按既有行为只渲染文本(工具结果里的图片在序列化时是[image result]),图片会进模型上下文但不在 tool 卡里显示。 -
命令行
pi-web-ui install <插件> --force之后插件一直「不存在」:CLI 装插件是先整目录删掉再拷新的(install --force的 rm→cp 窗口),撞上这个窗口期的一次插件扫描会把插件当成「已卸载」反激活;而反激活时没把插件从attempted集合里摘掉,目录回来后永远不会再激活——插件的 HTTP 路由(如 legado-web 的/plugins-api/legado-web/proxy)与 AI 工具在本进程内彻底消失,前端只报「代理请求失败 404 」,CLI 承诺的「服务运行中刷新浏览器即可加载」失效,必须重启服务才恢复。现在反激活会摘掉attempted并推进 epoch(重新import拿到磁盘上的新代码、浏览器也重拉插件 client bundle),刷新浏览器即自愈。回归:tests/unit/plugin-manager.test.ts(目录消失→回来必须重新激活且用新代码)+tests/plugin-test.mjs(真实 HTTP 路由的 rm→cp 窗口自愈)。
i18n
- 本版无文案增量(相对 v0.80.2,已核查)。
v0.80.2
Added
- 新官方插件 legado-web(📖 阅读):把 Legado / 阅读 的读书链路搬进 pi-web-ui——搜索 / 发现 / 详情 / 目录 / 正文,书源 JSON 与安卓版兼容,另带书源导入、废源检测与清理。插件自带内嵌前端需要的一切后端:跨域 + GBK 代理、本地存储(书源 / 书架 / 阅读进度只落数据目录
<dataDir>/legado-web/,不写浏览器 localStorage)、静态托管。安装pi-web-ui install xing-shuyin/pi-web-ui/plugins/legado-web(插件市场里也可一键装),刷新后顶栏多一个 📖 tab。- 顺带给 AI 配了修源接口:四个 agent 工具
legado_rules(规则速查)/legado_book_sources(读书源文件、只改坏掉的那几个字段)/legado_source_probe(逐步跑链路,回报每步请求、HTTP 状态、用到的规则与失败明细)/legado_run_rule(拿真实页体试一条规则再落盘);阅读页与书源页的「🤖 AI 修复源 / AI 新建书源」按钮把现场直接发给 AI 并开一个新对话(工作目录限定在插件数据目录)。规则引擎跑在 worker 里,同步 JS 规则(java.ajax等)走 SharedArrayBuffer 桥。
- 顺带给 AI 配了修源接口:四个 agent 工具
- 插件 → 宿主动作桥
window.__piWebUiHost:插件 client bundle 是裸 ESM,import 不到应用模块,之前只能往宿主发数据;现在也能让主应用做事——setView("chat" | "terminal" | "git" | "plugin:<id>")切主视图,startChat({ prompt, newChat?, cwd? })新建对话(可选切工作目录)并把 prompt 作为用户消息发出去。时序上startChat会串行等「cwd 切过去 → 对话换成新空白」才发 prompt(服务端new_chat是异步的,紧接着发会落到旧对话),每步都有超时,超时也发、不静默丢。定义见web/src/plugin-host.ts。
Changed
- 升级 SDK
@earendil-works/pi-coding-agent0.84.4 → 0.85.1(上游带来@earendil-works/chord、Anthropic SDK 0.123.0、esbuild 0.28 等);本仓库代码无需跟着改。 - README 中英双版按当前实况重写:功能清单补全(快捷键、Docker、队列撤回、消息构成、项目与会话、搜索与导航、文件树、终端与 Git、模型与设置、Agent 工具与内联标记、声音与通知、PWA、调优用环境变量等章节),中英两边同步并修掉失效锚点。
- 插件运行期数据
plugins/*/storage/加入.gitignore;legado-web 的上游前端源码与构建产物加入.prettierignore(保持上游风格,不被格式化重排)。
Fixed
- nginx 子路径示例配置删掉
favicon-streaming.svg的那条location:该图标早已不存在,留着只会让人以为得额外补一个文件。
i18n
- 本版无文案增量(相对 v0.80.1,已核查)。
v0.80.1
Added
- 更新面板新增「重启服务」:由
pi-web-ui server start|install起的实例,更新面板底部多一个按钮,点一下服务就重启(等价于pi-web-ui server restart)——更新完立即生效,不用回终端。服务端server/launch-origin.ts判定本实例是不是被平台服务托管(launchd / systemd / Windows watchdog),判定结果随ready.service下发,pi-web-ui server status也会显示启动方式;认不出来(前台pi-web-ui、npm run dev、Docker)就不画按钮、也拒绝restart_service——那里没有 supervisor,退出就真的停了。已装好的服务不用重装(运行时靠XPC_SERVICE_NAME/INVOCATION_ID/%APPDATA%\pi-web-ui\<name>.pid对比process.ppid识别),新装的另外烘焙PI_WEB_LAUNCHED_BY=service/PI_WEB_SERVICE_NAME。回归:tests/restart-service-test.mjs。
Fixed
- 终端接管 bash 修复:没有尾部管道的命令不再报
Cannot read properties of null (reading 'segment')(issue #121):date、ls | head -5这类命令没有「尾部限输出管道」,detectTrailingLimiter()返回null,而 #91 v2 的取值重构把原本的可选链写成了非空断言limiter!.segment—— 结果几乎每条一次性 bash 命令都在取值处直接 TypeError(只有以| tail/| less/| more/| cat结尾的命令能跑)。现已改回可选链(这几个值只在真的拆掉管道时才被取用)。回归:tests/unit/terminal-bash-limiter.test.ts(桩终端钉住取值路径,CI 必跑);tests/terminal-bash-test.mjs同步恢复可跑(动态导入走pathToFileURL,Windows 上也跑得起来;提示文案断言钉死中文;一次性终端退出改为轮询而非固定等待)。
i18n
- 前端新增 key(3):
restartService、restartingService、restartServiceTip - 服务端新增 key(2):
terminals.headtail.omitted.below、terminals.headtail.omitted.above
v0.79.0
Added
- 聊天消息支持渲染 LaTeX 公式(issue #116):
$...$行内、$$...$$独立行块走 KaTeX 渲染(remark-math + rehype-katex,字体随包离线可用);代码围栏/行内 code 不受影响,公式写坏了只显示红色源码不打断整条消息。 - 排队/插队气泡新增「撤回」按钮 ↩(#118):点一下把该条消息从队列取回、文字落回输入框(输入框非空时追加到末尾,绝不覆盖正在打的字),改完直接重发;连续撤回多条按序追加。队列里只存文本,撤回只回文字(附件不恢复)。
- 新增 paper(暖纸)/ mist(雾蓝灰)/ sakura(樱粉)三套浅色主题:主题切换器与
make-light-theme.mjs生成器同步增强,终端配色跟随主题;vscode-editor/db-client插件同步跟随亮色(见下 Fixed)。
Fixed
vscode-editor插件跟随亮色主题:之前文件树/标签栏/弹窗底色引用了主应用不存在的--bg-elev0/1变量,亮色下永远回退到深色硬编码值;编辑器(CodeMirroroneDark)与底部 SSH 终端配色也是写死的深色。现在底色改走--bg/--bg-elev,编辑器亮色用跟随--bg/--text/--accent的浅色壳(暗色仍是oneDark,Compartment 热切换),终端读--term-*调色板;主应用切换主题时(pi-web-ui:theme-change)已打开的编辑器与存活终端一起换肤,无需重载。db-client插件跟随亮色主题:同上,文件树/主区/表头/弹窗输入框底色引用的--bg-elev0/1改走--bg/--bg-elev(该插件无自绘深色组件,一次变量映射即完整跟随)。
i18n
- 前端新增 key(1):
queueRecallTip
v0.78.0
Added
- 文件预览支持渲染 HTML(
README.html这类文件不再只看到源码):打开.html/.htm/.xhtml默认是渲染视图,工具栏的 👁/</>与 Markdown 一样一键切源码,进编辑态自动落到源码。渲染走沙箱 iframe,页面里的 JavaScript 默认不执行(工具条显示「🛡 脚本已禁用(静态预览)」),要跑脚本得对当前这个文件点「启用脚本」显式放开(切文件即复位、不持久化、不写进任何配置);无论开关如何,iframe 一律不带allow-same-origin—— 页面拿不到本应用的同源/DOM/cookie/存储,表单提交与顶层跳转同样被挡(脚本开时工具条换成「⚠ 脚本已启用」并说明后果)。渲染地址是新增的目录映射路由,页面里的相对引用(<link href="../web/src/styles.css">、./app.js、图片…)按浏览器正常语义解析加载:/api/preview/<工作区相对路径>(机器浏览的绝对路径用__abs__/前缀),各路径段 URI 编码;.html文档下发Content-Security-Policy: sandbox(?allowJs=1时sandbox allow-scripts,永不加allow-same-origin)与X-Content-Type-Options: nosniff,其余子资源按真实 content-type 直送;..越界由workspacePath()拒绝(400path outside workspace)。/api/file直出的 HTML 也带上sandboxCSP——把预览地址单独在新标签页打开,一样拿不到应用源。
Fixed
- 重试与提示词模板「直发」补记模型使用次数:这两个入口都是「沿用当前模型再发一轮」,之前不计入
model-usage,模型下拉的「按使用次数排序」会漏掉这部分(现在与正常发送一致;模板直发记的是当前模型)。 vscode-editor插件中止上传时临时文件可能残留(Windows):abortUploadEntry旧写法是void fh.close()后立刻unlink并把错误吞掉,而 close 是异步的 —— 句柄还没关就删会EBUSY/EPERM,目标目录里就留下.vsc-upload-*.part。现在改成await close()→await unlink(),且upload_abort等清理完再回响应(客户端随后就会去核验目录);定时清扫与deactivate两条路径改为不等(void)。
Changed
- 排队/插队消息改成和正式用户消息同一套气泡(
MessageList.tsx的QueuedMessage复用.msg-user结构):Markdown 渲染(代码块/列表/链接等不再是一坨纯文本)、角色行显示「你」、状态 tag(插队/排队)与移除 ✕ 排在同一行;未发送仍用虚线边框 + 0.75 透明度区分,服务端真正下发后直接变成正常消息气泡(外观不再跳变)。styles.css里旧的.queued-bubble/.queued-text/.queued-remove一套样式一并删除。 - 官方插件做手机竖屏(≤640px)适配,桌面端表现不变:
db-client(连接侧栏变左滑抽屉、库表树变可折叠面板、结果表格在容器内横滑、Redis 键列表改上下排、触摸目标加大、输入框提到 16px 防 iOS 聚焦缩放)、vscode-editor(文件树变抽屉 + 顶栏 ☰、选中文件自动收起)、run-trace(三段改上下堆叠、时间轴压到 200px、回放条允许换行、触屏色块热区加大)、webmail、demo-mailbox。 docs/architecture-attachments.md的文件预览协议补一节「HTML 渲染走目录映射的 HTTP」(沙箱策略与相对引用语义)。
i18n
- 前端新增 key(8):
showHtmlSource、showHtmlPreview、htmlJsOff、htmlJsOffTip、htmlJsOn、htmlJsOnTip、htmlEnableJs、htmlDisableJs
v0.77.0
Added
- 结构化派单工具
delegate_task:六段式派单(agent + TASK / EXPECTED OUTCOME / REQUIRED TOOLS / MUST DO / MUST NOT DO / CONTEXT,有最小长度)+ 服务端校验——模板不可用、缺段、太短直接报错打回,模型补全后重试。执行体复用子代理 spawn 通道(真会话、白名单、模型优先级、左栏徽标、等待/改向/停止)。前端派单卡片:卡头 ◈ agent 芯片 + 「查看子代理」一键跳转,六段式正文(脏参数不抛错)。 - 7 个 specialist 子代理模板(移植自 oh-my-pi 内置 agents,改写为真子代理提示词):
oracle(只读架构/难 bug 顾问)、librarian(外部文档调研)、explore(代码库侦察)、metis(计划前澄清)、momus(计划评审)、multimodal-looker(PDF/图片/图表解读)、sisyphus-junior(单点执行)。subagent_spawn(template=)直接选用;老用户已有模板文件时一次性自动补齐(sidecar 记录已播种名单,此后删除不再复活)。 - skill 全文注入(设置 → 技能页,按技能单独勾选“全文”):勾选的技能
{{skills}}展开为正文(oh-my-pi 式### Skill:/ 引用描述 / 全文格式;单文件 8KB、总量 32KB 封顶,超限回落名录),不勾选的仍为名录由模型按需读取。改动下一轮即生效,随预设保存/应用。 - Agent 工具统一开关:设置新增「工具」tab,18 个工具(持久终端 7+子代理 7+
edit_soft/delegate_task/ask_user_question/markers_list)逐个开关,标记管理(总开关+分组+查询工具)也并入该 tab(原标记页移除),后端收成tool-manager.ts单一出入口(setAgentToolEnabled/applyAgentToolsGating),改动 live 生效无需 reload,随预设保存/应用;旧的终端/编辑/问卷开关自动迁移,旧客户端照常用。
Fixed
- 排队气泡的 ✕ 只删一条(#113):
removeFirstOccurrence(list, text)(server/queue-utils.ts,纯函数可单测)只移除第一处匹配,removeQueued的两条队列(插队 / 排队)都改用它。旧实现重建队列时用值过滤(filter((t) => t !== text)),同一条文本被排队两次时点一次 ✕ 会把两条一起删掉,而气泡只消失一条(要等下一次queue_update才对齐);现在与气泡 UI、本地显示镜像、DSH 引擎的「删第一条」语义一致。回归tests/unit/queue-utils.test.ts(6 例)。 - 手机端聊天内容贴边(列内缩被算成 0):两个原因都堵上了。①
--chat-pad回调到 14px(= 改前.msg自带的 14px 内边距)——中央列收敛成一条 token 时手机上取了 10px,消息文字/卡片比原来贴边 4px,输入框也跟着从 10px 调到 14px,两边仍齐平。②--msgs-gutter不再只信首帧前的探针:.messages挂载后改用真实元素实测并覆盖,窗口尺寸变化(含手机横竖屏)时再校一次——个别浏览器/设备上探针与真实滚动容器的 gutter 对不上,会把消息列多缩/少缩一条 gutter。另外.messages的左右内缩改成max(0px, calc(--chat-inset - --msgs-gutter))、上下留白改用padding-block独立声明:相减出负值时旧写法会让整条padding声明失效(连上下留白一起丢,内容直接贴边),现在最坏只是不扣那一条 gutter。回归tests/chat-column-align-test.mjs增加「消息列不贴边」断言(内缩不得小于列留白)。 - 输入框底部工具条在窄屏下重叠:428px 左右「思考」chip 会压到右侧的 发送/停止(流式时右侧最宽)。两处修正:① 工具条里的 chip(含外层
.dropdown锚点)补上min-width: 0/flex-shrink: 1,模型名与思考等级先收缩、再省略号截断,不再溢出到右侧按钮上(桌面窗口窄到主列放不下时同样有用);② 纯图标阈值从 420px 提到 560px:窄屏直接隐藏 模型名/思考等级/下拉箭头,chip 放大到 34×30,只留图标(文字交给 title 悬浮)。回归tests/composer-overlap-test.mjs(320–1200px 扫描,注入流式时的「排队|插队」对半胶囊,断言左侧不压右侧、胶囊 78px 且两半等宽、≤560px chip 只剩图标)。
Changed
- 新增全局运行态
web/src/app-globals.ts(模块级 store +useSyncExternalStore,useAppGlobals()/useIsDsh()/useIsManaged()):engine、managed、tabs、appVersion、serverVersion这些「整棵树都要知道、整个连接内只变一次」的信息不再从 App 逐层传 props —— GoalBar / SettingsModal / PiSetupModal / TopBar / FooterBar / ChatInput 改读全局(DSH 的四处 gating、受管实例的更新/插件入口都不再依赖“谁记得传这个 prop”)。写入点只有一处:use-chat.ts收到ready时(同步于 dispatch 之前,不会闪一帧 pi)。顺带修正 DSH 下「插队」名不副实:DSH 无 mid-run steering(prompt 一律 followUp),运行中只渲染「排队」半段(收成 38px 圆),placeholder 也换成placeholderStreamingQueued(回车与点排队都是本轮结束后才发)。回归tests/unit/app-globals.test.ts。 - WebSocket 发送器也收进全局:
appSend(web/src/app-globals.ts下半部分,use-chat用setAppSend装配)—— 19 个组件的sendprop 全部删除,App.tsx少 19 处逐层传参(对话框/弹窗/面板/插件视图/终端/SCM/底栏全部自己取),ClientMessage依赖也随之从这些文件消失;测试(dsh-question-dialog.test.ts)改用setAppSend注入并记录发出的消息,组件仍可测。两个例外是故意的:LeftPanel/RightPanel的 prop 改名为panelSend(它们拿的是 App 的包装函数,带「顺手关手机抽屉」的副作用,不能换成全局发送器);装配写在 render 期间而非 effect —— 子组件 effect 先于父组件跑,放 effect 里装配会让「挂载即发请求」的弹窗在 appSend 还是空的时候静默丢包。 - 全局运行态再扩三项:
ready/status/cwd。LeftPanel(三个都收)、RightPanel(cwd)、ChatInput(ready)、GlobalSearchModal(cwd)不再要这些 prop,改从useAppField(key)单字段订阅 —— cwd 是低频字段,单字段订阅让「切项目」的通知只到真正读 cwd 的组件,不会连带重渲染只读 engine 的组件。写入点:use-chat.ts里一个 effect 把 reducer 的真值镜像过去(单一来源,最多晚一帧;默认值只会是「未就绪 / 未连接 / 空目录」,看不出来)。本来就吃整个 ChatState 的App/TopBar/FooterBar仍直读chat.*(自己就持有数据,不必绕一圈)。 - 运行中发送位改成「排队|插队」对半胶囊:空闲态那颗发送圆钮在流式中原地变形为 78×38 的蓝胶囊,两半各 38px、中间一条 1px 半透明白线——左半「排队」(列表图标,加入队列,整轮跑完才发)、右半「插队」(↑,回车语义,本回合立刻响应,与 Enter 同一条路径)。原文字版「排队」药丸及其 ≤560px「收成图标」的兜底一并删除(窄屏右侧最宽从文字药丸降到固定 78px);空输入时整颗胶囊变暗、两半禁用(尺寸位置不动,工具条不跳),有文字或纯附件时解锁。停止语义与它们相反,仍是右侧独立的蓝圆,不并入胶囊。
tests/supplement-test.mjs同步改为点左半,并断言「空输入两半禁用 → 输入后解锁」。文案变更:前端新增 keysteerTip。 - 设置面板减负:常驻列表里的静态解释全部收进标题/开关旁的「?」悬浮提示(含 7 个「已关闭」后果说明、重试次数、子代理默认模型、全文注入说明等);行内只保留计数、空状态、报错与动态状态(如当前转写模型)。文案 key 无增减。
- 问卷(
ask_user_question)不再被工具挂死看门狗剁掉:以前它跟普通工具一样被算作「一个工具跑了 20 分钟」(PI_WEB_TOOL_TIMEOUT_MS),到点就 abort 整轮对话并弹「工具执行超过…已自动终止」——把还在思考的用户连对话一起终止。现在按工具名豁免:问卷等的是人类回答,不是挂死的工具,收场只走用户回答/取消与会话 dispose,不限时。同理,问卷挂着也不再算「失联」(stall 告警默认 180s 无 SDK 事件,对该对话跳过)。同时补上「刷新/重连后问卷对话框不再消失」:question_pending是即时通道,只推给提问那一刻在线的连接,刷新页面/新标签页都拿不到那条历史消息,而服务端还在阻塞等人回答;现在待答问卷同时挂在快照(UiState.pendingQuestion,标准引擎只带当前对话的那张,切回原对话会重推快照)上,两个引擎(标准 pi / DSH)重连后都会把面板恢复出来,由快照恢复的面板也能被快照收起(另一标签页答完/服务端取消),但即时通道弹出的面板不会被在途旧快照闪掉,已答过的问卷也不会被在途旧快照重新弹出。回归tests/question-bridge-test.mjs(零 token,本地假模型驱动整条链路)+tests/unit/pending-question.test.ts。
i18n
- 前端新增 key(28):
placeholderStreamingQueued、steerTip、settingsTools、toolsSectionTerminal、toolsSectionSubagent、toolsSectionOther、toolsSubagentDepHint、delegateTaskEnabledDesc、delegateTaskOffHint、todoListEnabledDesc、todoListOffHint、toolDescSubagentSpawn、toolDescSubagentGetResult、toolDescSubagentSteer、toolDescSubagentList、toolDescSubagentStop、toolDescSubagentWaitAll、toolDescSubagentTemplates、skillFullTextLabel、skillFullTextDesc、skillFullTextShort、delegateOpenSubagent、delegateSecTask、delegateSecExpected、delegateSecTools、delegateSecMustDo、delegateSecMustNotDo、delegateSecContext - 服务端新增 key(3):
delegate.validate.agent、delegate.validate.short、delegate.started
v0.76.0
Added
- 桌面通知的诊断能力(默认不显示在界面上):
sendTestNotification()会立刻发一条系统通知(不受「页面不在眼前」抑制影响,且带requireInteraction不会自己滑走),并汇报走的是 service worker 还是页面通知、失败原因、浏览器到底有没有留下这条通知(getNotifications(),区分「系统层面被压住」与「浏览器直接丢了」)与判定依据(焦点 / 可见性 / 是否最小化 / 空闲秒数)。界面在web/src/components/NotifyToggle.tsx的SHOW_NOTIFY_TEST_PANEL常量后面,排障时改成true。
Fixed
- Windows 上窗口最小化后依然收不到任何桌面通知(v0.75.0 只修了一半):Win11 实测,窗口最小化后
document.hasFocus()仍是true、visibilityState仍是"visible",连blur/visibilitychange都不发 —— 「只看焦点」和「焦点 且 可见」两种条件都在这个场景下把通知全部静默掉。现在改用只有最小化会变的那组信号判定(原生窗口矩形:screenX/screenY跳到屏幕外的「最小化坐标」,outerWidth/Height塌成标题栏;isCollapsedWindow,有单测),并且在 Windows 上额外要求「最近 2 分钟内有过页面交互」才背静默 —— 这个平台的焦点/可见性都不可信,宁可多提醒一次也不漏。非 Windows 平台行为不变(其焦点/可见性可信)。 - 通知发送路径不再因 service worker 抛错而彻底静默:注册存在但还没 active(首次加载 / 刚更新后)时
showNotification会失败,现在会退回页面通知,两者都失败也会把原因带出来(诊断按钮里看得到)。 - 修掉「只有第一次弹、之后怎么都不弹」:通知带固定
tag时,Windows 把同 tag 的新通知当成替掉旧条目,而且是静默的 —— 没有横幅、没有提示音,只要系统通知中心里还躺着一条 pi-web-ui 通知,后续每一条都会被无声替换(页面上看showNotification明明成功了)。现在干脆不用 tag(也不依赖renotify—— 实测它在 Windows toast 这层不起作用),每条都是全新 toast;代价是通知中心里会累积几条。
i18n
- 前端新增 key(9):
notifyTest、notifyTestBody、notifyTestSent、notifyTestFailed、notifyTestState、notifyTestHeld、notifyTestDropped、notifyTestGateSuppressed、notifyTestGateOpen - 前端中文变更(1):
notifyEnableDesc - 前端英文变更(1):
notifyEnableDesc