# 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 服务）· `web/`（前端 React 界面）· `bin/pi-web-ui.mjs`（CLI 入口）· `desktop/`（Electron 壳）· `deploy/`（守护进程配置示例）· `themes/`（主题样式表）· `plugins/`（官方插件体系，`catalog.json` 为市场清单）· `extensions/`（pi 扩展 `/webui`）· `docs/`（详尽架构与设计文档）· `tests/`（测试套件）。
- **server 核心事实源与入口（★=关键事实源）**：
  `protocol.ts`★（跨端 wire 协议唯一事实源）· `index.ts`（Express + WS 服务路由与心跳）· `agent-service.ts`（ClientSession / 运行态 / customTools 注册）· `tool-manager.ts`★（`AGENT_TOOL_CATALOG` 工具开关唯一事实源）· `tool-prompts.ts`★（bash 等工具提示词单源）· `tool-overrides.ts`（SDK 内置工具安全覆盖基底）· `client-state.ts`（持久化配置）· `tool-approval.ts`（审批门禁与规则库）· `plugins.ts`（UI 插槽与插件运行时）· `subagents.ts`（子代理管理）· `terminals.ts`（PTY 终端与 bash 接管）· `soft-cap.ts`（上下文软上限纯函数）· `goal-service.ts`（目标模式 2.0 审查会话编排）· `plan-mode.ts`（计划模式硬闸门）· `delegate-mode.ts`（审查者模式自动委派）· `preset-share.ts`（预设导入导出四道闸门净化）。
- **web/src 核心模块（★=优先阅读）**：
  `use-chat.ts`★（WS 通信 + Reducer + 终端 Bridge）· `app-globals.ts`★（模块级全局 Store + `appSend`，高频快照流禁入）· `ui-slots.ts`★（宿主 34 个 UI 扩展插槽与四层合并）· `types.ts`（协议类型 shim，受脚本守护）· `styles.css`（全应用共享 CSS 变量与主题基线）· `i18n.tsx`（前端双语国际化）· `composer-bridge.ts`（输入框草稿管理与自动聚焦）· `topbar-fit.ts`（顶栏溢出测量）· `use-floating-panel.ts`（浮层统管 Hook）。
- **测试与 CI 套件**：
  `tests/run-smoke.mjs`（零 token 冒烟聚合列表）· `tests/unit/`（vitest 纯函数单测）· `tests/*-test.mjs`（Playwright / WS 集成测试，无 Chrome 自动 SKIP）；CI 矩阵见 `.github/workflows/ci.yml`。

## 4. 核心架构与设计索引

详细文档位于 `docs/<主题>.md`，以下为各模块核心设计原则与事实源：

`docs/` 索引（找详细文档先看这里，均为 `docs/<名>.md`）：**architecture-core** 快照驱动/协议单源/安全边界/主题/多对话并发 · **architecture-attachments** 附件/图片/视觉桥/上传预览下载 · **architecture-terminal** 终端 PTY/SCM/活力检测/bash 接管 · **architecture-plugins** 插件形态/协议/宿主扩展点/MCP 桥/多根工作区 · **architecture-system-prompt** 系统提示词组装链路与 override 钩子 · **tool-context-budget** 工具上下文预算（文案精简 / 逐工具覆盖 / **延迟加载** + 前缀缓存硬约束）· **dsh-engine** DSH 预设/问卷/底栏统计/工具桥 · **goal-conversation-design** 目标模式 2.0（把目标审查变成对话：执行对话干活 + 当前对话当审查者；**已实施且只剩这一条路径** —— v1 的隐藏隔离审查会话 + 自治标记路径已删除，轮次/熔断/代次全在服务端，见该文档 §4/§11）· **development** 开发工作流/CI/编码约定/测试规范 · **release** 发布流程 · **deployment** 部署 · **env-vars** 环境变量全表 · **preset-sharing** 预设导入/导出/社区共享仓库（交换格式 / 白名单净化 / 一键分享 / 浏览列表）· **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`    | `terminal(action)` 单工具；`ownerKind` 明确 ClientSession/Conversation 归属；UI 挂载仅 attach，不重跑；后台任务持续持有取消/超时，pending 按 nonce 归属；`terminalBash` 分流（`persist` 控制 ai-bash 复用）；SCM 只读走 execFile，写操作走可见终端 |
| 工具开关/read 目录    | `docs/architecture-core.md`        | `AGENT_TOOL_CATALOG` 唯一事实源；`read` 覆盖目录走 ls 口径（`readDirEnabled` 是行为开关，不入目录；仅 pi 引擎）；`bash`/`read`/`write`/`edit` 四处覆盖与第三方扩展同名工具的共存（基底 = 扩展实现优先）见 `server/tool-overrides.ts`               |
| 审批                  | `server/tool-approval.ts`          | 规则库 `<dataDir>/approval-rules.json`（ask/deny/allow）；三档放行：全局关→本对话全部允许→允许同类；记忆只在内存，随过户搬                                                                                                                         |
| 问卷队列与父代理请示  | `docs/architecture-core.md`        | `question_queue` 按项目同步待答请求；`questionPresentation` 控制弹出/队列；子代理 `consult` 交父代理，`ask_user_question` 只由用户答；服务端截止/取消一次结算，过户保留请求 ID 与期限。                                                            |
| 问卷/草稿进快照       | `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`               | 双层管控：回合内逐会话重算 SDK reserve（=`cw-cap`，下限 16384，工具间隙即压，救自治长跑/子代理）+ 空闲态 pi-web-ui 主动 compact；cap 关闭/压不下时回落物理兜底；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(action="read"/"files"/"status")` 按需取                                                                                                                                |
| 历史消息按需加载      | `server/message-window.ts`         | 每连接 send 漏斗外包一层：首份快照只带尾部窗口（条数 + 字符预算），更早的按 `load_earlier_messages` 翻页；不碰 rev/baseRev，四引擎通吃                                                                                                             |
| 计划模式              | `server/plan-mode.ts`              | 目标条展开行「只规划」触发；动态剔除写工具并施加硬闸门拦截；禁倾倒大段代码；必须通过计划看板「开始实施」退出；回归 `plan-mode-test`。                                                                                                              |
| 审查者模式            | `server/delegate-mode.ts`          | 开启后主会话只审不干，Prompt 自动投递常驻执行对话；硬闸门拦截一切写类与派发工具；计划模式优先级高于委派；回归 `delegate-mode-test`。                                                                                                               |

### 对话通信

`communicate` 统一同项目的 send/request/reply/escalate/read，父/root 别名由服务端解析；发送者绑定实际执行会话，跨 await/过户按稳定对象查持有方。通信内容不扩张权限，项目/沙盒/Bot 边界必须在执行前校验。`conversation complete` 登记任务完成，manual 子代理仍有下级/问卷/通信时不得提前通知或结算工作树。旧交接与请示入口复用统一服务。详见 `docs/architecture-core.md`。

### MCP 项目运行态

`server/mcp-project-runtime.ts` 按项目 cwd 隔离 Bridge 与配置监视器；管理操作捕获项目归属，对话按自身 cwd 获取工具，禁止通过修改共享 Bridge 的 cwd 切换项目。全局配置修改须刷新各项目实例；列表需区分被覆盖配置与旧实例回退。目录 Skill 同步必须携带脚本和资源。详见 `docs/architecture-plugins.md`。

### 设备中心与跨设备协作

独立 Hub 与本机连接器位于 `server/device-center/`，部署与边界见 `docs/device-center.md`。主界面「设备中心」为原生 `devices` 视图（`DeviceCenter.tsx`），旧 `/devices` 重定向到该视图；未绑定时隐藏侧栏群组并展示连接/创建引导，离线或请求失败不得清空已知群组；本机 Hub 安装走 `hub-setup.ts` 的 loopback 同源接口与独立 Hub CLI，必须验证参数、隔离数据目录、互斥执行并在关闭时取消等待；配对接口仅允许回环来源与同源写入；群成员统一为 Bot，设备仅负责承载与连接。群任务仅派给显式 @ 的稳定 Bot ID，机器人接力继承父任务预算，禁止无界互派。`bots.ts` 按本机设备身份保存 Bot 的固定项目根、角色、模型及工具/Skill/MCP 白名单；群使用 `botIds`，消息使用 `targetBotIds`，任务与 `device_collab` 文件同步共用 Bot registry lease/revision；执行会话不得冒用其他 Bot。忙碌或停止未确认时禁止换根，旧文件包不得落入新根。文件同步先暂存预览后按 revision 导入，已有差异必须显式覆盖；凭据不得进入工具回执。Hub 默认单端口 `/device/<id>/` 路径访问设备，以 CSP sandbox（禁止 allow-same-origin）与设备专属随机能力路径隔离远程页面；旧独立域/端口保留兼容。跨设备代理只放行账号范围群聊接口，不得暴露本机绑定、目录与设置接口。

设备中心远程桌面由 `desktop.ts` 经既有 WebSocket 传输原生 JPEG，`desktop-native.ts` 管 Windows/macOS 助手，`desktop-linux.ts` 管 Linux X11（ffmpeg/xdotool）；不再依赖 VNC/noVNC。只读由服务端按连接强制，浏览器绘制 ACK 限制跨设备待确认帧，断开须释放输入。macOS 14+ 需屏幕录制/辅助功能权限，Wayland 尚不支持；启用状态保存在 `desktop-settings.json`。

### 画布协同

画布编辑版本、运行代次和产物边界见 `docs/architecture-canvas.md`。拓扑保存必须带 revision 并等待 ACK；运行态不得覆盖拓扑或复活删除的工程。分支按本轮激活边汇聚，返工必须有明确决策和次数边界。隔离产物通过 `server/canvas/canvas-workspace.ts` 进入集成 worktree 验证，不得静默回落或自动写回主树。

## 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`（在 `web/src/i18n.tsx` 中两处都加，改完跑 `npm run i18n:sync` 一键自动补齐与对齐 12 个语言包，`tests/unit/locales.test.ts` 锁对齐；语言包 `locales/*.json` 缺 key 回落英文）；守卫 `tests/unit/i18n-dead-keys.test.ts` 拦「加了没人引用的 key」（模板拼接的 `thinking.*` / `promptTok_*` 在白名单里）；
服务端 `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`**；冒烟用例**不得自己 `npm run build`**（并行下互相踩 `dist/` 出假红），需要 dist 一律调 `tests/lib/ensure-build.mjs`（跑器开跑前已串行构建一次，CI 用 `--no-build`）。
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_HISTORY_WINDOW`               | `300`                           | 历史消息**服务端按需加载**的尾部条数上限（另有 150 万字符字节预算兜底）：打开对话只下发最近这么多条，更早的由 `load_earlier_messages` 翻页拉取；`0`/`off`/`false`/`no` = 关闭（全量下发）。挂在每连接 send 漏斗上，四引擎统一生效（`server/message-window.ts`）              |
| `PI_WEB_TOOL_TIMEOUT_MS`              | 20 分钟                         | 看门狗超时（`ask_user_question` 豁免）                                                                                                                                                                                                                                       |
| `PI_WEB_SUBAGENT_AUTO_WAKE_MAX_TURNS` | `5`                             | 子代理无人类干预连续自动唤醒上限轮数的部署级默认（设置页开关优先，默认 5，0 = 不限制；挂起期间通知不丢，见 issue #726）                                                                                                                                                      |
| `PI_WEB_TOOL_LAZY_LOADING`            | `0`（关）                       | 工具延迟加载的**部署级默认**（设置页开关优先）：`1`/`true`/`on`/`yes` = 只有核心工具（bash/read/edit/write）+ `load_tools` 常驻，其余工具按需加载（见 `docs/tool-context-budget.md`）；空/其它值 = 全部工具直接活跃（默认，与 pi 命令行一致）                                |
| `PI_WEB_PRESET_REPO`                  | `xing-shuyin/pi-web-ui-presets` | 设置预设的社区共享仓库（`owner/name`）；`off`/`0`/`false`/`no` = 关闭一键分享（导入/导出仍可用），见 `docs/preset-sharing.md`                                                                                                                                                |
| `PI_WEB_PRESET_CATALOG_URL`           | 仓库 `index.json` 的 raw 地址   | 「浏览分享」的列表来源；空串或 `off` = 关闭浏览；5 分钟缓存 + 刷新绕过，失败保留上次列表                                                                                                                                                                                     |
| `PI_WEB_PRESET_GH`                    | `gh`                            | 一键分享调用的 GitHub CLI 路径（不可用时先试令牌 API，再回落「复制 JSON + 打开预填建 Issue 页」）                                                                                                                                                                            |
| `PI_WEB_PRESET_TOKEN`                 | 空                              | 无 gh 时直连 GitHub API 的令牌（`PI_WEB_PRESET_TOKEN` > `GH_TOKEN` > `GITHUB_TOKEN`）：有令牌就不需要 gh；不设也能用（预填页点一下 Submit）                                                                                                                                  |

## 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. 现行有效开发禁令与避坑清单

以下规则由历史线上问题与核心架构硬约束提炼而成，编写代码时必须严格遵守：

### 1. 通信协议与数据安全

- **`details` 字段硬限制 ≤64KB**：下发与持久化会超限整包丢弃。新工具严禁塞入大块原始数据（参照 `present-files-tool.ts` 做摘录预算）；前端渲染必须支持无 details 降级（`present-items.ts`）。
- **服务端 URL 必须包裹 `appUrl()`**：包括 `/ws`、`/api/*`、`/plugins/*`、`/themes/*`，杜绝反向代理子路径部署时 404。
- **Token Cookie 校验前必须解码**：`pi_web_token` 经 `encodeURIComponent` 存储，服务端读取必须先 `decodeCookieToken` 再校验（`server/auth-cookie.ts`）。
- **工作区严禁使用家目录根**：禁止设置 `$HOME` 为工作区根，防止递归扫描坏挂载造成事件循环卡死。
- **远期定时器必须使用 `armDelay` 分片**：Node.js 中 `setTimeout` 超过 2^31-1 毫秒（约 24.8 天）会整型溢出被截断为 1ms 从而引发死循环。超过 6 小时任务必须分片（守卫：`plugin-cron-overflow-test.mjs`）。
- **文本解码与行号统计规范**：预览与附件统一使用 `decodeText`（UTF-8 → GBK → latin1）；`countLines` 不计末尾空行，前端需 pop 末尾空串。

### 2. 工具定义与提示词规范

- **工具提示词三处职责分离（严禁跨处复述）**：
  1. Tool Schema 的 `description`：仅描述“做什么与边界”，≤600 字符；
  2. 系统提示词中的 `promptSnippet`：仅写“何时触发”，≤80 字符，不带工具名前缀；
  3. `Guidelines` 段落的 `promptGuidelines`：仅写“调用顺序/禁令/跨工具路由”。
     _必须使用精炼纯英文，违规会被守卫单测拦截_（守卫：`tests/unit/tool-prompt-hygiene.test.ts`）。`edit` 工具提示词亦归本项目单源覆盖（`agent-service.ts`）。
- **工具延迟加载硬约束：绝不破坏前缀缓存**：
  供应商 Prompt Cache 依赖系统提示词前缀完全一致。系统提示词 `{{tools}}` 目录必须保持完整且不随已加载状态变化；新加载工具的要点随 `load_tools` 回执进入对话；注册表仅允许追加（`toolsAdded`），严禁就地修改或删除（守卫：`tests/lazy-tools-test.mjs`，文档：`docs/tool-context-budget.md`）。
- **单工具多执行路径必须提示词单源**：以 bash 为例，原生、终端和自适应分流路径共用 `server/tool-prompts.ts`，禁止在 execute 覆盖实现中硬编码或复制描述。
- **新增工具三处联动**：新增可开关工具必须同时登记到 `tool-manager.ts`（`AGENT_TOOL_CATALOG` 事实源）、`agent-service.ts`（`customTools` 列表）以及 `tests/unit/tool-registration.test.ts`；内置工具覆盖必须走 `tool-overrides.ts`。
- **逐工具文案可编辑机制**：用户自定义文案仅作为偏好存入 `toolPromptOverrides`，新增可配置维度必须同步打补丁逻辑、wire 协议与设置页编辑器三处（守卫：`tests/unit/tool-prompt-overrides.test.ts`）。

### 3. UI 布局与样式规范

- **CSS 变量必须先定义后引用**：未定义的 `var(--x)` 会导致整条 CSS 声明失效；内联代码前景色与背景色对比度必须 ≥2:1（守卫：`tests/unit/css-tokens.test.ts`、`theme-inline-code-contrast.test.ts`）。
- **强调色实底上的文字必须走 `--accent-contrast`**：`background: var(--accent)` 配 `color: #fff` 会在浅色强调主题（`flow`/`wallpaper-flow` 的纯白、`wallpaper-obsidian` 的 `#e2e8f0`、`aetheris`/`cyberpunk`/`nord` 等）变成白底白字，按钮直接隐形。前景一律写 `var(--accent-contrast, #fff)`；插件同理：若私设前缀别名（如 `--xxx-accent: var(--accent)`）**必须成对声明** `--xxx-accent-contrast: var(--accent-contrast, #fff)`，推荐直接复用宿主全局 `.btn.primary`，严禁私设单向底色别名而硬编码白字；半透明 `color-mix(… var(--accent) 30% …)` 染色底则用 `var(--text)`；主题若把 `--accent` 定成浅色，必须同时声明反色的 `--accent-contrast`（守卫：`tests/unit/accent-contrast.test.ts`、`semantic-contrast.test.ts` 动态符号追踪别名）。
- **语义色实底同理走 `--on-*`**：`background: var(--green|--red|--amber|--blue|--send-blue|--stop-red)` 配 `color: #fff` 同样会隐形（默认主题 `--amber` 白字 1.67:1、`--green` 1.92:1、`--red` 2.77:1）。前景写 `var(--on-green|--on-red|--on-amber|--on-blue|--on-send-blue|--on-stop-red)`；状态色按「白/深取对比更高者」派生、动作色（blue/send/stop）白字 ≥3:1 就保留白字，主题侧由 `make-light-theme.mjs` 自动派生（守卫：`tests/unit/semantic-contrast.test.ts`）。
- **主题的「文字令牌」必须有明暗下限**：`--text-faint`（3.05:1）、`--accent`（3.5:1）、`--amber/--green/--red/--blue/--link/--link-soft/--info-blue/--skill-blue/--auth-green/--plugin-purple/--amber-text/--red-text/--err-text/--md-strong/--code-text`（4.5:1）在 `--bg` 与 `--bg-elev2` 上必须够对比——亮色主题照抄深色默认值会让警告文字、状态 chip、链接整体糊掉（曾出现 `--plugin-purple` 1.50:1）。`make-light-theme.mjs` 的 `TEXT_FLOORS` 会自动保持色相压暗，手写主题自行声明（守卫：`tests/unit/theme-text-contrast.test.ts`）。
- **长文本与长路径防溢出标准**：
  - 行容器（flex 容器）必须添加 `min-width: 0` 与 `max-width: 100%`；
  - 可断行文本（`.md` 内正文、路径）必须使用 `overflow-wrap: anywhere` 与 `word-break: break-word`；
  - 单行文本使用 `overflow: hidden` 与 `text-overflow: ellipsis`；
  - 独立面板与弹窗列表（如计划看板、沙箱差异）必须声明 `overscroll-behavior: contain` 防止整页滚动条穿透；但流内工具输出与代码文本块等非独立浮层容器严禁声明 `overscroll-behavior: contain`，防止滚轮黑洞锁死外部对话流（守卫：`tests/unit/text-wrap.test.ts`）。
- **卡头与操作按钮落点规范**：
  - 折叠卡头（`.chead`）右端**仅放置自身复制键**（`.chead-copy`，钉 `flex: none`），卡头内严禁悬挂消息级操作按钮；卡头提示文本设置 `flex: 0 1 auto` + `min-width: 0` 吸收缩放，窄屏下限制最大宽度，严禁用 `flex-wrap: wrap`。
  - 消息级操作栏（`.msg-actions`）仅对有正文的助手消息和用户消息生效；助手消息操作栏必须紧锚在最后一个正文块后，杜绝与工具卡分离。
- **UI 扩展点与浮层规则**：
  - 下拉菜单必须 Portal 到 `body` + `fixed` 布局，防止被父容器 `overflow` 截断；
  - 浮层统一使用 `useFloatingPanel`，禁止在 `scroll` 事件中强制关闭浮层（仅重算锚点，通过 mousedown/Escape 关闭）；
  - 侧边停靠栏（`SideDock`）为流内 flex 项目，浮动模式由 `sideDockFloat` 控制，严禁写死 fixed 遮挡侧边按钮。
- **输入框上方那条带子属于消息区**：快捷短语行只是浮在它上面，正文从芯片**之间**的缝里透出来（芯片本身是实底小卡片，压在正文上必须压得住字）。带子高度单源在 `.main` 的 `--composer-strip`（= `--composer-pad-top` + ChatInput 实测写入的 `--quick-row-h` + 6px）；改输入区上内边距或短语行留白必须同时顾三处：`.messages-wrap` 的负 margin、`.messages` 的底部留白、以及按容器底缘定位的 `.scroll-bottom` / `.qn-rail`（都要 `+ var(--composer-strip)` 回补），严禁把短语行改回 `position: absolute`（会与药丸/浮标抢同一条带子，折行时必叠）。守卫：`messages-behind-composer.test.ts`。
- **拖拽与触控交互标准**：
  - 图标编辑与拖拽必须监听 Pointer 事件，严禁使用 HTML5 DnD（移动端触屏不触发）；内置图标须在 `host-icon.tsx` 登记；菜单行文字统一 12.5px（守卫：`host-icon.test.ts`、`topbar-overflow-menu.test.ts`）。
  - 手机端横滑手势（`swipe-drawer.ts`）必须避开系统侧滑区，手势期间直写内联 transform 禁用动画过渡，松手后清理内联值交由 CSS 类过渡；移动端遮罩常驻（守卫：`swipe-drawer.test.ts`）。
- **消息操作语义**：提示悬浮即显且简短；重新提问和编辑后重问在当前会话截断原问题及后续内容后重发；分叉包含所选消息并创建新对话；回退打开对应用户问题的编辑框，提交后在当前会话重问，取消不改历史。详见 `docs/architecture-core.md`。
- **新会话初始化交互**：切换或新建会话须经 `focusComposer()` 聚焦（触屏除外），非 chat 视图自动切回，待发附件 chips 随新会话清空（守卫：`composer-draft.test.ts`）。

### 4. 架构与运行态约束

- **目标模式 2.0 运行铁律**：主对话作为审查者，服务端另起落盘执行者；轮次、代次与停滞熔断全由服务端管控，严禁模型自行调用 wait/spawn；审查指令必须注入执行证据（`goal-evidence.ts`）；会话中止或关闭即停止循环，不得无限续轮（守卫：`goal-delegated.test.ts`、`goal-evidence.test.ts`，文档：`docs/goal-conversation-design.md`）。
- **审查者委派模式（Delegate Mode）**：开启后主对话只审不干，用户输入自动派发给常驻执行会话；闸门强制拦截写操作与子代理派发；计划模式优先级高于委派。
- **预设依赖与部分应用**：交换文档顶层 `plugins` 仅携带插件需求元数据，不含配置、密钥、存储或授权；安装复用显式 `plugin_job`。部分预设未携带的字段不得补默认或清空当前设置，`plugins` 不得进入运行设置；保存、应用、延后加载和应用失败分别回执（见 `docs/preset-sharing.md`）。
- **预设分享必须白名单严格净化**：导入外部 JSON 必须通过 `server/preset-share.ts` 白名单校验（`preset-fields.ts`），丢弃未知字段，执行 dryRun 确认，严禁未经校验直接摊入 `ClientSettings`（守卫：`preset-share.test.ts`，文档：`docs/preset-sharing.md`）。
- **Windows 最小化通知**：Windows 最小化时页面 visibility 状态不准确，必须通过窗口尺寸矩形判断（`isCollapsedWindow`）；通知禁止携带 `tag` 字段，防止被系统静默吞并。
- **压缩软上限必须双层监控，换模型必须走 `applySessionModel`**：SDK 的 `compaction.reserveTokens` 是**回合内**唯一的检查点（工具调用间隙 `_compactBeforeNextAssistantResponse`），必须逐会话算成 `cw - cap`，否则自治长跑（子代理 / 单回合几十次工具调用）全程无空闲态，永不会压（`#773`）；且 reserve 依赖会话**当前**模型，任何裸 `session.setModel()` 都会让判定线卡在上一模型的 `W - C` 上，换模型一律走 `applySessionModel`；cap ≤ keepRecent + headroom 时不注射（否则每个工具间隙重压一次）。
- **模型目录官方整表替换**：通过 `patch-remote-catalog.ts` 幂等改写 SDK 目录，不残留旧定义，升级后自动重新应用。
- **DSH 运行态三规则**：shipped 预设使用 `preset-clones.ts` 的 file: 克隆隔离；问卷与过滤钩子挂 agent scope；统计指标吃直播帧与 assistant/message.usage（`dsh-usage.ts`）。
- **会话过户与状态迁移**：`take_over_conversation` 完整搬迁 runtime 本体，且**是事务性的**（源侧已摘、目标侧接入失败就原样搬回，绝不留「还在跑但没人持有」的幽灵会话，见 `#556` / `tests/takeover-rollback-test.mjs`）；**断连残骸的「另一处」行有宽限期**（`PI_WEB_OFFLINE_ROWS_TTL_MS`，默认 30 分钟，标 `ownerOffline`，仍可过户——手机 run 途中关页面后换设备仍能接管）；`ask_user_question` / `browser_page` 解析创建时的 session 对象（`bridgeTarget`）；跨页答复带 owner。
- **宿主提供的包只能声明在 `peerDependencies`（`"*"`）**：pi 扩展加载器只扫 `dependencies`（命中 `@earendil-works/pi-coding-agent` / `typebox` 就告警：嵌套副本会绕开加载器注入、搞出重复运行时模块）。本包同时是扩展与独立服务端，服务端子进程真要这两个包（`PI_WEB_SDK=bundled` 也要自带副本）→ 落在 `optionalDependencies`（npm 默认照装）。守卫 `tests/unit/extension-host-packages.test.ts`。
- **布局页改名 / 插件 `arrange.label` 必须落到渲染层**：合并引擎对「显式指定的文案」置 `UiSlotEntry.labelExplicit`；内置条目（顶栏按钮、底栏数值徽标）一直画写死的 i18n 与实时数值，只有旗立着时才让位（名字型顶掉内置文案；数值型把名字插在数值前，不吞实时数据）。只在设置页生效 = 假承诺（`#555`，守卫 `tests/unit/bar-item-unified.test.ts`）。
- **插件修改必须递增版本号（Bump Version）**：内置官方插件（`plugins/<id>/`）凡修改了代码/功能/静态资源（非纯文档），必须同步递增 `manifest.json` 中的 `version`（若有 `package.json` 的 `version` 须保持一致）。原因：随包热同步（`syncBuiltinPlugins`）与更新器（`plugin-updater`）均以 `compareVersions(srcVer, tgtVer) > 0` 作为升级判定的唯一权威事实源；若漏改版本号，老用户更新主程序后数据目录中的旧插件代码永远不会被热更新覆盖，导致缺陷滞留（守卫：`tests/unit/plugin-version-bump-guard.test.ts`）。
- **修改测试插件一定要及时更新本地插件**：本地实际运行加载的插件位于数据目录（`<dataDir>/plugins/<id>/`，如 `~/.pi-web/plugins/<id>/`）。修改或测试工作区内置插件时，除递增版本号外，必须及时将最新源码同步覆盖至本地数据目录的对应插件中，防止开发调试时因旧代码缓存导致行为脱节。
- **国际化字面量要求**：`i18n.tsx` 的词条值必须使用字符串字面量（仅允许 `+` 拼接），确保 `scripts/i18n-diff.mjs` 静态解析器正常运行。

### pi 请求上下文与证据

- `request-context.ts` 仅改本次 pi 模型请求视图，不改写转录；近期工具结果保留全文，按估算预算外置较早的大段只读输出。只读精确去重遇用户介入、修改或未知工具即失效。
- 证据使用 `context-evidence:<sha256>` / `context-evidence:index`，`evidence-tool-hooks.ts` 在 SDK 权限 hook 前解析，宿主 read 仍校验大小、完整性与权限；只有 SDK 适配器全部兼容才启用外置，取消必须传播。
- `compaction-instructions.ts` 统一默认压缩的任务保留指令并确定性附加证据索引；证据与会话同生命周期，分支复制、删除清理，迁移须携带 `.evidence` 目录。详见 `docs/tool-context-budget.md`。

### 异步归属、权限与持久化补充约束

- 跨 await 的会话操作必须捕获 Conversation/runtime 并贯穿实际 SDK 调用，禁止恢复后重新读取 active；工具审批/权限按稳定 SDK session 动态解析过户后的持有方。
- 工具执行及审批等待绑定取消信号，异步恢复与最终执行前检查；插件激活/reload 使用代次，过期实例只能清理，不能重新注册或覆盖新实例。
- 共享设置按字段 patch、草稿按会话条目更新权威状态；原子写失败不得降级截断目标文件。保存失败必须反馈。只支持单服务进程写同一 data-dir。
- 历史分页验证 historyGeneration 和锚点，失效触发全量同步；重复快照不得绕过字符预算。插件工具已应用集合按 SDK session 保存。
- 文件预览统一 CSP sandbox，allowJs 也不得 allow-same-origin；live-preview 仅以可撤销的预览 capability 支持模块/SSE，严禁转发宿主凭据。
- SSH 必须校验 known_hosts，未知/变更主机拒绝；`PI_WEB_SSH_KNOWN_HOSTS` 指定信任文件，操作说明见 `docs/env-vars.md`。

### 命令、文件与恢复状态边界

- WS 限额统一在 `server/payload-limits.ts`；attach 队列必须同时约束条数和累计字节，拒绝超限不得静默丢旧操作。
- 历史搜索不得复用列表展示截断或截断正文缓存判定命中；流式扫描限制并发，新查询取消旧查询，切项目不发布过期结果。
- 压缩恢复状态走 `compaction-state.ts` sidecar，运行中转录只有 SDK 可以写；恢复入口均须消费中断提示。终端历史用 `terminal_output.reset` 替换显示，回放前 flush 实时输出。
- 本地文件保存走 `file-save.ts` 保留元数据后替换；上传完整后排他提交，正文读取前准入并预留累计预算。取消后实际写流释放才可归还额度。详见 `docs/architecture-attachments.md`。
- 配置与任务先持久化再发布运行态；任务执行后记录写失败仍推进内存触发时间，禁止每秒重复副作用。

### 沙盒工作区

沙盒身份必须绑定执行会话并继承到派生执行者，不能仅靠 cwd 或可消失的注册项解除限制。无操作系统隔离后端时禁止宿主任意代码与未知工具；不支持执行前拒绝的引擎不得在沙盒启动任务。写回须以导入基线进行三方比较、验证预览版本并拒绝冲突与目标软链接越界；生命周期操作须冻结准入并等待全部持有者停止。注册变更共享、串行、原子持久化，失败不得假成功。详见 `docs/architecture-sandbox.md`。

### 根因整改边界

跨模块操作的稳定归属、取消代次、持久提交与安全清理约束见 `docs/root-cause-boundaries.md`。工作树状态查询失败不得按无改动处理或授权自动删除；清理使用非强制删除及条件分支删除。审查与整改证据见 `docs/audits/root-cause-review-2026-10-29.md`。

### 5. 提交前三连验（防 CI 失败铁律）

任何修改在提交 PR 或交付前，**必须依次通过以下三道硬验证**：

1. `npx prettier --write <改动文件>`：确保代码风格符合 Prettier 规则（CI 第一步硬门禁）；
2. `npm run typecheck`：确保全项目 5 个 tsconfig 零类型错误（编写单测 mock 上下文严禁传裸 `{}`，必须使用 `{} as any` 避免 TS2740）；
3. `npx vitest run <相关单测>`：确保改动模块所有单元测试全部通过。

---

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