Skip to content

feat(sdk): pi SDK 0.84.3 → 1.0.4(官方原语替换 + MCP 设置面板 + 界面打磨) - #90

Merged
Jaxton07 merged 13 commits into
mainfrom
feat/pi-sdk-1.0-upgrade
Oct 6, 2026
Merged

Jaxton07 merged 13 commits into
mainfrom
feat/pi-sdk-1.0-upgrade

Conversation

@Jaxton07

@Jaxton07 Jaxton07 commented Oct 6, 2026

Copy link
Copy Markdown
Owner

改动

把 pi SDK 从 0.84.3 升到 1.0.4(@earendil-works/pi-coding-agent / pi-ai,backend + desktop 两处声明都钉版本),
并按 1.0 的行为变化做了一轮「官方原语替换 + 能力补齐」,顺带落地 MCP 设置面板与一批界面打磨。
分三块:

1. SDK 0.84.3 → 1.0.4(主体)

  • 官方原语替换:能交给 SDK 的都交出去,不写兼容分支、不做版本嗅探。
  • P0 行为修复:
    • 撤回「发出后没有回复」的用户消息后上下文不再残留幽灵消息(手动回退 leaf 要补 session.refreshContext());
    • 任务列表面板改为扫 getBranch(),压缩后不再清空;
    • 会话重命名改精确查表(不再全目录枚举);
    • sessionFile 语义订正:路径在 create 时分配、内容首条消息才落盘 → 判落盘只认 existsSync,
      未落盘会话改名是合法操作(不再报错)。
  • 权限声明层(permissions/annotations.ts):非内置工具默认 readOnlyHint? allow : ask ——
    MCP / 第三方扩展工具没声明就一律确认;codemode 归高危(能跑任意脚本)。
  • 接入官方内置扩展(codemode / tool-search / mcp):注册顺序为 [...我们的, ...builtinExtensions],
    同名工具我们要赢;我们不标 replaceable、官方三个都标 → 将来同名被挤掉的是官方。
  • azure 旧 id 在读取侧归一;扩展加载诊断分级(未接入的官方内置只给一行提示,不再报红)。

2. MCP 设置面板(设置 → MCP)

  • 增删改 mcp.json(全局 ~/.pi/agent/mcp.json + 项目级 <项目>/.pi/mcp.json),
    新建默认 exposure: "deferred" + autoEnableCodemode: false。
  • 每行显示状态(已连 / 连接失败 / 已停用)、工具暴露方式、已连上的工具清单(标只读 / 写·会弹确认);
    失败原因来自解析官方 notify 文本(官方没有状态 API),解析不出来就不猜、回落「未连上」档。
  • 登录:官方 notify 给出授权 URL 时「登录」按钮真的打开授权页(不做假按钮)。
  • 新增 / 编辑 / 粘贴 JSON 走设置二级页面(左上角「← 返回」,不是浮层);暴露档位五档平铺。
  • 「重连」调用 reloadSessionsForCwd:跳过运行中的会话并回报 {reloaded, skipped},避免 reload 的
    session_shutdown 静默打断别的会话。

3. 界面打磨

  • 设置弹窗:卡片无边框(只靠 shadow-dialog 分层,与 quit-confirm 设计稿一致)、顶栏去掉底边线、
    左导航分界改「中段实、两头淡」发丝线;同族弹窗(确认 / 登录 / 信任)一起去掉 1px 边框。
  • 抽出竖向发丝线 .fade-rule-v,与主界面左栏共用同一个类。
  • 修一个自己引入的回归:.edge-fade 的 mask 会把整个绘制子树裁在容器盒里,
    挂在面板内的 fixed inset-0 登录蒙层因此只盖住右列、卡片被裁掉半边 →
    LoginDialog 改 createPortal(..., document.body)。

验证

  • npm run lint / npm run typecheck / npm run test:全绿(backend 69 文件 626 项、desktop 57 文件 666 项)
  • 真实 SDK 冒烟:npx tsx scripts/smoke-sdk-1.0.mts(新增入库)—— 14/14
  • 阶段冒烟:smoke-stage1/2/3/4/6 11/11、6/6、8/8、11/11、8/8
  • 真机 GUI(CDP 截图 + DOM 断言,脚本在 .local/tmp/):
    设置外框 11/11、MCP 面板 13/13;含两条免复发守卫 ——
    ① 结构不变量:mask 生效的 .edge-fade 容器内不得有 fixed 后代;
    ② 像素断言:开/关登录弹窗各截全窗,采样「设置顶栏 + 左导航」两点必须都变暗(改前 Δ=0/0,改后 Δ=-37/-46)。
  • 打包态冒烟:electron-builder --mac --arm64 通过,asar 内 quickjs-wasi/quickjs.wasm 就位;
    隔离 PI_CODING_AGENT_DIR + --user-data-dir 下 4/4(IPC / MCP 连上 / codemode 调 MCP 工具 / 读图)。

已知限制(不阻塞合并)

  • MCP OAuth(远程 server 授权)未做端到端验证:只在 stdio server 上验过连接与调用。
  • 不提供本地 llama.cpp 支持:官方内置扩展的工厂没从包根导出,本版未接入(界面上只显示一行不可用提示)。
  • MCP 服务器命令建议写绝对路径(图形进程 PATH 比终端窄,node/npx 可能找不到)。
  • cacheWarming 默认开启(官方 "streaming"),Percho 界面暂无开关,可在 ~/.pi/agent/settings.json 设 "off"。

发版说明

用户可见变更(New Features / Changes / Fixes / Notes / Known limitations)已按发版口径写成草稿,
发版时贴进 GitHub Release notes 即可。

阶段 0(冒烟与事实核对):
- 依赖 backend/shared/desktop 三处同升 1.0.4(desktop 声明用于 electron-builder
  extraResources 从根 node_modules 取包,不升会双版本安装)
- 新增 scripts/smoke-sdk-1.0.mts:9 条断言钉住 1.0.4 能力面(零凭证零网络);
  版本按 import.meta.resolve 判,不用 mod.VERSION(打包态它读 PI_PACKAGE_DIR
  指向的 pi-package,且会被 Percho 子进程继承)
- 事实:升 1.0.4 的编译面干净(0 报错),风险全在运行时

阶段 1(P0 行为修复):
- prompt preflightResult:0.84.3 是 boolean,1.0.4 是 "handled"|"queued"|"started"
  字符串 union(被拒时不再回调)→ 改三个显式分支,注释订正契约
- 撤回悬挂分支:删掉 1.0.4 起无人读取的 `agent.state.messages = …` 手写赋值,
  改调官方 refreshContext()(该分支绕过 navigateTree,不刷会让 session.messages
  停在撤回前 = 幽灵消息,影响 messageCount 与 compaction 判定)
- sessionFile 语义订正:1.0.4 是「路径在 create 时分配、文件内容等首条消息才写」,
  判「落盘了没」只认 existsSync;未发消息的会话改名是合法操作(随首条消息一起
  落盘、不丢名),不加落盘前置拦
- 蒸发偏移口径复核:system 从不参与 part 提取,1.0.4 的 wire 过滤 system 不改
  决策 → 只补注释 + 单测锁不变量;replay --core 与内置基线 diff 逐字节一致
- 新增 plan 未列出的行为变化修复:compaction 摘要请求 context 形状(systemPrompt
  归一成 messages[0] 的 system 字符串消息)→ compaction-image 断言按纯文本语义

验证:typecheck / lint / test(backend 580 + desktop 654)全绿;真实 SDK + 真实
模型的隔离手测 11/11(改名语义、受理回执与撤回无幽灵、在线蒸发按档位工作)。

文档:docs/INDEX.md(钉 1.0.4 + 新增冒烟脚本行 + 撤回 refreshContext)、
docs/PITFALLS.md(新增「typecheck 全绿 ≠ 无行为变化」条目 + 症状索引)。

channel: .local/agent-work/channel/pi-sdk-1.0-upgrade/IMPL-NOTES.md
- getTodos 换数据源:扫会话分支 getBranch()(权威历史)而不是压缩后的请求投影
  (1.0.4 起 session.messages = agent.state.messages = 压缩后的 wire,压缩一发生 todo
  工具结果就没了);顺带删掉不可能命中的 todo-reminder custom message 兜底分支
  (那条只在 context 钩子的 wire 上、不落 entry)。新增 todos-source.test.ts
- 离线改名/历史透视改用 SessionManager.findById(cwd, id) 精确查表(1.0.4 新增,只扫该项目
  会话目录),替掉 listAllSessions() 全量枚举;findById 只给路径,readOnly 判定改用
  isSubagentSessionPath(path)。session:setName 契约加 cwd?(渲染端从会话 meta 带上来),
  LAN 历史透视拿不到 cwd 时保留枚举兜底
- 自研工具换 defineTool() 写法(本阶段不加 annotations,留阶段 3):todo / show-image /
  webfetch / channel-watch×4 / subagent;顺带删掉 channel-watch 里手写的
  `params as { topic?: unknown }` 断言(schema 推导已够)
- azure 旧 id 读侧归一:1.0.4 把内置 azure-openai-responses 改名为 azure,用户 models.json
  里的旧键条目仍判为「覆写内置」(LEGACY_PROVIDER_IDS),作为新增输入则拒绝并提示新 id;
  编辑/删除走新 id 时顺手清旧键(不留孤儿)
- 撤回 jsdoc 补纪律:任何绕过 navigateTree 的手写 leaf 回退都必须自己 refreshContext()
- task 12(webfetch 截断换 truncateHead)跳过:字符口径 vs 行+字节口径,属改行为

task 10(内置 subagent 加 replaceable)实现后回退:实测同名工具「先注册者赢」+ inline 扩展
恒排在盘上扩展之后 → 切 inline 注册会让「内置优先」档输给第三方并多报冲突诊断。
replaceable 只能表达「我让位」,表达不了「我优先」。证据与备选见 channel IMPL-NOTES。

验证:typecheck / lint / test(backend 590 + desktop 654)全绿;阶段 0 冒烟 9 条仍过;
replay --core 与阶段 1 基线逐字节一致;真实 SDK+模型冒烟 6/6(defineTool 后工具全 active /
getTodos / findById 离线改名 / azure 归一)。

docs:INDEX(getTodos 数据源、setName 带 cwd、azure 归一)、PITFALLS(replaceable 的真相 +
models.json 任何 key 都是独立 provider 行)。
- 新增 permissions/annotations.ts:非内置工具(MCP server 工具、第三方盘上扩展工具)默认动作 =
  annotations.readOnlyHint === true ? allow : ask;缺声明/声明为写一律 ask(官方明确 annotations
  未验证,不能当放行依据)。接在规则求值之后:只把 allow 收紧为 ask,显式工具名规则永不被推翻
- 「非内置」判定 = 已知内置工具名集合 + ToolInfo.sourceInfo(不靠 unregister):
  SDK 自带 builtin:<name>(read/bash/powershell/edit/write/grep/find/ls)/ Percho 自研 <sdk:…>
  与 <inline:…> 视为受信(走既有硬编码分类);builtin:mcp 与名字前缀 mcp__ 一律不受信;
  查不到的工具 fail-safe 走 ask
- DEFAULT_PERMISSION_CONFIG.rules 加 codemode: "ask"(脚本不透明,等同 bash 的 sudo * 档)
- 自研工具按真实属性声明 annotations(不为少弹窗谎报只读):todo/show_image/webfetch/channel_list
  只读(webfetch 另加 openWorldHint);channel_subscribe/unsubscribe/post 非只读;subagent
  非只读 + destructive
- stores/settings.ts:剥掉 Electron invoke 错误前缀(Error invoking remote method '<ch>': Error: ),
  表单校验提示显示后端原文(含「azure-openai-responses 已改名为 azure」这类提示)

测试:permission-annotations.test.ts(纯函数矩阵 9 例)+ permission-extension.test.ts 新增 5 例
(MCP 只读放行 / 无声明弹窗 / readOnlyHint=false 弹窗 / 第三方扩展工具弹窗 / codemode 默认 ask /
MCP 也吃显式规则);原「自定义工具默认放行」按新语义改为默认 ask。

验证:typecheck / lint / test 全绿(backend 603 + desktop 654);真实会话+真实模型冒烟 5/5
(假 MCP 写工具弹确认并 block、只读 MCP 工具放行拿到结果、Percho 内置 todo 不弹)。

docs:INDEX(permissions/annotations.ts + 非内置默认动作速查)、PITFALLS(getAllTools 在 API 不在
ctx、sourceInfo.path 取值表)。plan 任务 12 就地标注「已研判:跳过 + 理由」。
- 新模块 project/builtin-extensions.ts:照抄 CLI 侧元数据注册三个官方内置扩展
  ({name, factory, builtin: true, replaceable: true}),不自己发明字段;llama.cpp 不接
  (它的工厂没从包根导出,exports map 拦住,注释写明官方导出 builtInExtensions 后怎么补)
- trust-loader 把内置扩展排在我们自己的扩展之前(同名覆盖时我们的在后,replaceable 让位语义不受影响)
- openExternal 全链路透传:PiBackendOptions.openExternal → loader deps → createMcpExtension
  (openUrl);桌面端 main 传 shell.openExternal(MCP OAuth 授权页交给系统浏览器)
- DefaultPackageManager 加 builtinExtensions: ["codemode","tool-search","mcp"]
- 未知 builtin 的诊断降级为 info:shared 类型加 level、renderer 灰字渲染(i18n 双字典)、
  单测锁映射;实测「设置里写 +builtin:llama.cpp」在 SDK 层是静默忽略(settings.extensions 是
  模式表,只在宿主名单里的名字才成为资源),所以这条映射是防御性的,见 IMPL-NOTES §3.1
- 补注释:自研工具声明 annotations 对本机权限门控不生效(走内置短路,见 permissions/annotations.ts)

验证:typecheck / lint / test 全绿(backend 605 + desktop 654);阶段 0-3 冒烟全部复跑通过;
阶段 4 冒烟 4/4(三个内置扩展加载 / MCP 真连上并注册 mcp__demo__* / system prompt 出现
mcp_servers 段 / 无红级诊断),用 .local/tmp/fake-mcp-server.mjs 手搓的最小 MCP stdio server 驱动。

docs:INDEX(builtin-extensions.ts、openExternal 选项)。

channel:IMPL-NOTES 追加阶段 4 节 + 更正上一轮「MCP 三态 mcp.remoteTools」的说法(该词 spec/plan/SDK
三处零命中,是我凭印象编的;正确的旋钮是 mcp.json 的 server 级 exposure(4 值,默认 codemode)
+ toolExposure + 顶层 autoEnableCodemode,阶段 5 按 spec §4.8 原样做、不新增用户可见设置项)。
先办 review 点的三件事:
- 内置扩展注册顺序修正为 [...我们的, ...builtinExtensions]:getAllRegisteredTools() 是同组
  「先注册者赢」,我们要在前;且官方三个内置都标 replaceable,我们不标 —— 将来官方同名工具
  被我方挤出局。抽 composeExtensionFactories() 接缝 + builtin-extensions.test.ts 锁住
- scripts/fake-mcp-server.mjs 入库(手搓最小 MCP stdio server,带 readOnlyHint 的 echo +
  无声明的 nuke;不进任何默认配置)
- PITFALLS 更正同名工具机制:replaceable 只表达「我让位」,「我优先」只能靠 customTools,
  注册顺序只在双方都非 replaceable 时决定归属(上轮写成「inline 在后→后写覆盖」是错的)

阶段 5(plan 22–27):
- shared:McpServerView/McpExposure/工具名解析(parseMcpToolName/groupMcpTools)+ MCP_CHANNELS
  (configList/configUpsert/configRemove)+ mcp:configReload(面板「重连」入口)+ mcp.tools:changed
- backend:mcp/config-store.ts(全局 + 受信项目 mcp.json 读写;形状校验进 errors;新建 server
  写 exposure: deferred、新建文件写顶层 autoEnableCodemode: false;不实现连接、不展开环境变量);
  tools/mcp-inventory.ts(三时机聚合 mcp__<server>__<tool>,集合变化才报);PiBackend 面板 API +
  写盘后 reload 受影响会话;mcpToolsMap() = 事件缓存 + registry 实时聚合(修「重连后一直未连上」)
- renderer:stores/mcp.ts + McpPanel(分区/胶囊/工具数/改 deferred/重连/编辑/删除/添加表单/错误/
  未受信与无活跃会话说明)+ tool-label 把 mcp__server__tool 呈现为 server › tool + i18n 双字典
  把设置分类名从「MCP 插件」改为「MCP」;表单提示「命令建议写绝对路径(GUI 进程 PATH 更窄)」

验证:typecheck / lint / test 全绿(backend 617 + desktop 659);阶段 4/5 冒烟 8/8(真实 MCP
连接、mcp_servers 段、面板数据、一键 deferred、autoEnableCodemode、重连后工具回归);
真机 dev 应用 CDP 验收:设置→MCP 面板渲染正确、IPC 往返正确(工具数显示需开会话,见 IMPL-NOTES §2)。

docs:INDEX(mcp/config-store、tools/mcp-inventory、stores/mcp、McpPanel、速查表一行)、
PITFALLS(同名工具机制更正)。
REVIEW §3 缺口(必须先修):
- reloadSessionsForCwd 跳过 isStreaming/isCompacting 的会话:AgentSession.reload() 没有运行中守卫、
  不 abort 在跑的 turn,却会 emitSessionShutdown → 官方 mcp 扩展关掉全部连接、旧 runner 被废弃
  (失败还被 catch 吞掉 = 用户视角「点了没反应」)。返回 { reloaded, skipped:[{sessionId,reason}] }
- 三个写盘动作改返回 McpMutationResult { config, reload }(shared 类型 + 3 个 IPC 通道同步)
- 面板:当前会话运行时「重连」disabled + title 提示;动作后分两档文案(已重连 N / 已写入但 M 个运行中)
- 单测 mcp-reload-guard.test.ts;阶段 4/5 冒烟 step 8 真机复现了跳过(skipped 里是 streaming 的 spy 会话)

阶段 6 其余:
- 用量口径:1.0.4 的 getSessionStats 已把任何 kind 的 usage entry(含 cache_warm)计入 → 无需改代码;
  补 usage-stats.test.ts 锁住
- 文档:INDEX(McpPanel 去掉「0.84 占位」+ 新文件行 / shared mcp.ts / stores mcp.ts + 速查表)、
  extension-ui.md SDK 钉 1.0.4、.local/docs/release.md 依赖钉 1.0.4、PITFALLS compaction 形状补 systemMessage
- 用户可见变更说明草稿:.local/agent-work/handoff/pi-1.0.4-release-notes.md
- 打包验证:build + electron-builder(arm64/x64 dmg+zip)+ asar 内 quickjs.wasm / 1.0.4 扩展 / 新 renderer 代码
  都在;extraResources(export-html / theme / assets / docs / examples)复核齐全,未加 asarUnpack;
  打包态(隔离 PI_CODING_AGENT_DIR + --user-data-dir)冒烟 4/4:IPC / MCP 连上 / codemode 调 MCP 工具
  (quickjs wasm 可用)/ 读图

验证:typecheck / lint / test 全绿(backend 620 + desktop 659);阶段 0-6 冒烟全过;GUI(dev)9/9;打包态 4/4。
…r extract-file 坑)

- release notes(.local/agent-work/handoff/pi-1.0.4-release-notes.md):
  补「缓存续命默认开启(cacheWarming: "streaming"),可在 ~/.pi/agent/settings.json 关("off")」,
  并把「不提供本地 llama.cpp 支持」写进 Known limitations;同时标注 MCP OAuth 链路未做端到端验证
- PITFALLS:新增「取证别用 asar extract-file」——它按 basename 解到**当前工作目录**,
  会在仓库里掉垃圾文件并让 biome 多报 warning;批量取证用 asar extract 到 .local/tmp/<dir>

DONE.md 已写(三命令摘要 + 验收清单逐项 + 已知未验证项 5 条)→ 频道终态。
设计稿:.local/design/ux/mcp-settings-panel/(index.html 可交互画板 + DESIGN.md + preview.png)

面板(components/settings/mcp/,替换原单文件实现):
- 顶部只留状态摘要(已连接 N 个 · 共 M 台 · K 个工具),绝对路径与「重连语义」收进页脚折叠区
- 行分三层:状态点+名称+档位胶囊+传输+状态词 / command(mono 单行中段省略)/ 描述+只读统计;
  操作 hover/键盘聚焦才显形;停用的整行降权并垫底
- 行内展开三态:工具清单(带「只读 / 写 · 会弹确认」)、失败原因(官方 notify 原文 + 可执行建议)、
  删除确认条;失败行给「查看原因」、需授权行给「登录」(真的打开官方授权页)
- 新增/编辑改右侧 popover(字段区可滚 + 操作条常显):写入位置/传输方式/暴露档位全用分段控件,
  命令非绝对路径给红字提示;空态给「添加服务器 + 粘贴 JSON 配置」两条入口;> 8 台才出现搜索框
- 运行中重连:顶部常显提示条(把禁用原因说出来)+ 行内小字

运行态数据链(补齐设计稿缺的两条数据):
- shared:McpToolView{name,readOnly} / McpServerStatus{tools,error,needsAuth,authUrl} / McpGlobalNotice;
  事件改名 mcp.tools:changed → mcp.servers:changed(载荷带运行态与全局提示)
- backend:tools/mcp-inventory.ts 聚合时带上 ToolInfo.annotations.readOnlyHint;
  新增 mcp/notices.ts 解析官方 mcp 扩展的 notify 文本(「MCP servers need attention」逐行 → 失败原因 /
  needs sign-in / 授权 URL;Sign in / Signed in 生命周期;MCP failed to load → 全局 error;
  工具不可达 → 全局 warning),解析不出来**不猜**(回落到「未连上」档)
- config-store / PiBackend:视图与事件都带上 error/needsAuth/authUrl/notice

测试:backend 新增 mcp-notices(9 例)、mcp-inventory(collectMcpTools 只读标记);desktop 新增
mcp/pure.test.ts(状态矩阵/排序过滤/粘贴解析 8 例)+ 面板初始态渲染冒烟;mcp-config 跟随新形状。
验证:typecheck / lint / test 全绿(backend 626 + desktop 666);阶段 4/5/7 冒烟 11/11(含
「起不来的 server 的官方 notify 被解析成失败原因」端到端);真机 GUI 验收 11/11(截图 .local/tmp/mcp-v2/)。

docs:INDEX(面板/写路径/运行态三段重写 + mcp/notices.ts)。
- 可展开项统一用通用箭头 ChevronRightIcon:收起指向右、展开旋转 90°(配置文件 / 查看原因),
  不再用文本「⌄ / ⌃」;新增图标 ArrowLeftIcon(设置二级页面的返回入口)
- 新增/编辑从右侧 popover 改成**设置二级页面**:左上角「← 返回」+ 标题 15px + 字段单列铺开 +
  底部右对齐「取消 / 保存」;「粘贴 JSON 配置」同样走二级页面(原来字段多时必须滚动)
- 表单字段保持精简(对照 Codex 那套「环境变量 / 环境变量传递 / 工作目录」不塞进表单):
  只在页面底部留一行指引「高级项去编辑 mcp.json」,一般用户不受干扰、高级用户不被堵
- 设计稿同步:新增画板 ⑥(二级页面)、概览表补「可展开项」行、决策点 2 就地改成二级页面、
  画板里的箭头全部换成图标;DESIGN.md 加 v1.1 修订节

验证:typecheck / lint / test 全绿(backend 626 + desktop 666);真机 GUI 验收 13/13
(新增两条:二级页面有返回按钮且列表被替换、底部有高级项指引);阶段 4/5/7 冒烟 11/11。
- 编辑器里的「更多 ⌄」去掉:总共就 5 个档位,折叠只是多一次点击 → 按需激活 / 仅脚本 / 直接 /
  仅模型 / 隐藏 全部平铺(i18n 里不再需要的 more 键一并删掉)
- 设计稿同步:画板 ⑥ 的分段控件换成五档、决策点 3 就地改成「五档全部平铺」、DESIGN.md 加 v1.1 第 3 条
- PITFALLS 新增一条:关 dev 应用只 `pkill electron-vite` 会留下 Electron 子进程占住 9224,
  新实例 devtools 起不来、CDP 连到旧实例的 chrome-error 页(自检:连上先看 location.href)

验证:typecheck / lint / test 全绿(backend 626 + desktop 666);真机 GUI 验收 13/13;
阶段 4/5/7 冒烟 11/11;我起的 dev 实例与残留 Electron 进程已全部清理。
- 顶栏:去掉 `border-b`(与对话页顶栏同一决策 —— 边界交给内容区的滚动边界淡出),
  内容区与左导航各自挂 `.edge-fade`(useEdgeFade;不溢出时不淡)
- 左导航与内容的分界:`border-r` 换成竖向发丝线 `.fade-rule-v`(中段实、两头淡),
  与主界面左栏**共用同一个类**(原先 `.sidebar::after` 是复刻了一份同样的渐变,
  并把旋钮 `--sidebar-rule-solid` 统一成 `--rule-solid`)
- SettingsDialog 拆成「只在打开时挂载」的外壳 + 弹窗体:useEdgeFade 在挂载时取 ref,
  元素后出现不会重跑,所以弹窗体必须随打开而挂载
- 文档同步:AGENTS.md 设计语言条目指向 `.fade-rule-v`;docs/INDEX 设置弹窗行补外框说明;
  MCP 设计稿 DESIGN.md v1.1 补第 4 条(画板里 nav 用的是「canvas 底色」,实装改成同底色 + 发丝线)

验证:typecheck / lint / test 全绿(backend 626 + desktop 666);真机 GUI 7/7(新增
.local/tmp/check-settings-chrome.mjs:顶栏无底边线 / nav 无 border-r 且有渐变 ::after /
两列 edge-fade / 滚动后触发淡出);MCP 面板 13/13;左栏开合动画脚本复跑正常,
并用自写的 PNG 逐点采样(.local/tmp/png-sample.py)确认像素层面:发丝线 1px、
浅色 223 / 深色 54、中段实约 50%、两端对称淡出(左栏与设置弹窗一致)。
设计语言里「浮层/卡片一律无边框」早就写着,但四个弹窗都还挂着 `border border-border`:
设置 / 登录 / 信任 / 确认。其中 ConfirmDialog 更明显 —— 它的设计稿(.local/design/ux/quit-confirm)
白纸黑字写着「卡片无边框,靠 shadow-dialog 分层,符合全站浮层规范」,实装却留了边框。

本次把四处边框一起去掉(尺寸/圆角/阴影都不动)。深色下边框本来也只是把白色发光的边补实,
去掉后与 RenamePopover 等其它浮层一致。

验证:typecheck / lint / test 全绿(backend 626 + desktop 666);真机 GUI 9/9
(新增两条断言:弹窗卡片 border-top=0px 且无 border 类、同族登录弹窗同样无边框),
浅色/深色各出一版截图(卡片外扩 28px 把投影一起收进画面)。
用户报的两个症状其实同一个根因:`.edge-fade` 的 mask 作用于**整个绘制子树**,且蒙版只覆盖容器自己的盒子 ——
设置弹窗右列刚加上的滚动边界淡出,把挂在 ProvidersPanel 里的 `fixed inset-0` 登录蒙层裁在右列盒内:
只盖住那一片、卡片超出盒子的左半边直接不画(下面不透明的左导航就「盖」上来了)。
`position: fixed` 没跑偏(rect 量出来仍是整窗),所以只有像素能看出来 —— 排查时别只信 getBoundingClientRect。

- LoginDialog 改 `createPortal(..., document.body)`(本项目其余 fixed 浮层都是这么做的,只有它漏了)
- 顺手修 McpPanel 的 cwd 来源:原来在 sessions 列表里按 activeSessionId 找 cwd,而那是**异步加载的子集**,
  刚打开会话、列表还没跟上时找不到 → 面板误报「当前没有打开的会话」。改用 sessions store 的 `cwd`
  (与左栏 activeCwd 同一事实源),并把「运行态要会话上报」的判断拆成独立的 `hasSession`
- 加两道免复发守卫(.local/tmp/check-settings-chrome.mjs,改前跑会红、改后绿):
  ① 结构不变量:任何 mask 生效的 `.edge-fade` 容器内不得有 fixed 后代(改前 fixedCount=1 精确点名)
  ② 像素断言:开/关登录弹窗各截全窗,采样设置顶栏与左导航两点都必须变暗(改前 Δ=0/0,改后 Δ=-37/-46)
- PITFALLS 新增条目(含「rect 对、像素不对」的判别法);AGENTS.md 设计语言补一条:
  `.edge-fade` 容器内不得挂 fixed 浮层

验证:typecheck / lint / test 全绿(backend 626 + desktop 666);设置外框 GUI 11/11;
MCP 面板 GUI 13/13(且这次是在「刚启动、没手动开过会话」的状态下跑过的 —— 正是原先会误报的场景)。
@Jaxton07
Jaxton07 merged commit b888a25 into main Oct 6, 2026
1 check passed
@Jaxton07
Jaxton07 deleted the feat/pi-sdk-1.0-upgrade branch October 6, 2026 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant