Repository navigation
Releases: cullysu/pi-workbench
Release list
v1.3.7 — 草稿保全、队列诚实与保存回滚
v1.3.7 — 草稿保全、队列诚实与保存回滚
来自独立 v1.3.6 审查的全部 5 项修复(2 个 P2 + 3 个 P3),主题是**「失败时的诚实」**:打开失败不吞草稿、离线队列不丢用户输入、保存失败不假装成功。
修复
P2-1 被拒绝的打开不再吃掉输入框草稿
- 输入框内容只在「不可回退点」(tab id 在线上确认)之后才清空——以前是先清空再等打开结果,打开被服务器拒绝时草稿就没了。
open-denied帧现在会拒绝挂起的打开 Promise(reject 函数通过state._openReject让 WS 处理器可达,另有 21 秒兜底防丢帧),submitPrompt捕获后在状态行提示「会话打开失败:…——输入框内容已保留」,草稿与待发附件原样保留。
P2-2 离线队列停止丢弃用户回合
- 200 帧上限只丢「刷新类」RPC(
get_state/get_entries/get_available_models/get_available_thinking_levels)——它们的答案在冲刷那一刻就过时了。 - 队列里没有可丢帧时,新发送被显式拒绝,状态行指明是哪种操作(「『prompt』未能入队,连接恢复后请重试」),不再静默丢弃 open/prompt/close/set_model/adopt。
P3-3 设默认模型检查后端应答
- 两处 set-default 都先检查
/api/config的 ack 再更新 cfg/chip——以前服务器 500 时乐观 UI 照样更新。
P3-4 Provider 保存的部分提交回滚
- 保存改在
modelsDoc的克隆上进行(POST 前的就地修改不再在保存被拒后残留在内存里); - models→routing→config 序列中途失败时,通过新增的
reloadConfigState()从磁盘重拉三份文档,内存与磁盘对齐。
P3-5 collectProc 上限变严格
- 每个 stdout/stderr chunk 在接收时就按剩余上限切片,close 路径再截断一次——以前单个超大 chunk(5000 字符 vs 200 上限)会整块漏过。
契约测试
scripts/verify-audit-fixes.mjs新增 5 条 wave-26 断言(composer 清空时序、open-denied 拒绝接线、队列策略、ack 检查 + 克隆/重载、chunk 级上限)。
校验
- 提交
ae72326;CI 8/8(run 39041566547);全部 7 个版本文件 = 1.3.7。
v1.3.6 — 进程身份、凭据边界与跨进程互斥
v1.3.6 — 进程身份、凭据边界与跨进程互斥
针对 v1.3.5 独立审计(50 项行为缺陷,13 项带隔离实测,commit e0e4364)逐条处置:40 项行为缺陷已修,8 项按审计自身的说明归入"已知未结"(见文末),1 项(F07 发布签名)只能由证书账号闭合。
桌面壳不再被假服务骗到(F01)
- 旧实现把"端口上有人回 HTTP/1.x 200"当成工作台,会把 API token 交给占位进程。现在 Electron 与 Tauri 都向新增的免鉴权
GET /api/identify?n=<hex>发一次性随机 nonce,要求x-piwb-proof = HMAC-SHA256(API_TOKEN, nonce);Tauri 侧手写 HMAC(sha2)+ 常数时间比较,3 秒读超时保留。scripts/ui-contract-probe.mjs增加该项的运行时断言。
凭据只在信任域内流转(F02–F04、F17–F19、F32、F38)
- "不含密钥"导出写明范围并真正执行:manifest 带
scope/scopeNote,config.json/settings.json以脱敏副本入包,baseUrl的 userinfo 与凭据类查询值一并剥离,恢复前对 provider 行做形状校验。 - MCP 哨兵不再跟着改掉的命令走:
***环境/参数只在 command + args 身份一致时还原;配置文件改为mkdir后先暂存再替换。 - 只写 key 字段在所有出现处都脱敏(
scrubUrlCreds/scrubArgSecrets/maskApiKeys),被掩码的models.json往返不再摧毁已存baseUrl。 - key 冷却绑定凭据的 env 名,而不是数组下标(
provider#env:NAME)——换序/移动过的 key 不再冷却错误的槽位;字面 key 供应商keyEnvsFor返回[];nextInChain拒绝models.json里不存在的 provider/model,不再把它当免密供应商。 - 损坏配置留证进
corrupt-backups/,最多 3 份、0600。 - Windows 凭据文件 ACL 不再只保护 token(F08):服务端与备份模块写入的每个凭据文件都按当前用户收紧 DACL(
icacls /inheritance:r /grant:r <user>:F)——token、config.json、models.json、mcp.json、routing.json、cron.json、settings.json、model-meta.json,以及导出的 zip、恢复快照与恢复临时文件。POSIX 的 0600 本来就是对的,Windows 之前等于没设。
日志脱敏如实说明范围(F05、F06)
- cron 单次运行日志创建即 0600(F05)。
docs/TROUBLESHOOTING.md不再暗示"日志已脱敏":明确列出会替换的模式,以及不会替换的秘密(其它格式 key、Cookie 值、URL 查询串 token、子进程 stderr),并要求提交前人工确认(F06)。
Cron:单写者 + 诚实的窗口(F09–F16)
- 跨进程单写者:每个任务一把锁文件(
openSync('wx')、0600、30 分钟后可接管、bootReset 清扫),两个服务实例不再跑同一个任务(F09/F10)。 - daily 当天窗口只在 spawn 成功后消费:spawn 抛错、key/model 冷却都改为延期而不是烧掉当天(F11–F13)。
- settle 记录本次实际使用的 provider/model/key 身份,
__keyIdx从子进程环境剥离(F14)。 - 运行中的任务可以取消:停用/删除任务会终止其子进程;
settleWatch在close永不到达时兜底终结;日志 fd 0600(F15/F16/F05)。
界面不再展示乐观假象(F20–F22、F40–F45)
- WS open 解析生效模型(默认模型 + 停用供应商门禁)并固定路由 key env;跨供应商
set_model会重启该 tab 的 pi 并上报pi-restarted(F20/F21)。 - 用过工具的那一轮不再自动重发——副作用重复前必须由用户确认(F22)。
set_model未被确认时模型 chip 回滚(F41);goal 对话框先回答等待中的 modal 再替换(F42);goals/主题/供应商/技能/cron 与高级 JSON 编辑器都检查后端回执并在失败时回滚(F43/F40);结构化 API 错误按错误渲染,不再显示成"空文件"/"没有会话"/"今日 0"(F44);附件在发送前做尺寸门禁,不再因超过 8 MiB WS 帧而清空草稿并断连(F45)。
读取器与账本不再丢数据(F23–F31、F34)
- Codex 导入保留真实的重复轮次(合并不同轮次的去重已移除);pi/Claude 导入保留原始时间戳与工具错误语义;各会话解析器的头部读取补齐 null 守卫(F23/F24/F29/F30)。
- 会话导出默认按活动分支(
?all=1导出全部分支),中途失败回包带可见错误块而不是静默截断的 200,无内容时降级为文本说明而不是空文件(F25/F28)。 - Claude 账本按
message.idlast-wins(旧实现 first-wins 会丢掉最终 usage);会话树上报windowed;readJsonl返回{items, truncated},用量/账本暴露cappedFiles/truncatedFiles(F26/F27/F31)。 saveModelMeta改为 tmp+rename(F34)。
开发一致性与文档(F49)
- README 代码围栏修正;README.en 补齐它缺的 prepare-runtime + embed-token 流程;
prepare-runtime.mjs拷贝data/,开发环境 KB 不再静默为空。
验证
lint 0、测试 40/40、路由扫描 47 PASS、探针(ws / failover / ui / chat / contract)全 PASS;契约测试 23 → 39 项:新增 16 条锁定本轮修复(env 名 key 身份、字面 key 供应商、回退链校验、daily 窗口延期、导出分支与空transcript、错误形状渲染),ui-contract-probe.mjs 增加 F01 挑战应答检查。CI 8 job 全绿后从 CI 产物发布(5 件资产 + SBOM + SHA256SUMS.txt,逐件校验 artifact digest 与条目名)。
已知未结(按审计原文的口径):F07 发布签名(等证书账号——hash 清单能查损坏,不能认证恶意发布)、F33 恢复的崩溃安全事务日志、F35 Tauri 子服务监督/自动重启、F36 启动校验失败后自动从可信 ZIP 重建 runtime、F46 侧栏重绘引起全会话重扫、F47 长期运行资源全生命周期预算、F48 键盘/辅助技术路径与 modal 语义、F50 安装器级实机验收与供应链深度(action 摘要固定、Node 校验和签名、SBOM 分层、Windows/macOS 安装器冒烟)。
v1.3.5 — 失败路径、信任契约与数据正确性
v1.3.5 — 失败路径、信任契约与数据正确性
针对 v1.3.4 独立审计(43 项行为缺陷 + 18 项工程不足,commit 261fd60)中全部 43 项行为缺陷逐条修复;按审计建议的优先序:凭据/备份 → 回退与执行身份 → 无人值守 → 假成功。
服务不再被敌意输入杀死
- 畸形 WebSocket 升级目标不再能全服务退出:
new URL("//[")曾在鉴权前抛异常、落入全局处理器收割全部 tab 与 cron 子进程并退出——现解析有守卫,普通 HTTP 的同步异常改为 400 回包而不是未处理 rejection。 - 空 tabId 不再全客户端广播:tab 作用域帧要求非空字符串 id;pi-exit 只发给原 owner/adopting 连接(旧实现在 Map 删除后广播,退出事件泄给所有连接)。
- Windows/macOS 外部终端 spawn 补异步 error 监听;FIFO 通过旧的 !isDirectory 检查会挂死事件循环——现在要求常规文件。
"不含密钥备份"改为 fail-closed,真正可信
- Cookie 类凭据头纳入脱敏(模式刻意放宽:多遮功能头可安全往返,漏一个就是真泄露)。
- 损坏的 cron.json 在无钥匙导出中被省略,不再回退打包原文。
- 导出文件名带随机后缀——同秒先"不含 key"再"含 key"不再让前一个路径变成含密钥文件。
- 全部秘密文件(配置/zip/恢复临时文件)创建即 0600,不再写完再 chmod。
凭据归属要求"已知端点"
- 保存与恢复都曾把"有 key 但没写 baseUrl 的旧 provider"当同一端点,把旧凭据继承到新地址。现在哨兵 key/headers 只在双方 baseUrl 都存在且相等时还原;未知旧端点 ≠ 匹配。
配置投毒挡在门口
- 备份导入对 routing.json 深校验:标量
state:1(曾使.cooldowns赋值抛 TypeError、拖垮全部路由 API)与[null]链被拒;loadRouting对所有消费者做防御性规范化。
回退链兑现承诺
- sameModelNewKey 尊重模型冷却:429 冷却的模型不再"同模型换 key"立即重撞配额(可能还是原 key),健康的后继供应商接管。
- 免密供应商(本机 Ollama/LM Studio、header 鉴权)进入回退链:"无受管 key"不再被当成"坏了"。
- 同供应商 set_model 保留 key 身份:切模型不再清 routeKeyIdx(进程实际还在用原 key,旧实现会让后续 401 冷却错误的槽位)。
Cron:不重跑、不漏跑
- 重启打断的任务不再双启动:bootReset 留下可区分状态,且 spawn 必定落盘 running 标志(旧的 status-only dirty 检查曾跳过写盘,下一 tick 读到 running:false 又启一次)。
- 延期标记到达被挤掉的每一个 daily(旧实现只标第一个);契约测试改为真实时序:interval 占满槽 → daily 到点 → 全部延期 → 槽位释放 → 全部补跑。
界面不再撒谎
- open-denied / rpc-denied 有处理:拒绝即清除乐观 tabId 并给出原因,不再留下没人应答的用户气泡;已知拒绝不再清空输入草稿。
- 所有保存面检查后端回执:api helper 非 2xx 一律解析为
{error};models/routing/config 保存与供应商开关只在确认后才说"已保存"。 - 迟到响应带请求身份:discover 期间切供应商即丢弃过期结果;导入列表/正文竞态按序号定胜负。
- "切回"只切换、绝不杀目标:当前有 tab 先分离进通知条;第 4 个后台会话改为"结束最旧 + 明确告知",不再静默失管。
- 新会话把 selModel 同步到实际打开的模型(chip 与失败上报描述真实执行);pi 异常退出真正接入回退(旧触发器在永远收不到 WS 帧的监听器里);共享弹窗先回答等待中的扩展再被替换;replay 重置流式节点(断线恢复后正文继续可见);恢复备份后强制刷新内存配置(旧实现可能把导入前状态写回去);健康页在后端无响应时如实报错;GOAL_DONE 必须是独立末行;50 条会话上限明示为窗口。
数据正确性
- 裸
nullJSONL 行在读取器即跳过(不再拖垮会话列表/统计/树);Codex 导入去重 response_item/event_msg 双表示;Gemini 读.content三种形态;Claude 账本按 message.id 去重(同一调用不再按行数翻倍);Codex 计数器回退按维度钳零;会话树报告truncated;文件预览声明截断;ZIP 中央目录 made-by/needed 字段修正。
验证
lint 0、测试 40-40、路由扫描 47 PASS、探针(ws / failover / ui / chat / contract)全 PASS;契约测试 16 → 23 项:审计点名的弱断言(可被 || 掩盖失败的断言、绕过调度器直接调运行函数的"延期"测试)全部替换为 tick 驱动的时序测试,新增锁定:bootReset 双启动、标量 routing 拒绝、未知端点恢复、Cookie 类脱敏、损坏 cron fail-closed、导出路径唯一。CI 8 job 全绿后从 CI 产物发布。
已知未结(不变):代码签名(等证书账号)、Windows/macOS 安装器级实机验收、Tauri 侧服务自动重启对齐、全发布 SBOM 分层、完整分页、a11y/i18n、会话标识架构统一、同步 IO 预算。
v1.3.4 — 凭据归属、执行身份与真实 pi 协议
v1.3.4 — 凭据归属、执行身份与真实 pi 协议
针对 v1.3.3 独立审计(50 项 = 29 缺陷 + 21 工程不足,commit 7a26217)中 29 项行为缺陷与可直接落地的工程项逐条修复。
凭据归属(全部按端点核对,不再按 provider 名硬继承)
- 改 URL 后"获取模型列表"不再把旧 Key 发往新地址:URL 变更且密钥留空时拒绝附加存储凭据,明确提示输入新端点密钥(保存路径 v1.3.3 已有守卫,discover 路径本次补齐)。
- 恢复备份不再把本机 Key 嫁接到改名端点:models.json 恢复合并先比对 baseUrl 再继承 apiKey/headers。
- 凭据头掩码可安全往返:GET 掩成
***的 Authorization 类头,POST 现在还原存储值而不是静默销毁;URL 变更时哨兵凭据头与 key 同步清除。 - "不含密钥"备份脱敏冷却/运行错误文本(上游错误可能内嵌触发它的密钥)。
执行身份(以 tab/cwd/请求为中心)
- 提示词不再可能发进运行在另一项目的 tab:submitPrompt 校验 tab 打开时的 cwd 与当前项目一致,不一致即分离重建——一条防线封住标题点击/添加项目/移除项目/配置重载全部旁路。
- 切项目 = detach:服务端把 tab 标记为无人管理(30 分钟孤儿 TTL 兜底),界面出"后台运行"通知条提供切回/结束;旧实现直接忘掉 tabId,留下无法管理、无法回收的子进程。
- 扩展确认框回复发回发出请求的那个 tab(旧实现读点击时的全局状态)。
- 失效回包不再污染已切换的项目:回退响应只在武装上下文仍然成立时才落地;故障上报携带武装 tabId,冷却落在正确的 key 槽。
- 删除当前项目时其运行中会话一并结束(弹窗明示)。
真实 pi 协议
- set_model 重绑真正生效:pi 0.85 返回 Model 对象(
{provider, id}),v1.3.3 读的data.model字符串从不存在——旧 key 索引残留导致 401 冷却打错供应商 key 池的路径已封死(对照随包 rpc-types 验证)。 - 同模型换 key 全链打通:
/api/routing/fail返回sameModelKey,前端优先用备用 key 重开同一模型,而不是直接跳供应商。 ${KEY}轮换真正到达 pi:环境覆盖此前写成{KEY},pi 永远读旧值;现写裸名。- 显式启动尊重供应商停用开关:
enabled: false现在拒绝 open(此前只过滤回退链)。
Cron
- 默认模型任务接入默认模型的密钥轮换;全部 key 冷却的供应商 fail-closed(报"keys cooled"),不再带着旧环境裸跑。
- 被在跑 interval 挤掉窗口的 daily 改为延期补跑(当日有效),排序修不了已关闭的窗口;窗口消耗移入真正 spawn 路径,拒绝不再反复写 cron.json。
前端 / 导入 / MCP / 壳
- 导入视图:迟到 replay 不再覆盖、导出按钮拦截(旧实现会导出上一个 live 会话)、点击列表项真正跳转工作台;工具结果按
data-id配对(dataset.callId从未被赋值过);replay 后工具卡映射保活;fork 缓存随发送失效 + 请求 id 防陈旧超时;重复分支与重复 get_state 去重。 - MCP 桥:陈旧子进程 stdout/stderr 不再污染新客户端;
null行不炸;失败路径与 shutdown 杀整棵进程树(Windows taskkill /T、POSIX 进程组);主 pi stdin 加异步 EPIPE 兜底。 - Tauri 壳:身份握手 3 秒读超时;剥离
NODE_OPTIONS/NODE_PATH(与 Electron 对齐)。 - 文档/供应链:README 开发启动补
PIWB_EMBED_TOKEN=1说明;MCP 面板文案对齐真实 lazy/eager 默认;故障文档指向真实日志文件 server.log;SBOM 重新解析多文件 vendor 行(uPlot 曾静默缺失);仓库描述去掉"10MB / data never leaves"。
验证
lint 0、测试 40-40、路由扫描 47 PASS、探针(ws / failover / ui / chat / contract)全 PASS;契约测试从 5 项扩到 16 项并进入 CI e2e(上一版有三条声明没有测试锁定,全部被审计击穿——本版起修复必须带锁)。CI 8 job 全绿后从 CI 产物发布(SHA256SUMS + SBOM 均含)。
代码签名仍等证书账号(唯一用户侧门控)。
v1.3.3 — 陈旧进程身份、cron 失闭与导入隔离修复
v1.3.3 — 陈旧进程身份、cron 失闭与导入隔离修复
针对 v1.3.2 独立审计(42 项,commit 760bd41)中核实为真的缺陷逐项修复;不实主张以源码行为反驳。
进程与会话身份
- 陈旧 pi 进程不能再替被替换的 tab 说话:stdout/stderr 泵按进程身份(而非仅 tabId)校验,旧 spawn 的迟到事件不会再落进新 owner 的时间线。
- 确认成功的模型切换会重绑 tab 的路由身份:此后 401 冷却的是 tab 实际使用的 provider/key,而不是它启动时的。
- Failover 武装快照移到 await 之前:等待故障上报期间用户切走 tab,重发瞄准的是被武装的那个 tab,不再"认领当前碰巧活跃的会话"。
- 点击项目标题即分离 live tab:切项目不再让输入框继续绑着上一个项目的 tab/会话。
- 重连恢复忙碌态:
get_state回isStreaming:false会清掉卡住的 streaming 指示;fork 报cancelled保持视图不动,完成的 fork 重读会话身份。
Cron 可靠性
- 失效项目 fail-closed:任务报"project missing",不再静默降级到 HOME 让无人值守 agent 在错误目录里读写。
- spawn 前失败释放并发预算(两次 throw 曾让 cron 整个进程生命周期停摆);daily 任务优先调度,热 interval 任务无法永远占满两个槽把它饿出窗口;无人值守任务遵循交互侧密钥轮换(已冷却的 key 不再武装无人值守运行)。
数据与安全
- 改 baseUrl 不再带走旧端点的密钥:URL 变更且密钥为掩码哨兵时清除存储 key,而不是把它发给新地址。
- 备份脱敏覆盖凭据头(Authorization/x-api-key/token 类),恢复时脱敏 models.json 与本机已存密钥合并而不是丢弃;
$KEY/${KEY}两种环境变量引用在 failover 中同等解析。 - JSON
nullWS 帧在属性访问前被拒;collectProc 超时强杀后 5 秒自解(孤儿孙进程握住管道也不再挂死调用方)。 - MCP 桥:陈旧子进程的退出不再拆掉较新的客户端;stdin EPIPE 吞掉;缓冲区末行先于 pending 拒绝刷新;lazy 工具重名数字后缀去重。
- 导入历史保持惰性:导入视图下 regenerate/edit 按钮被拦截并给出状态行;导入的工具结果按
callId ?? id配对而不是永远贴最后一张卡。 - Usage:Gemini 导入读
m.parts;会话树父链环防护;合并层保留 cacheWrite/costEst,明细与总计不再互相矛盾。CHANGELOG 1.3.0/1.3.1 粘连标题修复。
验证:lint 0、测试 40-40、路由扫描 47 PASS、探针(ws / failover / ui / chat / contract / audit-fixes 5 项)全 PASS;CI 8 job 全绿(打包 job 由 lint+test+e2e 门入)。
代码签名仍等证书账号(唯一用户侧门控)。
v1.3.2 — 版本身份修复与执行可靠性
v1.3.2 — 版本身份修复与执行可靠性
v1.3.1 已废弃:其 tag 指向的提交里版本元数据仍为 1.3.0(版本号 bump 只落在 main、未进 tag),构成"资产名 1.3.1 / 应用内 1.3.0"的版本身份错误。该 release 与 tag 已删除;本版以完全一致的版本元数据(tag=commit=资产名=应用内 1.3.2)重新发布,包含 v1.3.1 全部内容。
v1.3.1 审计(独立,34 项)与 v1.3.0/v1.3.1 补充修复
- Cron 生命周期:并发预算每次 settle 释放("最多 2 并发"曾退化为"每生命周期 2 次",复现 1,2,2);畸形 daily 时间跳过而非崩整个 tick;run-now 报 accepted/rejected;settled 子进程移出关闭集合(生命周期泄漏);cron 子进程纳入服务退出收割。
- Failover:重发只认领回退自己打开的 tab(旧实现把用户切走的会话认领为回退目标,A 的提示词会发进 B);停止/切换/新回退都在触发时重校验。
- 只读导入真实接线(v1.2.8 声称的修复因补丁批次部分写回丢失,本版由契约探针锁定):导入视图拦截执行入口、隔离 live 事件、跨项目点击同步、重连补取状态与回放。
- 弹窗单次回复;文件浏览与注册/git/终端同一项目身份;provider 测试重试真正去掉 max_tokens(旧"uncapped retry"重发完全相同的请求体)。
- 可复现构建:移除构建期
cargo update——提交的 Cargo.lock 同时是审计与构建的依赖树;pin-runtime-deps 校验 tarball 的 SHA-512 与锁文件 integrity 一致(仅版本号匹配不再足够)。 - uncaughtException 安全停机:收割子进程后退出(桌面壳自动重启),不再以未知状态继续对外服务;
PIWB_NO_EXIT_ON_UNCAUGHT=1为调试后门。 - 文档:README 版本示例/体积/安全模型(当前用户权限、无沙箱)如实化。
验证:lint 0、测试 40-40、路由扫描 47 PASS、七探针(ws / failover / ui / chat / contract / audit-fixes / sweep)全 PASS;CI 8 job 全绿(打包 job 由 lint+test+e2e 门入)。
代码签名仍等证书账号(唯一用户侧门控)。
v1.3.0 — 审计六项 P1 修复与执行正确性
v1.3.0 — 审计六项 P1 修复与执行正确性
v1.2.9 独立审计(34 项,含隔离 fixture 与 fake spawn/VM 复现)的六项 P1 与相关 P2 全量落地。审计自带的复现脚本已指向本版源码重跑,四条核心场景全部 PASS(脚本入库为 scripts/verify-audit-fixes.mjs,随门禁保留)。
执行正确性(P1)
- Cron 预算在每次 settle 释放——运行计数此前只增不减,"最多 2 并发"实际是"每服务生命周期最多 2 次"(审计复现 spawn 1,2,2 后停摆)。另:畸形 daily 时间跳过而非崩溃整个 tick;run-now 返回 accepted/rejected(预算拒绝不再报 ok);cron 子进程纳入服务退出收割。
- Failover 重发不再发进用户切过去的会话——重发身份的"重锚定"原先发生在第二个定时器内部,等于把用户切换后的 tab 认领为回退自己的 tab、失效检查永远通过。认领现在只发生一次(回退自己打开的 tab),后续检查只校验。
只读导入与弹窗(P1)
- v1.2.8/v1.2.9 的只读导入修复实际未进发布包(wave-18 补丁批次在一个失败锚点上提前中止,部分写回只留下 submit 守卫)——审计复现"导入后 tabId 不变、importView 未设置"属实。openImported 标志、live 事件隔离、真实会话打开即退出、跨项目点击同步,全部重新落地,并由契约探针与审计复现双验证。
- 弹窗单次回复:显式点击会先清取消钩子再关闭——此前 OK 会先发 confirmed:false 再发 confirmed:true,同一请求收到两条冲突响应。
数据与供应链(P2)
- 用量分项与总计一致:provider 行补 cacheWrite、日桶折入 costEst、provider 行不再拿供应商名当模型名估价——"总 $3 / 日 $0"的矛盾消除。
- Codex 账本:首个累计样本即真实用量(空基线丢掉它——100 只记成 50 的反例),事件按自身时间戳分桶(跨日不再归会话开始日)。
- 备份:排除密钥时保留脱敏版 models.json(供应商 URL/模型列表不再丢失);导入校验成员级形状(
[null]项目、无 time 的 daily 不再通过——后者曾让整个调度 tick 崩溃);rename 前 chmod;压缩输入 128MB 上限;回滚失败如实报告。 - MCP:传输/超时错误抛进 pi 的失败层(pi-agent-core 契约:fulfilled 即成功,details 只是 UI 元数据);超时发送协议级取消,远端工具不再无人等待地继续产生副作用。
- readJsonl 的无换行巨行 cap 移到能执行到的位置(旧位置在换行循环体内——无换行洪流永远不会到达)。
- CI:三个打包 job 现在依赖全部质量门禁(lint+test+e2e);Windows Tauri 构建前刷新 lockfile——被审计的锁就是被构建的锁;契约探针真实进入 e2e job(v1.2.9 的这条声明是假的,本版为真并在 CI 实跑通过)。
文档
README.en/CONTRIBUTING 的 Node 下限修正为 ≥22.19;英文备份文案与脱敏默认对齐;搜索占位文案改为如实的"过滤当前项目会话"。
验证:lint 0、测试 40-40、路由扫描 47 PASS、六探针(ws / failover / ui / chat / contract / audit-fixes)全 PASS;CI 8 job 全绿,打包 job 由全部门禁门入。
代码签名仍等证书账号(唯一用户侧门控)。
v1.2.9 — MCP 用户取消与契约探针
v1.2.9 — MCP 用户取消与契约探针
Fixed
- MCP 用户取消真正到达 MCP server:pi 给每个工具 execute 传 AbortSignal(types.d.ts:372)——桥此前一直丢弃它。现在信号穿入
tools/call:本地调用立即失败,并向 MCP server 发送协议级notifications/cancelled,服务端停止工作。 - 扩展 confirm/select 的 Escape 回复这次真的发布了:新写的浏览器级契约探针(见下)首次运行就抓到 v1.2.8 的该修复实际未进发布包(wave-18 批次在另一个失败锚点上提前中止,confirm/select 的 modalCancel 编辑随部分写回丢失)。修复已补,探针锁定。
Added
scripts/ui-contract-probe.mjs:前端状态契约的浏览器级反向测试,已接入 CI e2e job——- T1 导入隔离:导入视图打开时提交被拒并给出解释,线上零 open/prompt 帧;
- T2 Escape 回复:关闭 confirm 弹窗在线上发出
{confirmed:false}; - 金丝雀帧守护记录器自身。
验证:lint 0、测试 40-40、路由扫描 47 PASS、ws-probe / failover-e2e / ui-probe / chat-e2e / ui-contract-probe 全 PASS;CI 8 job 全绿(含 AppImage 实机启动与契约探针)。
代码签名仍等证书账号(唯一用户侧门控)。
v1.2.8 — 已排期产品专项落地
v1.2.8 — 已排期产品专项落地
v1.2.6 独立评审中明确排期的四个产品专项全部落地(评审 #3/#4/#7/#10),每一项都是前端状态契约的修正。
跨项目会话上下文同步(评审 #3)
点击另一项目块下的历史会话时,面板先同步到该会话所属项目(标题、项目树、目标横幅),再打开会话——标题/文件/Git/目标上下文不再描述上一个项目。
只读导入的执行隔离(评审 #4)
导入历史成为真正的只读视图:执行入口(发送框与目标续跑都落在那里)被拦截并给出明确状态提示;运行中代理的 pi-event 与 pi-exit 帧不再写进导入画面;打开任何真实会话即离开导入视图。运行中的代理保持运行——它只是不能再污染你在读的历史、也不能吞掉新输入。
WS 重连补取(评审 #7)
socket 恢复后,当前 tab 自动补发 get_state 并从磁盘整段回放当前会话——掉线期间流式输出的内容从磁盘找回,不再留下"一直忙碌 + 缺最终回答"(导入视图打开时跳过)。
扩展对话框的 Escape 回复(评审 #10)
confirm/select 弹窗的 Escape/背景关闭现在会回复扩展(confirmed:false / cancelled:true)——关闭弹窗不再让无超时的扩展永远等待。
验证:lint 0 / 测试 40-40 / 路由扫描 47 PASS / ws-probe、failover-e2e、ui-probe、chat-e2e 全 PASS;CI 8 job 全绿(含 AppImage 实机启动)。四项均为前端状态契约——浏览器级反向测试与下一批探针扩展一起排期。
代码签名仍等证书账号(唯一用户侧门控)。
v1.2.7 — 停止语义、账本正确性与第十七波收口
v1.2.7 — 停止语义、账本正确性与第十七波收口
第十七轮独立评审(v1.2.6,62 项编号评价)中经源码与复现核实的缺陷全量落地。这轮的关键词是把"声称的修复"逐条对齐实现——评审抓到的数处"上轮未兑现",本轮每一项都带实测验证。
停止语义(P1)
- 回退重发现在是可取消的执行: armed 快照(会话文件、项目、提示词)+ 代次令牌;用户停止、切换 tab、切换项目或更新的回退都会使其失效,且两个定时回调在触发时重新校验(旧实现读活状态,停止后仍会重开会话重发)。
- 回退打开的新 tab 由回退自己接管(重锚定),不再误判"被别人切换"——chat-e2e 真实浏览器验证了整条链(停止守卫 + 快照 + 重发渲染)。
- 新会话携带发送前选择的思考等级(WS open 帧带
thinking)。
账本与统计正确性(全部实测复现后修复)
- 估算费用不再随查询次数增长:usageCache 返回缓存对象,旧的折算直接改写它——同一会话 0.0015→0.003 的翻倍机制(就地修改缓存)移除,折叠改入局部变量。
- cacheWrite 贯通全部维度:models/providers 分项此前完全没有该字段;日桶补齐 cacheWrite 与按日估算费用。
- ZCode 账本读真实字段名:rollout 的
cacheReadTokens/cacheWriteTokens此前被通用递归归零,改为直读。 - Codex 累计量做差分、按日分桶:token_usage 事件是累计值,旧实现把整文件用量堆在会话开始日(且窗口钳制把老会话的新用量整条丢掉)——现在相邻累计做差、逐条按自身时间戳入桶、窗口外丢弃。
供应商回退(P1,明文 key)
- 明文 key(models.json 内直接写 key——最常见的用法)现在真的能被 401 冷却:旧实现把冷却写进
$ENV轮换槽位,明文 key 永远读不到它,链路继续选坏 key;且providerHasKey被明文短路。修复 + 成功后解除。 - failover-e2e 的错误 PASS 条件整改:旧断言把"401 后仍可选"当 PASS——新断言要求 401 后该供应商必须从链上消失、成功后重新可用(本机实测 PASS)。
安全与完整性
- DOMPurify 3.1.7 → 3.4.16(三项公开公告影响范围);vendored 库进入 SBOM(从 vendor README 版本表读出——npm audit 永远看不到它们,SBOM 现在能看到)。
- runtime manifest 真正覆盖嵌套依赖:walk 里的旧守卫把每一层嵌套 node_modules 都跳过(12,552 个文件零覆盖,含刚钉住的 undici)——守卫移除后本地实测 13,954 条全覆盖(含 pinned 依赖 268 文件),AppImage 启动验证走通全链。
- afterPack 找不到布局或缺少外壳文件时抛错(此前静默跳过);壳 manifest 拒绝空文件集(与 runtime manifest 对齐)。
- 备份导入字段级校验:
config.json={}不再能覆盖有效配置(必须projects数组;cron/routing/models 各自门禁)——上轮的"对象即通过"确实是纸面校验。
其它核实修复
- cron:run-now 消耗当日窗口(手动+到点不再双跑);全局并发预算 2(8 个到期任务不再一次 tick 全启动);UI 新建任务携带当前项目 cwd。
readJsonl的无换行巨行在读取过程中丢弃(旧检查在读完整个文件后才跑,约束为零——评审对,我上轮放错了位置)。- MCP 失败现在抛进 pi 的执行层(返回值带 isError detail 仍被 pi 记为成功);lazy 目录带参数名与必填项。
口径留档(评审同意非补丁项)
- 跨项目面板上下文(#3)、只读导入的执行隔离(#4)、WS 重连补状态(#7):产品专项,按评审建议的优先级排入下轮专项而非补丁轮。
- 成品安装的 Windows/macOS 启动验收(#56)依赖签名与实机;Tauri 更新链接(#29 已在上轮改系统浏览器)、无头验证边界照旧文档化。
门禁:lint 0、测试 40/40、路由扫描 47 PASS/0 FAIL、ws-probe / failover-e2e(含新明文 key 断言)/ ui-probe / chat-e2e 全 PASS、CI 8 job(AppImage 启动实测 13,954 条 manifest 全链)、manifest 覆盖数与 SBOM 断言实测。