Skip to content

Releases: cullysu/pi-workbench

v1.3.7 — 草稿保全、队列诚实与保存回滚

Choose a tag to compare

@cullysu cullysu released this 17 Oct 17:48

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 — 进程身份、凭据边界与跨进程互斥

Choose a tag to compare

@cullysu cullysu released this 16 Oct 18:25

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.id last-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 — 失败路径、信任契约与数据正确性

Choose a tag to compare

@cullysu cullysu released this 12 Oct 15:14

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 条会话上限明示为窗口。

数据正确性

  • 裸 null JSONL 行在读取器即跳过(不再拖垮会话列表/统计/树);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 协议

Choose a tag to compare

@cullysu cullysu released this 12 Oct 09:02

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 失闭与导入隔离修复

Choose a tag to compare

@cullysu cullysu released this 12 Oct 01:24

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 null WS 帧在属性访问前被拒;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 — 版本身份修复与执行可靠性

Choose a tag to compare

@cullysu cullysu released this 11 Oct 13:17

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 修复与执行正确性

Choose a tag to compare

@cullysu cullysu released this 11 Oct 09:24

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 用户取消与契约探针

Choose a tag to compare

@cullysu cullysu released this 11 Oct 06:36

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 — 已排期产品专项落地

Choose a tag to compare

@cullysu cullysu released this 10 Oct 20:26

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 — 停止语义、账本正确性与第十七波收口

Choose a tag to compare

@cullysu cullysu released this 10 Oct 19:42

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 断言实测。