# AGENTS.md — pi-web-ui 项目指南

> 给 AI 编码助手（pi / Claude Code / Cursor 等）看的高层指南，细节按主题在 `docs/`。
> 修改后在 pi 里跑 `/reload`。精简前全文备份：`AGENTS.md.bak`。

## 1. 项目是什么

pi-web-ui 是 pi 编码智能体（`@earendil-works/pi-coding-agent` SDK）的 Web 聊天界面：
浏览器对话、文件树、附件、内置终端（xterm.js + node-pty）、模型管理、声音提醒、中英文切换。
一条命令可跑，可 Docker / systemd / launchd / Windows 计划任务部署。
另有 **Electron 桌面壳**（`desktop/`，随机空闲口起同一 server，网页版零改动；见 `desktop/README.md`）。

- 仓库：`git@github.com:xing-shuyin/pi-web-ui.git`；npm 包 `pi-web-ui`
- Node **>= 22.19.0**；版本 `package.json` 与 `package-lock.json` 两处同步

## 2. 技术栈

| 层     | 技术                                                                   |
| ------ | ---------------------------------------------------------------------- |
| 后端   | Node + Express（静态 + `/api/health`）+ `ws`（`/ws`）                  |
| 前端   | React 19 + Vite 8 + react-markdown + highlight.js + xterm.js           |
| 智能体 | `@earendil-works/pi-coding-agent` SDK（进程内，读 `~/.pi/agent` 配置） |
| 终端   | node-pty（服务端 PTY）+ `@xterm/xterm`（经 terminal bridge 转发）      |
| 样式   | 单文件 `web/src/styles.css`（CSS 变量主题，默认深色）                  |

## 3. 目录结构

完整注释版目录树见 `docs/directory-reference.md`（扁平速览）。

顶层：`server/` 后端（ESM，编译到 `dist/server/`）· `web/` 前端（编译到 `web/dist/`，gitignore 但进 npm 包）·
`bin/pi-web-ui.mjs`（CLI）· `desktop/`（Electron 壳）· `deploy/`（launchd/systemd/Windows 任务示例）·
`themes/`（完整样式表主题）· `plugins/`（官方插件，各自 README；`catalog.json` = 市场内置列表，加插件在此加一条 + PR；`page-picker/extension` 是浏览器扩展不是插件）·
`extensions/webui.ts`（pi 扩展 `/webui`）· `dev/`（本地辅助，不进包）· `docs/`（见 §4 表）· `tests/`（见下）。

server 关键文件（★=单源/入口，必读；其余按名读源码，不在此逐条注释）：
`protocol.ts`★（wire 协议唯一事实源）· `index.ts`（express + `/ws` 分发 + 心跳 + `pending` 重放队列）·
`agent-service.ts`（ClientSession/多对话并发/工具注册 customTools）· `serialize.ts`（SDK→UiMessage，`details` ≤64KB 下发）·
`tool-manager.ts`★（`AGENT_TOOL_CATALOG`，工具开关唯一事实源）· `tool-approval.ts` + `approval-rules.ts`（审批门禁 + 规则库）·
`client-state.ts`（`client-state.json` 持久化：locale/workspaceRoots/softCap…）· `composer-drafts.ts`（草稿中心）·
`plugins.ts`（`UI_SLOT_ALIASES`/`UI_SLOTS` 别名+枚举）· `plugin-catalog.ts`/`plugin-catalog-sync.ts`/`plugin-installer.ts`（市场目录同步+后台安装）·
`plugin-grants.ts`（目录授权）· `plugin-project.ts`（host.project.create）·
`subagents.ts` + `subagent-templates.ts`（第一方子代理+模板库）·
`attachments.ts`（只发路径引用，不注入内容）· `files-service.ts` + `text-sniff.ts`（`decodeText`/`countLines`）·
`vision-bridge.ts`（纯文本模型看图转写）· `present-files-tool.ts`（只读探测+摘录预算）·
`terminals.ts`（PTY 管理；`terminalBash` 开关分流）· `scm.ts`（只读 git，execFile 直跑）·
`model-admin.ts`（models.json+多密钥）· `patch-remote-catalog.ts`（模型目录整表替换补丁）·
`soft-cap.ts`（压缩软上限纯函数）· `i18n.ts`（服务端语言：`resolveServerLang`/`pick`/`bilingual`/`getServerBlock`）·
`control-socket.ts`（本地 status/quiesce）· `launch-origin.ts`（托管探测）· `webui-context.ts`（扩展 UI 桥）·
`settings-service.ts`/`goal-service.ts`/`slash-commands.ts`/`themes.ts`/`uploads.ts`/`bg-servers.ts`/`queue-utils.ts`/`process-utils.ts`/`tool-info.ts`/`conversation-read-tool.ts`/`compact-context-tool.ts`/`edit-soft-tool.ts`/`read-tool.ts`/`ensure-bash.ts`/`patch-node-pty.ts`。
`server/dsh/`：`preset-clones.ts`（file: 克隆）+ `runtime/{launcher,goal-rpc,custom-prompt}.mjs` + `dsh-usage.ts`（统计映射）。

web/src 关键（★=先读这三个）：`use-chat.ts`★（WS+reducer+终端 bridge）· `app-globals.ts`★（模块级 store + `appSend`；快照数据禁入）·
`types.ts`（`export type * from "../../server/protocol"` shim，由 `scripts/check-protocol-sync.mjs` 守护）·
`ui-slots.ts`★（宿主 UI 扩展点：四层合并+22 slot）· `slot-toolbar.tsx`（各组件 `*HostNodes`+`renderMergedToolbar`）·
`base-url.ts`（`appUrl()`，见 §9）· `i18n.tsx`（文案只许字符串字面量，见 §9）· `styles.css`（全部样式）·
`notify.ts`（`isCollapsedWindow`/`shouldSuppressNotify`）· `composer-bridge.ts` + `composer-draft.ts`（`advanceComposerSession`/`focusComposer`）·
`topbar-fit.ts`（实测宽度溢出）· `use-floating-panel.ts`（浮层统一 Hook）· `pending-question.ts`（问卷恢复）·
`present-items.ts`/`present-settings.ts`/`file-preview-bridge.ts`（present_files 卡片）· `tool-info-state.ts`/`tool-schema.ts`· `plugin-host.ts`（宿主 API 时序）。
components 按文件名自解释，只记特殊机制：`ContextMenu`/`ToolInfoDialog`（portal 到 body + fixed，同因 overflow 裁剪）·
`TopBar`/`FooterBar`（单一扁平流，slot 顺序直排+`hostNodes`）· `SlotTabs`/`PluginPage`（切走即 cleanup 不挂载）·其余读源码。

tests：`run-smoke.mjs`（零 token 冒烟聚合，ALL 列表）· `unit/`（vitest 纯函数单测）· `*-test.mjs`（手写 Playwright/WS；**需 Chrome 的不入 smoke**，手动跑，缺 Chrome 自动 SKIP）· `scratch/`（一次性，gitignore）。
CI：`ci.yml`（协议同步→typecheck→build→vitest→冒烟）· `release-notes.yml` · `desktop-release.yml`。

## 4. 核心架构（每主题一句话）

`docs/` 索引（找详细文档先看这里，均为 `docs/<名>.md`）：**architecture-core** 快照驱动/协议单源/安全边界/主题/多对话并发 · **architecture-attachments** 附件/图片/视觉桥/上传预览下载 · **architecture-terminal** 终端 PTY/SCM/活力检测/bash 接管 · **architecture-plugins** 插件形态/协议/宿主扩展点/MCP 桥/多根工作区 · **architecture-system-prompt** 系统提示词组装链路与 override 钩子 · **dsh-engine** DSH 预设/问卷/底栏统计/工具桥 · **goal-conversation-design** 目标模式 2.0（把目标审查变成对话：执行对话干活 + 当前对话当审查者；**已实施且只剩这一条路径** —— v1 的隐藏隔离审查会话 + 自治标记路径已删除，轮次/熔断/代次全在服务端，见该文档 §4/§11）· **development** 开发工作流/CI/编码约定/测试规范 · **release** 发布流程 · **deployment** 部署 · **env-vars** 环境变量全表 · **antigravity-proxy** 反代接入 · **directory-reference** 完整注释版目录树

| 主题                  | 文档                               | 一句话                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| --------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 快照驱动              | `docs/architecture-core.md`        | 服务端唯一事实源，60ms 节流；`snapshot_delta` 增量 + `message_delta` 实时通道；`send()` 背压超阈丢 snapshot（幂等，250ms 重试）；`get_state`/`flushSnapshot` 立即推一次                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| 协议单源              | 同上                               | 只改 `server/protocol.ts`，两端 switch 各加分支；`web/src/types.ts` 是 shim                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| 全局运行态            | 同上                               | 整树共享放 `app-globals.ts`（`useAppField` 单字段订阅，组件不收 `send` prop 用 `appSend`）；快照流数据禁入 store                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| 安全边界              | 同上                               | 默认 loopback；WS Origin/Host 校验；quiesce 准入；provider headers 不下发浏览器                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| 主题                  | 同上                               | styles.css 是共享基线；主题=完整样式表可覆盖一切；终端跟随主题                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| 多对话并发            | 同上                               | 每对话独立 runtime，上限 8/项目（子代理不计）；运行列表口径 listed ∪ 有内容的当前对话；clientId 存 sessionStorage，每标签页独立                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| 附件/预览             | `docs/architecture-attachments.md` | 只给路径引用（`reference`/`lines`，内容不注入）；预览 512KB 上限+嗅探+GBK 回退；媒体走 HTTP Range；下载绕 Safe Browsing                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| 终端/SCM              | `docs/architecture-terminal.md`    | 每 Conversation 一个 TerminalManager；`terminalBash` 开关分流（`persist` 决定一次性/ai-bash 持久）；SCM 只读走 execFile，写操作走可见终端                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| 工具开关/read 目录    | `docs/architecture-core.md`        | `AGENT_TOOL_CATALOG` 唯一事实源；`read` 覆盖目录走 ls 口径（`readDirEnabled` 是行为开关，不入目录；仅 pi 引擎）；`read`/`write`/`edit` 三处覆盖与第三方扩展同名工具的共存（基底 = 扩展实现优先）见 `server/tool-overrides.ts`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| 审批                  | `server/tool-approval.ts`          | 规则库 `<dataDir>/approval-rules.json`（ask/deny/allow）；三档放行：全局关→本对话全部允许→允许同类；记忆只在内存，随过户搬                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| 问卷/草稿进快照       | `docs/architecture-core.md`        | `UiState.pendingQuestion`（刷新恢复）+ `UiState.draft`（只跟全量快照，`draft_update` 自带 sessionId）                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| 临时对话              | `server/agent-service.ts`          | `new_chat {ephemeral:true}`→内存不落盘；`UiState.isEphemeral` 恒存在于 light state；`persist_conversation` 一键转正（id 不变）                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| 插件/UI 扩展点        | `docs/architecture-plugins.md`     | `<dataDir>/plugins/<id>/`；manifest `ui` 与 `host.ui.register()` 同一套别名+枚举；合并「宿主<插件<arrange<用户偏好」；插件不碰 DOM；`PLUGIN_API_VERSION=2`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| 多根工作区            | 同上                               | `set_workspace_roots` 按项目 cwd 存（上限 8）；**额外根=工作区内**，插件读免授权；AI 只在主 cwd 干活                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| 子代理模板            | `server/subagent-templates.ts`     | 全局共享；append/replace + 白名单 + 可选模型/强度（空=跟随）；停用对 AI 不可见                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| DSH                   | `docs/dsh-engine.md`               | 四预设 file: 克隆 mount；问卷/技能钩子挂 agent scope（host 收不到 scoped 事件）；底栏统计吃直播帧+usage（`dsh-usage.ts`）                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| 压缩软上限            | `server/soft-cap.ts`               | C→`reserveTokens=W−C` live 注入；关/非法回填 16384；pi 引擎独有                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| 看门狗                | `docs/architecture-core.md`        | 20 分钟 abort 会话（不碰后台服务）；`ask_user_question` 豁免                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| present_files         | `server/present-files-tool.ts`     | 只读探测+摘录预算走 `details`；前端无 details 也能渲染（参数解析+合并）；远端本地打开报不支持                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| browser_page/对话引用 | `docs/architecture-core.md`        | 闸门在扩展侧；对话引用只发 `<conversation-ref>` aside，模型经 `conversation_read` 按需取                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| 计划模式              | `server/plan-mode.ts`              | **目标条展开行**的 `host:goal-plan` —— **一次性动作按钮，不是开关**：点一下 = 开服务端只规划闸门 + 把目标输入直接当消息发出（`set_plan_mode{true}` 紧跟 `prompt`，同 WS 按序 → 本轮在闸门内跑）；空输入则只开闸门。与提炼/发送同列同口径（「点一下直接发」），`goalModeEnabled=false` 时随目标条一起隐藏。**退出计划的唯一出口 = 计划看板里的「开始实施」**（PlanBoard：`set_plan_mode{false}` + 发实施请求）；开启时通过 `applyToolGating` 动态将写类与旁路工具从活跃集剔除（模型视野内无写工具，物理杜绝倾倒代码发起写调用）；`withPlanModeGate` 硬闸门挂 customTools 整列 + write/edit 覆盖 + 终端 `checkSafety`，非只读 bash 与越权写调用显式抛错拦截（isError 为 true）；提示词追加 `PLAN_MODE_SYSTEM_PROMPT`（切换时 reload，**正文可在设置页「提示词」区改**：append/replace + 自定义，语义同视觉桥/AI 提交信息，不进预设；**禁代码倾倒**——不贴整文件/完整函数，代码只留 ~20 行短片段；**小需求跳过 `plan_update` 走短计划**）；产物走 `plan_update` 看板；DSH 不支持。回归 `plan-mode-test` |
| 审查者模式            | `server/delegate-mode.ts`          | **自动委派（默认关）**：会话级开关（`set_delegate_mode`，设置页「审查」区 + 底栏 `host:status-delegate` 标识）→ 开启后本对话**只审阅不施工**：用户每条 prompt 由服务端转给一个**常驻落盘执行对话**（`dispatchToDelegate`，首轮 prompt 交给 `spawnSubagentConversation` 起步、之后 `sendUserMessage` 追加），主对话这一轮不跑模型；`withDelegationGate` 硬闸门拒写类/非常规 bash/**派发类**（派活是服务端的活）+ `DELEGATION_SYSTEM_PROMPT` 段（切换时 reload）。**计划模式优先**（同开不派活，否则与 plan 拒 spawn 死锁）；DSH 不支持（明确 notice，不静默）。执行对话跑完只通知（不自动验收）。布尔随转录落盘（`delegate/mode`），执行对话 id 只在本进程内有效。回归 `delegate-mode-test`                                                                                                                                                                                                                                                                                                          |

## 5. 开发工作流

> 详见 `docs/development.md`

```bash
npm run dev          # node --watch 后端(:8788) + vite 前端(:5173)
npm run typecheck    # 双端 tsc --noEmit（提交前必跑）
npm run format       # prettier（提交前必跑；只检查用 format:check）
npm run lint         # oxlint（提交前必跑；自动修用 lint:fix）
npm run build        # build:web + build:server
npm start            # 跑 dist/server/index.js（生产）
npm test             # vitest
npm run test:smoke   # 零 token 冒烟
```

约定：缩进 Tab；样式全在 `styles.css`；协议消息只改 `protocol.ts`（§4）；服务端 URL 一律 `appUrl()` 包一层（§9）。
i18n：前端 `useT()`，核心 `zh`/`en`（新 key 两处都加，`tests/unit/locales.test.ts` 锁对齐；语言包 `locales/*.json` 缺 key 回落英文）；
服务端 `pick(lang,zh,en,key?)`（key 全局唯一 `<模块>.<slug>`；多行 `getServerBlock`）；**工具定义提示词（description/promptSnippet/promptGuidelines）纯英文精简**，守卫 `tests/unit/tool-prompt-hygiene.test.ts`；notice 推 UI 用 `text`+`textEn` 双字段。
测试：端口 ≥8900 隔离；data-dir `mkdtempSync` 隔离；精确清理自己进程；**禁 `pkill -f`**。
Playwright：Chrome 路径走 `tests/lib/chrome.mjs`（`PI_WEB_CHROME` 可覆盖）；仓库根用 `fileURLToPath(new URL("..", import.meta.url))`（Windows 下 `URL.pathname` 会 ENOENT）；win32 清理走 `tests/lib/port-utils.mjs` 的 `freePort`。

## 6. 发布流程

> 详见 `docs/release.md`

```bash
npm run typecheck && npm run build
npm run changelog:i18n   # 文案有增减必跑
git add -A && git commit -m "feat(xxx): 描述"   # 不带 Co-authored-by
git push origin main
git tag vX.Y.Z && git push origin vX.Y.Z   # tag 带 v，与 npm 版本一致；Action 自动建 Release+桌面包
npm publish
```

版本须高于 registry；升级后 `pi-web-ui server restart`；发布前检查示例文件不泄密；Release 说明预览：`node scripts/release-notes.mjs X.Y.Z --base v<上版>`。

## 7. 环境变量

> 完整列表见 `docs/env-vars.md`

| 变量                        | 默认            | 作用                                                                                                                                                                                                                                                                         |
| --------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PI_WEB_PORT`               | `8787`          | HTTP 端口                                                                                                                                                                                                                                                                    |
| `PI_WEB_HOST`               | `127.0.0.1`     | 监听地址（默认 loopback）                                                                                                                                                                                                                                                    |
| `PI_WEB_CWD`                | `process.cwd()` | 智能体工作区                                                                                                                                                                                                                                                                 |
| `PI_WEB_DATA_DIR`           | `~/.pi-web`     | 数据目录                                                                                                                                                                                                                                                                     |
| `PI_WEB_SDK`                | `global`        | #321 起默认跟随：机器上有**更新**的 pi 副本（祖先链，如全局 pi CLI）就用它（多份取最高），没有或更旧回落自带副本；`bundled`/off/0/false/no = 强制自带（可复现，报 bug 用）。升级全局 pi 后**重启服务**即生效；更新面板会亮「运行中 vs 机器上」差距并提供「安装全局引擎」入口 |
| `PI_WEB_TOKEN`              | 空              | 共享口令鉴权                                                                                                                                                                                                                                                                 |
| `PI_WEB_PLUGIN_CATALOG_URL` | 官方清单        | 市场目录来源；空/`off`/`0`/`false`/`no` 关闭；`PI_WEB_PLUGIN_CATALOG_INSTALL=1` 才开机自动安装                                                                                                                                                                               |
| `PI_WEB_TOOL_TIMEOUT_MS`    | 20 分钟         | 看门狗超时（`ask_user_question` 豁免）                                                                                                                                                                                                                                       |

## 8. 部署

> 详见 `docs/deployment.md`

- 前台：`pi-web-ui --port 9000 --cwd /path`
- 自启：`pi-web-ui server install`（macOS→launchd / Linux→systemd / Windows→登录 Run 键，免管理员）
- Docker：`docker compose up -d`

## 9. 常见坑（现行有效禁令清单）

- **`details` ≤64KB 超限整丢**：`serialize.ts` 下发+随会话持久；新工具别塞大块内容（自封顶，见 `present-files-tool.ts` 摘录预算）；前端渲染不许依赖 details（参数解析+可选合并，见 `present-items.ts`）。
- **新增可开关工具动三处**：`tool-manager.ts` 的 `AGENT_TOOL_CATALOG`（唯一事实源；工具名字符串只在此定义，模块 import+re-export）＋ `agent-service.ts` 的 customTools 注册（DSH 无注册面除外）＋ `tests/unit/tool-registration.test.ts` 的 FACTORY_TOOLS。**对 SDK 内置工具的覆盖（read/write/edit）不进创建时的 customTools** —— 那会静默顶掉第三方扩展注册的同名工具，要走 `server/tool-overrides.ts`（基底 = 扩展实现优先，见 `docs/architecture-core.md`「覆盖与第三方扩展同名工具共存」）。两道 CI 守卫：注册→目录、目录→设置行（`OTHER_AGENT_TOOLS` 循环渲染，不手写行；`todo_list` 固定 markers 分区；核心内置工具 bash/read/edit/write 不入目录，走设置页「核心工具」区单独开关（`CORE_BUILTIN_TOOL_NAMES`，禁用名单同样接受））。
- **服务端 URL 必须 `appUrl()` 包一层**（`/ws`、`/api/*`、`/plugins/*`、`/themes/*`），否则 nginx 子路径部署 404；排查先看请求带没带应用根前缀。
- **elsewhere/过户**：`take_over_conversation` 搬 runtime 本体（单 writer）；`ask_user_question`/`browser_page` 在调用瞬间按 runtime 身份解析持有方（`bridgeTarget`+`findConversationHome`，锚点是 `created.session` 对象不是 conv id）；跨页作答 `question_answer` 带 `owner`；「另一处」行左键两段确认；elsewhere 只列 live 会话。回归：`takeover-test`/`idle-takeover-test`/`remote-answer-test`/`elsewhere-lifecycle-test`。
- **远期 cron 别交给 `host.schedule`**：Node 超 2^31-1ms 的 setTimeout 截断成 1ms → 死循环；远期用 `armDelay` 分片（≤6h）+ `nextCronFire` 回 null（「不再触发」）；notes 式需求挂一条 `* * * * *` 巡检、自带 `nextDue` 落库。回归：`plugin-cron-overflow-test.mjs`。
- **工作区禁家目录根**：`server install` 默认 `~/pi-web-ui`；`$HOME` 的同步扫描会被坏挂载挂起致事件循环假死。`ready` 只代表传输通，不代表会话就绪——测试判活按插件真实内容，不拿 `ready` 当信号；`attach` 链用户路径禁同步 `statSync`。
- **文本解码/行号**：预览·附件统一 `decodeText`（UTF-8→GBK→latin1）；`countLines` 不算尾随换行，前端 `split("\n")` 后 pop 末尾空串。
- **Windows 最小化通知**：`hasFocus`/`visibilityState` 在最小化时照常撒谎，只看窗口矩形（`isCollapsedWindow`，`notify.ts`）；Windows 吞通知还要求近 2 分钟有页面交互；**通知禁带 `tag`**（同 tag 静默替换，`renotify` 救不回）。排查：`NotifyToggle.tsx` 的 `SHOW_NOTIFY_TEST_PANEL` 改 true。
- **模型目录=官方整表替换**：`patch-remote-catalog.ts` 启动幂等改写 SDK（无旧残留、无"新增 N 个"）；`npm install`/SDK 升级后重启自动重打，结构变化自动跳过。验证：`tests/scratch/verify-patch.mjs`。
- **token cookie**：`pi_web_token` 存 `encodeURIComponent` 后的值，**读时先 `decodeCookieToken` 再比**（`server/auth-cookie.ts`）；改口令后一次正确的 `?token=` 即永久恢复（有效请求刷新 cookie，401+失效 cookie 自动 Expire）。回归：`token-auth-test.mjs`。
- **CSS 变量先定义后引用**：未定义的 `var(--x)` 整条声明失效；守卫 `tests/unit/css-tokens.test.ts`（CI 必跑），新增豁免写理由。
- **长英文/长路径不许溢容器**（手机端高频）：`.messages` 是 `overflow-y:auto`，未声明的 `overflow-x` 计算值即 `auto` —— 任何子元素横向溢出都会变成整条消息区的横向滚动条。三条同源病根：①`white-space: pre-wrap` 只在空白处折行，挡不住无空格长 token（长单词/绝对路径/URL/model id）；②行级 flex 子项 `min-width:auto` 不肯缩，一个长单词顶宽整张卡片；③宽表格/长公式要自己收着。统一口径：**行容器**（装长文本的 flex 行/列）先钉 `min-width:0` + `max-width:100%`；**能断行的**用 `min-width:0` + `overflow-wrap:anywhere` + `word-break:break-word`（**基线在 `.md`**，p/li/td/a/内联 code 靠继承全覆盖）；**单行元素**用 `overflow:hidden` + `text-overflow:ellipsis`（overflow 非 visible 时 `min-width:auto` 自动解析为 0）；代码块/终端输出走 `pre` + `overflow-x:auto`。高度封顶的列表（如 `.plan-board-steps`）必须 `overflow-y:auto` + `overscroll-behavior:contain`，否则条目一多整页连输入框/顶栏/状态栏一起滚。守卫 `tests/unit/text-wrap.test.ts`（静态扫 styles.css，CI 必跑；清单分「可断行容器 / 单行文本 / 行容器」三档），新增「渲染模型文本的容器或行」要进它的清单 —— 目标条、工具调用卡头、折叠徽标都已登记（窄屏实报过：目标条一行长文案把消息区顶出横向滚动条）。
- **消息级按钮只有两个落点：卡头=纯复制键，`.msg-actions` 行=正文/用户消息**（口径简化过，别再加第三处）：① **所有卡头只有自己的复制键** —— 思考 / 工具 / 附件 / 技能 / 压缩摘要共用的 `.chead` 行右端就是 `.chead-copy`（复制思考 / 复制参数），**不挂任何消息级按钮**（历史包袱 `headExtra` prop + `.chead-actions` 容器 + 对应 CSS 已全删，见 `ThinkingBlock` / `ToolCallBlock`；此前「谁恰好是第一个 head 谁挂整排按钮」导致同一条回复里两张 bash 卡右端按钮数不一样，窄屏实报）；② **`.msg-actions` 行只给「有正文的助手消息」和「用户消息」**（`Message.tsx` 的 `showMessageActions = !editing && (role === "user" || canCopyWhole)`）—— 纯工具调用 / 纯思考 / 附件卡 / 插件消息没有正文，`canCopyWhole`（复制三件套要正文）与 `canSpeak` 都为假，那一行只剩「派生分支 / 回滚」这类对卡片无意义的按钮，复制已在卡头。**助手消息把这一行锚在最后一个正文块之后**（`anchorIndex` 扫 `asText` 找末个 text 块插在它后面，窄屏实报「先说话、再发工具」时按钮被工具卡隔开、看着像属于工具卡），无正文块才落消息末尾；**用户消息不锚**（`.msg-user .msg-body` 是气泡，插进去会坐进气泡里）。用户那一行是「直接重问 / 编辑重问 / 派生分支 / 回滚 / 复制三件套」。**右键菜单不受影响**：`contextmenu.toolcall`（卡头右键）+ `contextmenu.message`（消息右键）保持全功能，是这两条之外的功能入口。
- **卡头（`.chead`）右端的复制键要对齐**（思考 / 工具 / 附件 / 技能 / 压缩摘要共用一套）：触屏 `hover:none` 下 `.chead > .chead-copy` 在流内常驻并钉 `flex:none`（右缘对齐基准），卡头成了一行「左：图标+标题+参数提示，右：复制键」。写 `.toolcall-path` 等提示文本时**必须可缩**（`min-width:0` + ellipsis，不许 `flex-shrink:0`）：不让步 → 整行顶宽 → 复制键被顶出卡头右缘（`.toolcall overflow:hidden` 裁掉、点不到），且每行按提示长度各自右移，右端**列不齐**（窄屏实报）。配套口径：主体 `.chead-title.toolcall-name` 写 `flex:none`（选择器要 `.chead-title.toolcall-name` 提权重才压得住后面那条 `.chead-title`），提示文本一律 `flex: 0 1 auto` + `min-width:0`（缺口全由它们吸收），复制键钉 `flex:none`；≤640px 额外把**路径少显示点**（`max-width: min(20ch, 32%)`，命令预览 `min(28ch, 44%)`）并把 `.chead` 的 `gap` 收到 4px。守卫：同一个 `text-wrap.test.ts` 的「卡片头右端按钮列」分组（`.chead` 行容器 / 四个 hint 可缩 / `.chead > .chead-copy` 的 `flex:none` / 工具名 `flex:none` / 思考标题可缩）。⚠️ 别用 `flex-wrap:wrap` 兜底：复制键是最后一个子项，换行会把它单独甩到第二行左端。
- **UI 扩展点三规则**：① slot 两端同口径（manifest `ui` 与 `host.ui.register()` 同一别名+枚举；前端不认识静默丢弃）；② 新可隐藏/可排序入口必须在渲染组件接 slot（`hostNodes`+顺序驱动，无数据时退化全画；顶栏单一扁平流，禁按种类分组容器；溢出与隐藏走同一「⋯」菜单、实测宽度决定，`topbar-fit.ts`）；③ 往下展开的菜单 portal 到 body + fixed（z-index 救不了 overflow 裁剪），关闭走 ref；浮层统一 `useFloatingPanel`，**禁在 scroll 里关**（只重算锚点；mousedown/Escape 才关）。回归：`ui-layout-ui-test`（真 Chrome）。
- **新对话三连**：经 `focusComposer()` 自动聚焦输入框（触屏豁免）；非 chat 视图先切回 chat；待发附件 chips 随会话清空（`advanceComposerSession`，判据 `UiState.sessionId`；空 sessionId 瞬时态不清）。单测：`composer-draft.test.ts`。
- **目标模式 2.0 三规则**（唯一路径：主对话=审查者、服务端另起**落盘**常驻执行对话）：① 一轮 = 服务端派活 + 服务端判定，**轮次/熔断/代次全在服务端**，契约明令禁止模型自己 spawn/wait（否则预算与停滞检测同时失真）；② 取样口径 = 工作区 `git diff` 指纹 + **执行者会话**错误特征（主对话是审查者，它的工具报错不代表执行受挫）；③ 审查指令投递前先 `waitMainIdle`、**先登记 verdict waiter 再投递**（`conv.awaitingVerdict` + 代次作废丢弃迟到结论）；**「中止/关闭就是停止」** —— `waitRoleAgent` 返回 `canceled`/`gone` 即终止循环（不续轮），`error`/`timeout` 才在预算内续轮。执行者落盘 → 占项目 8 个普通对话名额之一（满员即中止并提示，无降级路径）。回归：`tests/unit/goal-delegated.test.ts`、`goal-delegated-test`（零 token，已在 ALL 列表）。
- **DSH 三规则**：shipped 预设用 `preset-clones.ts` 的 file: 克隆（`file:` 指文件不是目录；roster 以 clone 为唯一 system 根，关 `includeShippedRoot`）；问卷 answerer 与技能过滤钩子挂每次会话 `setup()` 的 agent scope；底栏统计吃直播帧（wrapper `{global:true}` 转 `assistant.stream`）+ `assistant/message.usage`，纯映射 `dsh-usage.ts`。回归：`preset-clones.test.ts`、`dsh-smoke-test.mjs` §2.5、`dsh-stats-test.mjs`。
- **`i18n.tsx` value 只许字符串字面量**（`+` 续行可）：`scripts/i18n-diff.mjs` 手写解析器只认这个；改完文案跑 `npm run changelog:i18n` 验证。
- **PR 提交前必须三连验（防 CI 失败铁律）**：① `npx prettier --write <改动文件>`（CI 第一道硬门禁，漏掉直接首步挂掉）；② `npm run typecheck`（Vitest 运行时只管执行、默认不阻断类型错误，必须显式全量编译确保 5 个 tsconfig 零错误；编写单测时 mock context 必须传 `{} as any`，严禁传裸 `{}` 触发 TS2740 阻断）；③ `npx vitest run <单测>`。严禁未跑格式化与全量类型校验直接提交 PR。

---

_结构/流程变更时同步更新本文件及相关 `docs/`。修改后运行 `/reload` 生效。_
