04. E2E 测试 Plan
翻译说明: 本页是与 英文源规格 一一对应的机器辅助翻译。代码、协议字段和标识符保持原文;如翻译与英文源事实有歧义,以英文版本为准。
范围:MVP 接受场景加上 PI-Desktop 当前发货的产品增量 状态:已接受(protocol/Electron 自动化已激活;完整桌面 Playwright 仍在计划中) 交叉引用:验收标准 · 里程碑 · ai-开发工作流程 · 变更清单
E2E-IMAGES-provider-save-feedback
- 前提: 生图配置 UI fixture,中英文界面。
- 步骤: 勾选生图模型并保存服务商;从摘要菜单切换默认生图模型; 再取消唯一生图模型的标记并保存。
- 预期: 服务商编辑确认“服务已更新”,清除生图选择后也不会提示已选择 生图模型;摘要菜单切换仍显示生图选择成功提示。绑定保存行为不变。
- 规格: 03-runtime/21-image-generation。验收: B。
- 里程碑: 维护。状态:
scripts/e2e-image-generation-ui.mjs自动覆盖。
E2E-IMAGES-deselect-default
- 前提: 只有一个服务商、一个模型,且该模型已标记为生图模型。
- 步骤: 在服务商编辑器取消生图勾选,先取消编辑,再重新操作并保存;重新打开设置和编辑器,选择该模型作为默认对话模型。
- 预期: 取消编辑保留原绑定;保存清除候选和默认生图绑定。重新打开后摘要行隐藏、复选框未勾选,模型恢复为可选对话模型。
- 覆盖:
scripts/e2e-image-generation-ui.mjs使用 API 边界 fixture 验证英文与中文交互;不代表真实服务商测试。
1. 目标
- 记录 MVP 必须验证的每个用户可见和协议可见的行为。
- 提供映射到验收标准 (A–H) 和里程碑 (M1–M6) 的场景目录。
- 作为可追溯性主干:场景 ID ↔ 验收标准 ↔ 规范。
- 定义代码 pull request 的相关 E2E 合入门。
- 让验证证据与准备合入的可执行提交保持关联。
2. 非目标
- 完全由UI驱动的自动化覆盖;协议和源合同自动化是 仍在计划中,而更广泛的桌面套件仍在计划中。
- 通用性能/压力测试(后 MVP);桌面响应性的有界回归检查由相关功能场景覆盖。
- 原生 Windows/Linux 发布资格(已发布的工件存在;原生 资格差距仍然记录在案)。
- 市场发行商来源和恶意插件沙箱场景;基本的 browse/install/update 流已成为目录的一部分。
- 远程 Gateway / 浏览器控制仍在范围外(ADR 0004 / 基线 #20);有界的本地回环 MCP 控制面由 E2E-220 覆盖。
3. 测试金字塔
╱ E2E ╲ — few, high-value, cross-system
╱ Integration ╲ — IPC/RPC contracts, host↔renderer
╱ Unit ╲ — per-module, fast, isolated| 级别 | 适用范围 | 计数目标 | 工装 |
|---|---|---|---|
| 单位 | 单模块,无 IPC | 很多 | Vitest / Rust#[测试] |
| 整合 | IPC 合约,主机↔渲染器,主机↔sidecar | 中等 | Vitest + IPC 模拟或现场 Electron |
| E2E | 桌面应用程序的完整用户旅程 | 100 多个功能+ US-UI 视觉目录 | 现在协议烟雾 + Electron 探针;后来成为剧作家 |
策略:记录所有 E2E 场景;当变更风险需要时,与代码一起添加或更新 unit/integration 测试;代码 pull request 使用有选择且高价值的 E2E 套件。低层级测试用于定位正确性,E2E 用于验证跨进程运行时行为;低层级测试通过不能豁免相关 E2E 门禁。
4. 工具意图
| 工具 | 目的 | 状态 |
|---|---|---|
| 维斯特 | 单元+集成(TS侧) | 活动(pnpm test,共享包) |
| Rust #[测试] | 主机核心单元测试 | 活跃(cargo test -p host-core) |
| 协议烟雾 | 主机 RPC + 工具 + 无头插件 | 有效(test:e2e,20 次检查) |
| Electron 探针 | 启动桥、会话列表响应性和崩溃监控 | 活跃(test:e2e:boot、test:e2e:supervision) |
| 剧作家 | 完整的 UI 驱动旅程 | 计划(M5 之后) |
决策:协议烟雾测试和 Electron 探针是活跃的验证资产;更广泛的桌面旅程按场景执行,所需平台不可用时记录为环境受限。
5. 环境要求
| 要求 | 详情 |
|---|---|
| 平台 | macOS arm64、Intel x64、Windows x64 和 Linux x64 发行目标 (D126/D285) |
| 公司简介 | 干净的 ~/.pi-desktop 配置文件(无需事先配置) |
| 固定装置 | 示例项目目录 (examples/fixtures/sample-project/) |
| 示例插件 | 从本地路径加载 examples/plugins/hello |
| 提供商 | 至少一个拥有有效密钥的提供商(测试帐户) |
| 显示 | 无头功能 Electron 或真实显示 |
6. 场景模板
每个场景都以这种格式记录:
### E2E-<ID>: <title>
- **Preconditions**: what must be true before steps start
- **Steps**: ordered list of user / system actions
- **Expected**: observable outcome that proves correctness
- **Specs linked**: relevant spec file(s)
- **Acceptance criterion**: which A–H letter(s) this verifies
- **Milestone**: M1–M6 target
- **Status**: Draft | Documented | Partially automated | Automated | PassedE2E PR 合入门
每个代码 pull request 都必须在打开或更新前,在已经包含最新 origin/main 的候选上通过与其回归面相关的 E2E(pnpm check:pr-base)。不得打开落后于 origin/main 的 PR。不要为了跑 E2E 把任务合进本地 main。代码变更包括渲染器、Electron Main、Preload、Agent Runtime、Rust host-core、会话、转录、计划、插件、MCP、权限、供应商/模型运行时、持久化、进程生命周期、打包/启动,以及影响应用执行的构建或 CI 行为。仅文档更改在不影响可执行行为时可豁免。
E2E 环境复用
task-candidate E2E 从请求工作树运行,但使用主工作区已经准备好的宿主开发环境。必要时通过引用或链接复用兼容的 Node/pnpm 工具链、node_modules、Electron、Rust/Cargo 目标、包存储、构建缓存和忽略的配置。
不得为了 E2E 单独运行 pnpm install、npm install,或创建第二套依赖/运行时环境。只隔离可变测试状态:临时 profile、数据目录、socket、端口、日志和工件。仅当宿主环境缺失或不兼容时才安装或重建依赖,并记录原因;干净的 CI 和发布 runner 可以按 lockfile 安装。
根目录 package.json 是可执行命令的事实来源。最低选择如下:
- 跨域运行时、host 或 IPC:
pnpm test:e2e。 - Electron 启动、preload 或窗口生命周期:
pnpm test:e2e和pnpm test:e2e:boot。 - 会话列表刷新或模型能力查询:
pnpm test:e2e和pnpm test:e2e:boot, 包括合成大列表的响应性检查。 - 设置/输入框/插件搜索的主题表面:先运行
pnpm build:js,再运行pnpm test:e2e:theme-surfaces。 - 输入框剪贴板表示与文本插入:
pnpm test:e2e:composer-paste。 - 输入框斜杠菜单名称/描述布局:先运行
pnpm build:js,再运行pnpm test:e2e:composer-autocomplete。 - 聊天记录渲染边界和跨活动段委派显示:
pnpm test:e2e:transcript。 - Plan host/runtime:
pnpm test:e2e和pnpm test:e2e:plan。 - Plan UI:
pnpm test:e2e:plan和pnpm test:e2e:plan-ui。 - host/sidecar 监督、崩溃恢复或重启:
pnpm test:e2e和pnpm test:e2e:supervision。 - 子代理生命周期:
pnpm test:e2e和pnpm test:e2e:subagents。 - 导入扩展依赖安装或 registry 边界改动:
pnpm test:e2e:plugin-import-deps。 - 受信任扩展或插件扩展改动:
pnpm test:e2e:trusted-extensions。 - 会话通信 / Session Orchestrator:
pnpm test:e2e:collaboration。 - 完成通知静默或静默回合契约(D193 / D446):
pnpm test:e2e:session-completion。 - 同时涉及多个面的改动使用适用套件的并集。
pnpm test:e2e 是 host RPC、IPC、Agent 执行、插件、持久化集成和共享运行时合约的默认跨系统烟雾测试。由于显示、平台、凭据、硬件或其他环境能力缺失而无法运行的必需套件,必须记录为 NOT RUN,并说明原因、替代验证和剩余风险。在具备条件且可信的环境中通过前,该 pull request 不具备合入条件。
必需结果必须对应准备合入的可执行提交。E2E 通过后如果可执行代码发生变化,必须重新运行受影响套件。报告命令、结果、测试提交以及相关环境限制;不得声称未运行的套件已通过。
E2E 失败策略
必需 E2E 失败时必须先分类为实现回归、测试回归、环境失败或已知的不稳定基础设施,修复产品或测试缺陷后重新运行受影响套件。不得删除场景、削弱断言或添加掩盖确定性失败的重试。所需平台尚未自动化时,保留已记录状态并说明仍需进行的平台验证。
7. MVP 场景目录
运行时资源治理
E2E-097:工具爆发受到限制,主机重启后恢复
- 先决条件:主机核心健康;一个会话有一个工作区;的 监督探针可以终止主机进程。
- 步骤:1) 调度比主机工具预算更大的突发,其中包含 读取工具和 shell 命令。 2) 突发运行时观察
app.health。 3) 在活动呼叫期间终止 host-core。 4)等待一位监督者 重新启动。 5) 冲洗持久性发件箱。 - 预期:活动 shell 进程永远不会超过配置的全局和 每个会话的限制。多余的工作返回
HOST_OVERLOADED或在 有界队列。仅运行一个重启循环;过时的一代调用很快就会失败 作为HOST_UNAVAILABLE;无重复ERR_STREAM_DESTROYED持久风暴 被发射。同一突发期间的临时操作系统线程压力不会 通过其 stdio 控制路径终止 host-core;主机仍保留在一台上 发电和容量错误保持结构化。已完成 assistant/tool 恢复后消息将保留一次。 - 链接规格:
03-runtime/06-host-rpc-protocol.md,03-runtime/07-process-model.md、03-runtime/08-error-codes.md、03-runtime/09-logging-and-observability.md、ADR 0051 - 验收:A(运行时运行状况)、C(工具执行和恢复)
- 里程碑:M5
- 状态:已记录;自动化待定
启动和健康检查
E2E-001:应用程序启动并显示主窗口
- 先决条件:macOS arm64 或 Intel x64;没有先前的
~/.pi-desktop配置文件。对于 开发通道、工作区包构建输出不存在或早于 他们的 TypeScript 来源。 - 步骤:1) 启动 PI-Desktop。在开发通道中,使用
pnpm dev。 2) 观察主窗口出现。 - 预期:开发启动之前会重建所有工作区依赖项 host-core 和 Electron 启动。窗口首先显示品牌启动画面 当 bootstrap 运行时,然后用当前的英文显示主 shell 语言环境目录;没有编译错误、缺少菜单运行时错误或崩溃; 版本信息可见。分类日志会写入关键生命周期和错误记录。GitHub 自动更新在首个窗口出现之后才开始, 且不会因约 60 秒的网络超时把状态钉在
checking。 - 链接规格:
03-runtime/07-process-model.md、04-ux/01-ui-ia.md、03-runtime/09-logging-and-observability.md§7b - 接受:A(应用程序启动)
- 里程碑:M1
- 状态:部分自动化(
runtime-build-contract.test.mjs涵盖 依赖构建合约;update-timeout.test.mjs与auto-update.test.mjs覆盖限时自动检查;Electron 窗口启动仍处于草案状态)
E2E-002:IPC 桥功能正常
- 前提条件:应用程序正在运行。
- 步骤: 1) 触发调用 preload IPC 的操作(例如版本查询)。 2)观察渲染器中的结果。
- 预期:Main↔渲染器 IPC 返回预期数据;没有错误。
- 链接规格:
03-runtime/01-ipc-protocol.md - 验收:A(桥梁正常)
- 里程碑:M1
- 状态:自动化(
scripts/e2e-electron-boot.mjs— 沙盒 preload 桥 + IPC 往返)
E2E-003:Rust 主机运行状况检查响应
- 前提条件:应用程序正在运行; Rust host-core sidecar 已启动。
- 步骤:1) 使用协议版本 10 进行 Electron 握手。 2) 呼叫主机 健康检查 RPC。 3) 使用不匹配的旧协议和新协议重复启动 固定装置。
- 预期:协议 v10 主机返回
ok并记录握手。 除 v9 之外的所有版本,无论较旧还是较新,都会在 对话界面变为交互式,因此 Plan approval/state 事件和 上下文检查点不能悄无声息地丢失。 - 链接规格:
03-runtime/05-host-core-rust.md、03-runtime/06-host-rpc-protocol.md - 验收:A(桥梁正常)
- 里程碑:M1
- 状态:自动(协议烟雾)
E2E-004:出现首次运行内联检查表
- 先决条件:新鲜的配置文件(无
~/.pi-desktop)。 - 步骤:1) 在新的配置文件上启动应用程序。 2) 遵守新手引导清单。
- 预期:显示内嵌清单; provider/key 项目打开设置 → Agent,可选插件项打开app-shell Plugins 目的地。
- 链接规格:
04-ux/05-onboarding.md - 验收:A(首次运行清单)
- 里程碑:M2
- 状态:自动(协议烟雾:主机加入状态;UI 清单手册)
提供商和密钥
E2E-169:默认模型选择器选择已配置模型
- 前置条件:存在两个可运行的提供商,其中一个至少配置两个模型绑定。
- 步骤:打开“设置 → 模型配置 → 更改默认模型”,选择该提供商下的第二个模型。
- 预期:选择器显示模型级条目而非仅显示提供商;所选模型被标记为当前模型,新建会话继承准确的模型及其所属提供商。之后改默认模型不会改写已创建会话的存储模型;只有新会话和未持久化的首页草稿跟随当前默认。 按提供商或模型搜索会在本地过滤;结果列表在不移动设置卡片的情况下滚动,无匹配查询显示空状态。
- 关联规格:
03-runtime/13-model-catalog-and-selection.md - 验收标准:B(模型选择)
- 里程碑:M6
- 状态:已记录;自动化待完成
E2E-005:添加提供商并保存 API 密钥
- 前提条件:应用程序运行;没有配置提供商。
- 步骤: 1) 打开设置 → Agent。 2) 打开添加提供商对话框。 3) 输入名称、基础 URL、模型 ID 和 API 密钥。 4)保存。
- 预期:提供商显示为带有秘密徽章的卡片;安全存储密钥(不在明文配置中);英雄摘要计数更新。
- 链接规格:
03-runtime/12-provider-config-schema.md、03-runtime/14-secrets-storage.md - 接受:B(添加提供商,保存密钥)
- 里程碑:M2
- 状态:自动(协议烟雾:提供商创建+秘密,无明文回显)
E2E-PROVIDER-defaults-survive-an-added-provider:新增提供商不改写应用默认值
- 前提条件:应用运行;提供商 A 已保存并设为应用默认模型;另有提供商 B 提供不同的模型;A 上配置了一个图片模型,另一家服务上也配置了一个。
- 步骤:1) 打开设置 → 模型配置,新增提供商 B,保存时不动「默认模型」行。2) 确认「默认模型」行仍是提供商 A 与其确切模型,且新建会话使用它。3) 将某个图片模型设为默认画图模型,再新增一个同样提供图片模型的服务并保存。4) 确认「默认画图模型」行仍指向原绑定,而选择器的候选里出现新提供商的图片模型。5) 删除拥有默认值的那家提供商,再新增一个既提供对话模型又提供图片模型的服务并保存。6) 确认两个默认值此时都解析到新增的服务。
- 预期:新增提供商保存后不会改写仍然可解析的应用默认值:默认模型保留「默认模型」行已经展示的那对提供商/模型,默认画图模型保留其存储绑定,同时候选列表继续增长。只有已无法解析的默认值(提供商被删除,或其模型已从提供商移除)才会由新增的提供商填补,因此只有在应用否则将无从运行时才会写入设置。显式的「设为默认」操作、编辑路径,以及回落到首个剩余绑定的行为都不变。
- 链接规格:
03-runtime/13-model-catalog-and-selection.md - 接受:B(模型选择)
- 里程碑:M6
- 状态:已文档化;由
apps/desktop/test/default-model-display.test.mjs、apps/desktop/test/image-generation-default.test.mjs、apps/desktop/test/provider-model-config.test.mjs覆盖
E2E-PROVIDER-custom-model-limits:手输的自定义模型 id 从模型库取限额
- 前提条件:应用运行;models.dev 快照随构建发布;已打开某个提供商编辑器。
- 步骤:1) 在「添加模型」输入框里输入模型库已发布的模型 id 并添加,确认新行的上下文长度、最大输出与思考等级与已发布记录一致,而不是 128,000 / 8,192 且无思考等级。2) 输入模型库未发布的 id 并添加,确认该行沿用通用种子 128,000 / 8,192 且无思考等级。3) 在服务模型列表不可用的状态下添加一个 id,确认该行仍然只出现一次且可编辑。4) 添加一个 id 后立刻修改其限额(此时查询尚未返回),确认手输的值被保留。
- 预期:
providers.lookupModel只从本地快照回答手输 id —— 不访问提供商网络、不调用主机 —— 命中时按「被勾选的模型」同样的口径播种绑定(已发布的上下文长度、最大输出、思考等级,contextWindowSource: "catalog"),而存储的 id 保持用户输入的原样。未命中、调用失败,或该 id 已被本次发现结果描述过时,行为与之前一致:一行可用,通用种子,列表既不卡住也不重复。 - 链接规格:
03-runtime/12-provider-config-schema.md、03-runtime/13-model-catalog-and-selection.md - 接受:B(多模型提供商配置)
- 里程碑:M2
- 状态:已文档化;由
apps/desktop/test/model-custom-lookup.test.mjs与apps/desktop/test/provider-lookup-model-handler.test.mjs覆盖
E2E-005H:从较长的服务模型列表一次全选当前可见模型
- 前提条件:应用已运行;添加或编辑服务(或厂商账户)对话框已打开,且该服务返回较长的模型列表,其中至少有一个模型 ID 不会匹配稍后的搜索。
- 步骤:1) 等待左侧列出该服务的模型。确认列表标题旁有一个全选复选框,在尚未勾选任何行时为空。2) 勾选该复选框。确认列出的每一行都被勾选,右侧为每一行出现一条绑定,并保留已配置的高级覆盖。3) 取消其中一行,确认标题复选框变为不确定态。再次勾选后,剩余可见行被选中且不会重复已选绑定。4) 输入只匹配一部分模型的过滤。取消勾选标题复选框,确认只有匹配的已选行被移除;被过滤隐藏的模型仍留在右侧。5) 再次勾选标题复选框,确认只把匹配行加回来。清空过滤后,先前被隐藏的已选模型仍在。6) 保存。
- 预期:一个标题复选框即可勾选或取消当前可见列表。搜索过滤会缩小“全部”的范围。过滤外已选模型保持不变。新勾选的行采用已发布的上限与思考等级;已有绑定不会被重建。厂商账户编辑对话框也有同一控件,因为两个对话框共用该选择器。
- 链接规格:
03-runtime/13-model-catalog-and-selection.md、04-ux/06-settings-ia.md、04-ux/08-component-spec.md - 验收:B(多模型提供商配置)
- 里程碑:M2
- 状态:单元覆盖(共享选择器源契约);界面场景待完成
E2E-PROVIDER-configured-models-search:右栏搜索收窄已配置模型列表
- 前提条件:应用已运行;已保存一个包含至少三个模型绑定的提供商,其中一个绑定带别名,另有一个绑定的 id 与其目录显示名不同。
- 步骤:1) 打开设置 → 模型配置并编辑该提供商。2) 在右栏「模型设置」标题旁的搜索框输入某个模型 id 的一部分,确认已配置列表随输入收窄,而标题旁的计数仍报告全部已配置模型。3) 先按另一行的别名搜索、再按目录显示名搜索,确认两者都能找到它们代表的绑定。4) 输入一个匹配不到任何行的查询,确认右栏显示它自己的空提示,而不是「尚未选择模型」。5) 在该查询仍在输入框中时,于左侧发现列表勾选一个模型,确认它被加入且可见(只有当该过滤会把它隐藏时输入框才被清空);再勾选一个 id 符合该查询的模型,确认过滤词保留。6) 清空过滤后删掉全部已配置模型,确认输入框中不留任何查询词(它此时处于禁用态)。7) 保存。
- 预期:右栏搜索仅在本地过滤已配置列表,不产生主机往返;匹配对模型 id、别名与目录显示名做不区分大小写的子串匹配。计数徽标始终报告全部已配置模型,因此被过滤的视图不会被误认为提供商提供的模型更少。在过滤生效时新增的模型绝不会落在视野之外:当新行会被隐藏时清空输入框,当过滤仍能显示它时保留;列表清空时会丢弃查询词,避免过滤词滞留在被禁用的输入框里。
- 链接规格:
03-runtime/13-model-catalog-and-selection.md、04-ux/08-component-spec.md - 验收:B(多模型提供商配置)
- 里程碑:M2
- 状态:已记录;界面自动化待补
E2E-005I:从选择器标题重新获取服务模型列表
- 前提条件:添加或编辑服务(或厂商账户)对话框已打开,且该服务可访问并提供
/models列表。 - 步骤:1) 确认左侧标题旁有「获取列表」,在有效端点就绪前为禁用。2) 填入有效端点。确认在 600 ms 防抖等待期间「获取列表」已可用。立刻点击;确认它不等待该窗口,探测时显示加载文案,然后列出模型。3) 再次点击「获取列表」。确认加载期间保留当前行,并以实时结果替换。4) 让服务离线后再点「获取列表」;确认出现分类错误且先前的行仍在。5) 恢复服务,再点「获取列表」,确认实时列表回来。6) 在厂商账户编辑对话框重复上述步骤。
- 预期:标题上的操作会立刻向服务探测,包括端点刚变为有效后的防抖等待期间。凭据变更触发的自动发现不变。两种凭据都有同一控件,因为两个对话框共用该选择器。
- 链接规格:
03-runtime/13-model-catalog-and-selection.md、04-ux/06-settings-ia.md、04-ux/08-component-spec.md - 验收:B(多模型提供商配置)
- 里程碑:M2
- 状态:单元覆盖(发现 hook + 选择器源契约);界面场景待完成
E2E-005D:配置智谱 / Z.AI 命名端点预设
- 前提条件:应用已运行;尚未配置智谱提供商;models.dev 快照包含
zhipuai、zhipuai-coding-plan、zai、zai-coding-plan。 - 步骤:1) 打开设置 → 模型配置并打开添加提供商对话框。2) 打开「服务」并输入 过滤,选择 智谱 AI Coding Plan。确认在填入 API 密钥前不会开始模型发现。 3) 确认常见路径是服务 + API 密钥并带主机摘要,输入 API Key,等待模型发现。4) 选择发现的模型并保存。5) 重新打开该提供商,将服务 切换到 Z.AI,再切到 自定义端点。
- 预期:Coding Plan 显示服务 + API 密钥、主机摘要
open.bigmodel.cn/api/coding/paas/v4,焦点落在密钥框。名称与接口格式不在 常见路径上。保存行持久化vendorKey: "zhipuai-coding-plan"、apiStyle: "chat_completions"以及精确 Coding Plan URL。切到 Z.AI 会把主机 摘要换成api.z.ai/api/paas/v4与vendorKey: "zai"。切到自定义端点后露出 可编辑的名称和 Base URL。针对智谱 URL 的后续 Agent 回合以 Zhipu thinking (thinkingFormat: "zai")发送 Completions。 - 链接规格:
03-runtime/11-provider-model-system.md、03-runtime/12-provider-config-schema.md、04-ux/06-settings-ia.md、ADR 0155 - 验收:B(模型配置与密钥存储)
- 里程碑:M2
- 状态:单元覆盖(预设匹配、目录别名、Completions 兼容);界面场景草稿
E2E-005E:聚合端点上的 DeepSeek 思考回放带上 reasoning_content
- 前提条件:已配置一个 Base URL 不含
deepseek.com的 OpenAI 兼容提供商,模型 ID 属于 DeepSeek 系(例如硅基流动deepseek-ai/DeepSeek-V3.2),并开启思考模式。 - 步骤:1) 在思考模式下开始会话。2) 完成若干回合,其中至少一条助手回复没有思考文本。3) 再发一条提示,使历史被回放到提供商。
- 预期:后续 Completions 请求的每条 assistant 消息都带推理字段。官方
api.deepseek.com行对无思考内容的回合仍可使用""。非官方 DeepSeek 系中转在可用时回传保留的真实思考,否则使用 ADR 0256 文档化的非空占位符——压缩后不得静默回填空串。不会仅因模型 ID 含"deepseek"就把thinkingFormat改成"deepseek"。官方api.deepseek.com行仍使用 pi-ai 按 URL 检测的 DeepSeekthinkingFormat。 - 链接规格:
03-runtime/11-provider-model-system.md、03-runtime/12-provider-config-schema.md - 验收:B(提供商 Completions 兼容)
- 里程碑:M2
- 状态:单元覆盖(compat 注入 + convertMessages 空/非空补全 + 压缩保留推理上线证明);现场 OpenCode/聚合端验证仍推迟;界面场景待完成
E2E-005G:按供应商自定义 HTTP 请求头
- 前提条件:一个 API 密钥 AI 服务(含 OpenCode Go)和一个已登录的厂商 OAuth 账户。
- 步骤:打开高级设置弹框,确认只有标题栏关闭按钮而没有底部操作行;用常用预设添加 User-Agent,导入超过五行可见区域的 JSON,再复制请求头 JSON;确认列表最多显示五行,更多请求头在自身区域滚动;确认剪贴板是与持久化相同的规范化对象(忽略空名称,后者覆盖前者),并有本地化成功反馈。
- 预期:复制输出与保存到该行的
pairsToRecord映射一致;导入仍接受两种 JSON 形状;窄宽度下工具栏换行而不溢出。空映射恢复适配器默认值。 - 链接规格:
03-runtime/12-provider-config-schema.md、04-ux/06-settings-ia.md、ADR 0178 - 验收:B(模型配置)
- 里程碑:M2
- 状态:单元覆盖(表单 Advanced 复制契约);界面场景草稿
E2E-005J:GitHub Copilot OAuth 请求携带原生 IDE 标头
- 前提条件:已有一个登录的 GitHub Copilot OAuth 账户并选定模型;确定性的捕获代理会记录模型请求标头。
- 步骤:1)针对该账户发起 Agent 回合并记录请求标头。2)助手回复后发送后续回合并记录下一次请求。3)选定模型支持视觉时,用图像附件重复测试。4)设置一个与 Copilot 默认标头同名的已保存自定义 header,再发送一次回合。
- 预期:每次 Copilot 模型请求都包含固定 pin 的 pi-ai 传输身份标头
Editor-Version、Editor-Plugin-Version与Copilot-Integration-Id。用户发起的请求中X-Initiator为user,延续请求中为agent;Openai-Intent为conversation-edits,图像请求包含Copilot-Vision-Request: true。OAuth 行继续使用本地 provider id 进行账户绑定,已保存的自定义 header 仍是最后的覆盖层。 - 链接规格:
03-runtime/11-provider-model-system.md、03-runtime/12-provider-config-schema.md、ADR 0095 - 验收:B(模型配置)、F(运行时提供商请求)
- 里程碑:M2
- 状态:单元覆盖(行级模型标头与请求上下文标头);真实 Copilot 账户旅程待验证
E2E-006:密钥在重启后仍然存在
- 先决条件:已配置提供商+密钥。
- 步骤:1) 退出应用程序。 2)重新启动。 3) 打开设置 → Agent → 提供商。
- 预期:提供商仍在列表中;密钥可用(无需重新输入)。
- 链接规格:
03-runtime/14-secrets-storage.md - 接受:B(密钥在重新启动后仍然有效)
- 里程碑:M2
- 状态:草案
E2E-007:无提供商阻止提示
- 前提条件:应用程序运行;没有配置提供商。
- 步骤:1) 尝试开始聊天。
- 预期:清除阻止提示,解释必须配置提供程序。
- 链接规格:
04-ux/06-settings-ia.md - 接受:B(阻止提示)
- 里程碑:M2
- 状态:草案
对话流和中止
E2E-008:新会话并发送消息
- 先决条件:已配置提供商。
- 步骤:1) 创建新会话。 2) 输入消息。 3)发送。
- 预期:转录结果立即显示紧凑的本地化
Working…发送后、第一个助手或工具事件之前的状态。思考、工具和回答出现后 仍保留提示,已知具体阶段时显示对应等待状态。等待用户操作时隐藏, 回合结束时消失。 - 链接规格:
03-runtime/02-agent-runtime.md、03-runtime/10-session-state-machine.md - 接受:C(新会话,发送消息)
- 里程碑:M2
- 状态:自动化(协议烟雾、实时模型通道;需要 PI_DESKTOP_TEST_API_KEY)
E2E-008d:Composer 回车发送与修饰键发送
- 先决条件:已配置提供商;已打开会话。
- 步骤:1) 保持「回车发送」开启,输入草稿并按 Enter。2) 在设置中关闭 「回车发送」。3) 输入第二段草稿并按 Cmd/Ctrl+Enter。4) 在第三段草稿中按 Enter 和 Shift+Enter。
- 预期:步骤 1 发送。关闭设置后,Cmd/Ctrl+Enter 发送;普通 Enter 与 Shift+Enter 只换行、不发送。IME 组合输入和打开的自动补全菜单仍优先于发送。
- 链接规格:
04-ux/01-ui-ia.md、04-ux/08-component-spec.md§11.5、04-ux/09-interaction-patterns.md§1.2 - 验收:C(Composer 发送)
- 里程碑:M2
- 状态:源级回归(
composer-ime.test.mjs);完整 UI 键盘旅程仍为 Draft。 协议冒烟不会派发按键事件。
E2E-008a:首轮工具按需加载
- 先决条件:Agent 模式;提供商已配置;
BrowserPreview或 启用插件工具可用;请求捕获可以检查第一个和 后续提供商有效负载。 - 步骤: 1) 创建一个新会话并发送一个简单的提示。 2) 检查 第一个提供商请求的工具列表。 3) 要求代理创建或编辑 HTML 页面并观察工具活动。 4) 之后启动第二个用户提示 预览任务完成。
- 预期:第一个请求仅包含模式核心工具(Agent:
Working…/thinking/tool/answer/03-runtime/02-agent-runtime.md/03-runtime/10-session-state-machine.md;聊天:BrowserPreview/Agent/Agent) 和本地ToolSearch;延迟模式是 仅由有界# On-demand tools目录表示。代理打电话ToolSearch在BrowserPreview之前(或选定的 plugin/ToolSearch工具) 当需要该功能并且匹配模式可用时 下一个模型回合。对于用户可见的 HTML 可交付成果,BrowserPreview是 创建或第一次有意义的视觉编辑后调用一次,然后重复使用 在页面优化时通过实时重新加载。已生成,仅供测试,并且 非可视 HTML 文件不会触发预览调用。加载的集合不 泄漏到下一个提示的第一个请求中。工具激活标记幸存 在不授予主机权限或工作区逃逸的情况下重新加载脚本。 - 链接规格:
03-runtime/02-agent-runtime.md§7.1,03-runtime/03-tools-and-permissions.md§2.1、ADR 0048、08-meta/decisions-log.md(D185) - 验收:C(第一轮和流)+ E(工具执行)
- 里程碑:M5
- 状态:单位覆盖(
agent-runtime延迟工具测试);真人模特 请求捕获和完整的 Electron 旅程待处理
E2E-008b:随应用打包的浏览器插件 chrome 与 CDP
- 先决条件:带随应用打包插件的打包或检出构建;带工作区 HTML 文件的 Agent 会话;可用 Plan 会话。
- 步骤:1) 确认插件页列出
pi.browser,已启用,不可卸载。 2) 打开工作面板并从插件视图启动浏览器。3) 让代理预览工作区 HTML 文件(BrowserPreview),再通过 ToolSearchcdp/Browser做 snapshot。 4) 切到 Plan 并调用插件 Browser 工具。5) 禁用pi.browser。 6) 调用BrowserPreview并点击 http(s) 对话链接。7) 从第三方或测试调用方 通过pi.browser.cdp发送Network.getAllCookies。 - 预期:启动项没有宿主 Browser 行。预览打开插件视图并实时重载文件。 插件工具
plugin_pi_browser_Browser在 ToolSearch 后可以 snapshot。Plan 拒绝 插件工具(PLUGIN_DISABLED_IN_PLAN),而BrowserPreview仍可调用。禁用后 隐藏视图和工具;BrowserPreview报错;http(s) 芯片走openExternal。Cookie CDP 被拒绝。访客页边界留在插件视图内。 - 链接规格:ADR 0170、D333、
07-plugins/03-plugin-api.md、03-runtime/03-tools-and-permissions.md - 验收:E(插件视图 + 工具)+ 安全白名单
- 里程碑:M5
- 状态:单位覆盖(
bundled-plugins、browser-cdp、browser-preview-tool);完整 Electron 旅程待处理
E2E-009:UI 中可见的流式令牌
- 先决条件:会话处于活动状态;消息已发送。
- 步骤:1) 请求包含 Markdown 和行内/块级数学公式的长答案,公式同时使用 美元符号(
$…$/$$…$$)和 TeX 括号(\(…\)/\[…\])分隔符。 2) 观察助理的流式响应。3) 让答案完成并检查渲染器控制台。 - 预期:运行时块通过增量 Markdown 渲染器逐渐出现,最终响应完整。 四种数学分隔符均由 KaTeX 渲染,其中
\[…\]使用块级布局。渲染器不会 启动第二个动画帧打字机循环、触发 React 错误 185,或因 CSP 拒绝 Vite 内联的 KaTeX 字体。 - 链接规格:
03-runtime/02-agent-runtime.md,04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、05-security/01-security.md - 验收:C(流式输出),质量
- 里程碑:M2
- 状态:部分自动化(协议实时模型流、
renderer-stream-safety.test.mjs中的渲染器源回归,以及latex-math.test.mjs中的数学分隔符渲染; 完整 UI 观察仍为草案)
E2E-010:中止生成
- 先决条件:一个会话可以产生故意延迟的第一个 令牌和流响应。
- 步骤:1) 发送普通文本,并在助手文本、思考或工具行开始前确认 清空后的草稿显示单一“停止”控件。2) 回合运行时输入另一条草稿,确认 同一槽位变为可用的“发送”;点击发送排队后,确认草稿为空时再次显示“停止”。 3) 在助手输出开始前停止,确认用户行被撤消且文本恢复到输入框。4) 再次发送, 等待部分输出,然后在流式传输中停止。5) 观察抄本和输入框。
- 预期:运行中且有内容时使用“发送”并将提示加入队列;运行中且为空时使用“停止”。 未答复的发送将被撤消并恢复其草稿。流式发送停止,保留部分响应且没有草稿恢复或重复 用户回合。该会话仍然可用。
- 链接规格:
03-runtime/02-agent-runtime.md - 接受:C(中止)
- 里程碑:M2
- 状态:草案
E2E-171:流式回复在退出、崩溃和停止后仍然保留
- 先决条件:一个转录长度超过渲染器一页(超过 100 条消息)的会话,以及一个 在首次工具调用前至少流式输出十秒的模型。
- 步骤:1) 发送提示并让回复流式输出约 5 秒。2) 在流式过程中退出应用 (Cmd+Q / 托盘退出),重新启动并打开该会话。3) 重复发送,然后在流式过程中 杀掉 agent sidecar 进程并观察转录。4) 重复发送,在流式过程中按“停止”,然后从 侧边栏重新打开会话,并检查
sessions/<id>.jsonl与sessions/<id>.inflight.json。5) 重复发送并让其正常完成。 - 预期:2) 会话显示用户提示,其后是截至退出前至多 1.5 秒的已流式文本,作为
aborted回合下的一条aborted助手行;会话中更早的内容没有缺失或截断。 3) 正在流式的行原地落定为aborted并保留文本,重新加载后同一行仍在,且不再有.inflight.json。4) 停止后以及重新打开后部分回复立即可见;转录文件没有被重写 (其更早的行逐字节相同),并且在中止的最终行落盘后检查点文件已消失。5) 完成的 回复每个助手片段恰好一行,没有aborted重复行,也没有检查点文件。退出时尚未离开 outbox 的已完成回复在重新启动后仍然存在(从检查点提升为complete,或在第一次session.get之前从 outbox 排空)。 - 链接规格:
03-runtime/04-data-storage.md、03-runtime/06-host-rpc-protocol.md、03-runtime/07-process-model.md、03-runtime/01-ipc-protocol.md - 接受:C(中止)、F(持久化)
- 里程碑:M2
- 状态:草案
E2E-SESSION-outbox-duplicate-id-does-not-drop-history
- 先决条件:两个会话的工具行把同一个
toolCallId当作messages.id(例如call_421522)。第一个会话已经持久化该 id。第二个会话随后又跑了若干回合,助手/工具行排在这条碰撞追加之后。 - 步骤:1) 在会话 A 完成一条 id 为
call_421522的工具调用。2) 在会话 B 使用同一供应商工具 id,再继续聊几轮。3) 退出并重新打开。4) 打开两个会话。 - 预期:会话 A 仍有原来的工具行。会话 B 重新打开后仍有后续回合;碰撞的工具行存成
{sessionB}:{call_421522}(或等价改写 id)。持久化 outbox 为空,没有停在UNIQUE constraint failed: messages.id。任一会话都没有丢掉更晚的助手/工具行。 - 链接规格:
03-runtime/04-data-storage.md、03-runtime/06-host-rpc-protocol.md、ADR 0041、D444 - 接受:F(持久化)
- 里程碑:M2
- 状态:单位已覆盖(
append_message_remaps_ids_owned_by_another_session、persistence-outbox.test.mjs);桌面旅程待补
E2E-SESSION-outbox-poison-does-not-drop-history
- 先决条件:一个 session-collaboration 投递回合正在运行。用户用 Alt+Enter 做 steering(内容与投递不一致)。之后本会话或其他会话的助手/工具行排在这条追加后面。可选:outbox 里已有旧宿主留下的
PERMISSION_DENIED:队头。 - 步骤:1) 开始一次协作投递回合。2) Alt+Enter 一条转向输入。3) 让该回合产出助手/工具行(也可在另一会话)。4) 退出并重新打开。5) 打开受影响会话。
- 预期:steering 作为没有
session_message来源的人类用户行落盘。投递用户行仍有宿主来源。更晚的助手/工具行在重新打开后仍在。outbox 为空,没有停在PERMISSION_DENIED:。PLUGIN_PERMISSION_DENIED队头仍会暂停而不是排空。 - 链接规格:
03-runtime/04-data-storage.md、03-runtime/06-host-rpc-protocol.md、ADR 0041、ADR 0239、ADR active-turn-steering、D597 - 接受:F(持久化)
- 里程碑:M2
- 状态:单位已覆盖(
steering_input_persists_without_inheriting_delivery_origin、persistence-outbox.test.mjs);桌面旅程待补
E2E-173:展开中的实时委托运行过程跟随最新输出
- 先决条件:一个绑定项目的 Agent 会话,提供商流被模拟为一个仍在运行的
explorer委托:它已经产出足够多的思考和工具行,溢出有界的.subagent-run-rows区域,并且在卡片展开后继续追加行并流式输出回答。 - 步骤:1) 展开正在运行的委派节点,不要触碰嵌套滚动区。2) 观察新的嵌套行 到达(思考、工具调用、流式回答)。3) 在委托仍在产出时,向上滚动嵌套区域以 回看更早的工具行。4) 点击嵌套的「回到最新」控件。5) 用键盘重复(
Tab到带 标签的运行分组,然后PageUp/ArrowUp)。6) 确认父级转录自身的跟随状态 不变:若已吸底则仍吸底;若用户已向上滚动转录,则仍不跟随。 - 预期:展开时嵌套滚动区钉在最新一行。钉住时,新的嵌套输出无需用户滚动 即可保持在视野中。第一次真正的向上手势暂停嵌套跟随,让更早的行留在视野中, 新输出在下方追加,并在嵌套滚动区上显示「回到最新」控件(不是父级转录的那个 控件)。点击它,或滚回嵌套底部 48px 以内,会重新钉住并跳到最新嵌套行。布局 夹持或程序性 follow
scrollTo不会解除嵌套跟随。父级转录的钉住 / 回到最新 状态相互独立。运行标题和折叠导轨保持可见且不被裁掉。 - 链接规格:
04-ux/08-component-spec.md§9.9、04-ux/09-interaction-patterns.md§9.1、decisions-log D271 / D302 - 接受:C(会话)、Quality
- 里程碑:M6+
- 状态:单位/源契约已覆盖(
subagent-transcript.test.mjs、transcript-scroll.test.mjs);桌面旅程待补
E2E-011:在项目和临时会话之间切换
- 先决条件:一个保留的项目会话和一个无路径的临时会话 会话存在。两份成绩单都超过一个视口,并且具有不同的最终结果 记录。
- 步骤:1) 从其精确路径侧边栏组打开项目会话。 2) 滚动到较早的记录并确认出现跳转到最新的控件。 3) 打开临时会话并观察其第一个绘制的帧。 4) 开关 快速项目→临时→项目,而前两个成绩单读取 延迟,并观察哪一行响应以及哪个目标提交。 5) Hover/focus 暂时的,再次切换到它来锻炼暖缓存,然后 启用减少运动后重复。 6)观察聊天内容和工作空间 铬。
- 预期:侧边栏不包含最近的聚合;保留项目 范围组和无路径会话仍处于临时状态;每个 成绩单加载正确;选择临时清除项目上下文并 不继承任何工作空间访问权限;两次会议仍然持续进行。每个会话的 首次激活在转录本底部绘制其独特的最终记录,而无需 首先暴露记录顶部、另一个会话的滚动位置, 或陈旧的跳转到最新控件。最新点击的行立即响应 并且其转录请求不会等待被取代的读取;只有决赛 project/session/work-panel 元组提交。在冷切换时,当前可见的 面板在一条细进度轨道下继续显示它自己的会话,直到目标提交, 输入框保持已落定的 home/docked 形状但处于惰性,因此提示无法 发往正在离开的会话;任何时刻都没有转录被调暗。热重新访问 会立即以其自己的内容和滚动位置揭示保留的面板,然后就地 重新验证而没有可见变化。减少运动呈现静态进度轨迹并 保持相同的目的地,没有骨架屏重新挂载,也没有生动地穿越历史。
- 链接规格:
03-runtime/10-session-state-machine.md,04-ux/01-ui-ia.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md - 接受:C(切换会话)
- 里程碑:M2
- 状态:涵盖源级回归;全视觉场景草稿
E2E-011a:新会话,而另一个会话仍在流式传输
- 先决条件:已配置提供商;会话 A 正在流式传输很长的时间 响应且输入框草稿为空(单一提交槽位显示“停止”)。
- 步骤: 1) 当 A 仍在直播时,单击“新任务”/“新聊天”。 2) 观察新会话的输入框。 3) 输入提示并在 A 时发送 继续在后台播放。 4)让A完成并观察 又是新会议的输入框。
- 预期:新会话立即显示空闲的发送控件 (绝不是卡住的 stop 控件)并且其文本区域已启用;提示 正常发送和流式传输,而 A 继续在后台运行。当A 结束时,其跨会话
agent_end不会更改新会话的 输入框状态,该状态与发送控件保持空闲状态。 - 链接规格:
04-ux/08-component-spec.md(§11.4),04-ux/09-interaction-patterns.md(§1.6,§11) - 接受:C(会话隔离、聊天和流)
- 里程碑:M2
- 状态:单位覆盖(
composer-send-state.test.mjs);完整的 UI 场景草稿
E2E-011b:从保留的项目组创建新会话
- 先决条件:已配置提供商;至少有一个保留项目 在侧边栏中可见;当前对话可能处于空闲或流状态。
- 步骤: 1) 单击项目组的“新建会话”控件。 2) 等待 要加载的项目对话。 3) 键入提示并检查发送控件。 4) 无需再次单击“新建会话”即可发送。
- 预期:项目激活和会话创建作为一个渲染器提交 导航流程。目标输入框可以使用“发送”进行编辑 一旦存在有效的草稿和模型,控制就启用;较早的 项目的后台转动不能使其禁用。提示被接受 第一次发送尝试时。
- 链接规格:
04-ux/09-interaction-patterns.md(§1.6),04-ux/08-component-spec.md(§11.4) - 接受:C(项目会话创建和发送准备)
- 里程碑:M2
- 状态:覆盖源级回归(
app-store-sidebar.test.mjs); 完整的 UI 场景草稿
对话顶部栏
E2E-087:对话顶部栏在聊天路径上呈现
- 先决条件:已配置提供商;至少存在一个会话。
- 步骤: 1) 打开聊天路径。 2) 检查顶部的 46px 栏 交谈区。 3) 确认其显示简洁的 session/task 标题和新任务/搜索/命令 动作按钮;确认 侧边栏切换仅在侧边栏折叠时出现(展开时, 侧边栏拥有该控件)。 4)切换到Pull requests,Scheduled, 插件或设置路由并检查同一顶部区域。
- 预期:在聊天路径上,对话顶部栏仅显示标题和操作; 它没有模型或 Agent|Plan|Goal 模式控制。左侧输入 Composer 芯片拥有 活动会话的 Agent/Plan/Goal 开关,Composer 右侧组合芯片拥有模型和推理选择。的 任务标题是唯一可见的标题文本,使用可用宽度,仅在溢出时显示省略号。 在宽窄布局、侧边栏展开/折叠及工作面板打开/关闭时,检查超过 10 个字符的 英文标题,确保标题不会覆盖操作按钮。完整标题和项目范围可通过工具提示获得。紧凑型 运行时出现状态点。侧边栏 切换仅在折叠状态下存在(没有 侧边栏控件的重复)。在所有其他路线上均采用无框拖曳 band 会改为渲染(没有顶栏控件)。该栏可拖动以移动 窗户;交互式控件不会启动窗口拖动。 macOS 仅在侧边栏折叠时为交通灯保留左侧 88px 的空间 (全屏 8 像素); Windows/Linux 将右侧 112px 留空 本机窗口控件。
- 链接规格:
04-ux/08-component-spec.md(§2 顶栏) - 验收:C (send/UI),质量
- 里程碑:M2
- 状态:草案
E2E-087a:目的页面标头在 macOS 上避开标题栏带
- 先决条件:macOS 构建;至少安装一个插件。
- 步骤:1) 在默认窗口尺寸下打开插件路由。 2) 检查页面顶部: “插件”标题行、主操作按钮和溢出菜单按钮。 3) 将页面滚到顶部, 确认没有内容被 46px 带遮挡。 4) 在定时任务和合并请求路由上重复。 5) 打开某个插件的详情侧面板并检查其标头。
- 预期:页面标头在 macOS 上完整渲染在无框拖拽带下方,与 Windows/Linux 一致:标题行不被裁切,已安装 / 插件市场分段控件和 搜索框位于预期偏移处,而不是贴在窗口顶边。
.page-frame在 darwin、 win32、linux 上都保留--ds-toolbar-height加 8px 缓冲。插件详情侧面板 叠在带之上(z-index: 60),其标头保持在顶边,关闭按钮退出拖拽矩形。 - 链接规格:
04-ux/08-component-spec.md(§2.3 布局) - 验收:C (UI),质量
- 里程碑:M2
- 状态:已覆盖源级回归测试 (
apps/desktop/test/plugins-page-style.test.mjs);完整 UI 场景为草案
E2E-088:Composer Agent/Plan/Goal 芯片更新会话
- 先决条件:聊天路线激活;选定的会话。
- 步骤: 1) 单击左侧输入的 Composer 模式芯片,进入 Plan。 2) 发送通常需要 Write/Edit 的提示并观察行为。 3) 单击同一 Composer 芯片返回 Agent。 4) 开始回合并尝试 在运行中或待处理的 Plan 批准可见时切换模式。
- 预期:Composer 芯片更新活动会话
mode(Plan 和 Goal 硬否认 Write/Edit 和插件工具,而 Bash 遵循所选的 权限模式; Agent 根据权限设置允许其正常工具)。的 当存在轮流或活动待批准时芯片被禁用并重新启用 会话返回 idle/planning 后。不呈现顶栏模式控件。 - 链接规格:
04-ux/08-component-spec.md(§2,§11),03-runtime/03-tools-and-permissions.md(§10),03-runtime/04-data-storage.md(§8) - 验收:C、E
- 里程碑:M2
- 状态:草案
E2E-088a:Composer 配置控件在 project/session 初始化后仍然存在
- 先决条件:已配置提供商;新项目或新会话流程是 当目标
activeSessionId仍在解析时可见。 - 步骤:1) 检查 Composer 模式、Thinking 和权限控制 在 empty/home 转换期间。 2)点击模式控制并确认 前进到下一个模式。 3) 打开Thinking并选择支持的级别。 4)打开权限模式,选择自动。 5) 检查目标会话 导航完成后。
- 预期:没有任何空闲配置触发器被仅仅禁用 因为目的地会话尚未预测。第一个 配置操作创建或重用目标草稿,保留 选择值,并且不需要第二次单击。跑步回合和 待批准仍会禁用控件。在该过渡背后,聊天区遵循 冷切换规则:当前可见的面板继续显示它自己的会话直到目标 提交,唯一的等待可供性是那条细进度轨道,没有任何内容被调暗, 并且在可见面板成为活动会话之前提示提交保持惰性,因此提示 无法发往正在离开的会话。
- 链接规格:
04-ux/08-component-spec.md(§11),04-ux/09-interaction-patterns.md(§5A)、ADR 0137 - 接受:C(新 project/session 输入框)
- 里程碑:M2
- 状态:草案
E2E-088b:Composer 占位提示遵循页面和会话上下文
- 先决条件:英文和简体中文语言环境可用;已配置提供商;可以打开空首页和两个会话。
- 步骤:1)在空首页记录欢迎占位符,并等待超过 4 秒确认其不变。2)打开会话 A,记录提示, 输入并清空文本、聚焦并失焦文本区域后等待,确认文案不变。3)切换到会话 B,再切回 A, 分别记录提示变化。4)在首页和会话间切换,检查命令/文件和快捷键提示。5)输入
/并检查斜杠菜单,包含英文/中文长描述、短描述、无描述、 独立标题和参数提示以及超长斜杠名称的 Skill;再检查@模式中的长文件名。 在 1040px 和 1680px 视口、320px 和 640px 输入框宽度下重复。 6)切换到 zh-CN,重复上下文切换检查。 - 预期:首次渲染的上下文从欢迎语开始,在页面/会话上下文变化前保持不变。每次上下文切换才推进到 下一条本地化命令/文件或快捷键提示,并使用透明度渐变;不存在计时器驱动的变化。快捷键提示包含 Shift+Enter 和提交提示,命令/文件提示包含
/和@。斜杠菜单仍包含/new、/compact、/agent-mode、/plan-mode和/goal-mode。zh-CN 显示对应文案,包括Shift+Enter 换行 · 点击发送提交。 长描述只使用命令名称和提示之后的剩余空间,短名称始终完整可见。描述和超长名称 在行内省略,不产生横向溢出;文件名仍使用可用行宽。名称高亮、点击接受及输入框 保持焦点的行为不变。 - 链接规格:
04-ux/08-component-spec.md(§11)、04-ux/04-builtin-commands.md(§7) - 验收:C(发送/UI)、本地化、质量
- 里程碑:M2
- 状态:源代码覆盖(
composer-placeholder-context.test.mjs);斜杠菜单布局由pnpm build:js后运行pnpm test:e2e:composer-autocomplete覆盖(真实 React/Chromium、生产 CSS、 确定性命令夹具,无需提供商)。完整提供商/会话场景仍为草案;分支执行不替代合并后 E2E
E2E-089:Composer 模型菜单向上打开并切换模型
- 先决条件:聊天路线激活;提供商已配置。
- 步骤:1) 单击 Composer 右侧的模型 × 推理芯片。2)确认菜单 从底部输入框向上打开。3)进入“模型”并选择不同的 provider/model, 返回根菜单。4)进入“推理级别”并选择支持的级别。5)从命令面板 或应用程序菜单打开设置。
- 预期:根菜单仅显示“模型”和“推理级别”;“模型”子菜单列出 已启用、可运行提供程序中保存的模型绑定,并将每个模型行缩进显示在其提供商 标题下方。“推理级别”子菜单仅列出所选模型发布的级别。缓存或实时发现结果 可以为已保存绑定提供显示名称和元数据,但未配置的发现结果不会出现;发现 不可用时仍可选择已配置 ID。选择会话模型/推理配置后菜单保持打开并返回根菜单; 设置从命令面板/菜单打开。Composer 模型触发器会省略长 ID。
- 链接规格:
04-ux/08-component-spec.md(§11,模型菜单),03-runtime/13-model-catalog-and-selection.md - 接受:C
- 里程碑:M2
- 状态:草案
E2E-090:转录底部保留跟踪停靠的输入框高度
- 先决条件:聊天路线激活;会议记录 超出一个视口,因此最后一条消息位于停靠的编辑器附近。
- 步骤: 1) 将脚本滚动到最新消息。 2) 测量 最后一条消息与停靠的编辑器顶部之间的垂直间隙。 3) 在编辑器中输入几行,这样草稿就会变成多行。 4) 重新测量间隙并确认上面最后一条消息仍然完全可见 输入框(不重叠)。 5) 将草稿折叠回一行并 确认差距缩小至约 16 像素储备。
- 预期:最后一条消息位于输入框上方(一个小的, 一致的差距)而不是远远低于它;储备遵循输入框的 通过
--composer-dock-height获得真实高度,因此更高的多线草稿会推动 将成绩单向上而不是覆盖它。跳转到最新按钮和 小地图在每个草稿高度都固定在输入框上方。 - 链接规格:
04-ux/08-component-spec.md(§4.3 MainChat 布局) - 验收:C (send/UI),质量
- 里程碑:M2
- 状态:草案
E2E-144:发送提示使成绩单保持在最新回合
- 先决条件:聊天路线激活;所选会话包含足够的 历史记录溢出记录视口;成绩单是最晚的 消息或已向上滚动。
- 步骤:1) 发送提示。 2)观察第一次发送的记录 通过持久化的用户消息事件和第一个流式传输的行来确定状态。 3) 发送前手动向上滚动后重复此操作。
- 预期:发送立即隐藏跳转控件并重新固定 布局阶段的转录。历史行仅向上移动 添加新回合;视口永远不会闪烁到对话的顶部, 新用户回合以及流式响应仍然在底部可见。
- 链接规格:
04-ux/08-component-spec.md(§4.3,§4.4),04-ux/09-interaction-patterns.md(§9.1,§10.4) - 验收:C (send/UI),质量
- 里程碑:M2
- 状态:草案
工作区打开
E2E-012:打开项目目录
- 前提条件:应用程序运行;没有项目开放。
- 步骤: 1) 通过 UI 打开项目目录。 2) 选择本地文件夹。
- 预期:显示项目路径;工具路径相对于项目根目录进行解析。
- 规格链接:
03-runtime/15-workspace-ignore-rules.md - 接受:D(打开项目,显示路径)
- 里程碑:M3
- 状态:草案
E2E-013:只读工具在项目中工作
- 前提条件:项目目录打开。
- 步骤: 1) 要求代理读取项目中的文件。 2)观察结果。
- 预期:
Read在项目范围内立即返回。在 Agent 模式下, 代理在使用之前通过ToolSearch激活Glob或Grep; Plan 在第一次请求时保持其 read/search 内核可用。所有结果 保持在项目范围内。 - 链接规格:
03-runtime/03-tools-and-permissions.md - 验收:E(Read/Glob/Grep 工作),D(基于项目的工具)
- 里程碑:M3
- 状态:自动化(协议烟雾:示例项目中的 Read + Glob)
权限允许/拒绝/超时
E2E-014:Write/Edit/Bash 触发权限卡
- 先决条件:Agent 模式;项目开放。
- 步骤: 1) 要求代理写入文件。 2)遵守许可卡。
- 预期:许可卡内联显示在原始成绩单中 包含工具名称、工作区、参数预览、倒计时和 allow/deny 选项。它不创建背景或模式,也不覆盖其他会话。
- 链接规格:
04-ux/03-permission-ux.md、03-runtime/03-tools-and-permissions.md - 接受:E(Write/Edit/Bash 触发确认)
- 里程碑:M3
- 状态:草案
E2E-015:拒绝权限阻止执行
- 前提条件:显示许可卡。
- 步骤: 1) 单击权限卡上的“拒绝”。 2) 观察座席反应。
- 预期:工具未执行;代理人收到拒绝结果;没有更改文件。
- 链接规格:
03-runtime/03-tools-and-permissions.md - 接受:E(拒绝→不执行)
- 里程碑:M3
- 状态:草案
E2E-016:允许的权限执行工具
- 前提条件:显示许可卡。
- 步骤: 1) 单击权限卡上的“允许”。 2) 观察代理响应和 UI。
- 预期:工具已执行;结果返回到模型并显示在UI中;文件已修改。
- 链接规格:
03-runtime/03-tools-and-permissions.md - 接受:E(允许→结果返回)
- 里程碑:M3
- 状态:草案
E2E-017:权限超时默认为拒绝
- 前提条件:显示许可卡;没有用户操作。
- 步骤: 1) 等待 120 秒而不响应权限卡。 2)观察结果。
- 预期:超时后自动拒绝权限;工具未执行。
- 链接规格:
03-runtime/03-tools-and-permissions.md - 接受:E(超时→拒绝)
- 里程碑:M3
- 状态:草案
E2E-018:Plan 否认工作区突变和插件工具
- 先决条件:Plan 模式处于活动状态,选择“自动”并注册插件代理工具。
- 步骤: 1) 要求 Agent 调用 Write、Edit 和插件工具。 2)询问 运行创建标记文件的 Bash 命令。 3) 重复 Bash 调用 选择询问并检查许可卡。
- 预期:写入、编辑和插件工具不可见且不直接 尝试返回
WRITE_DISABLED_IN_PLAN、EDIT_DISABLED_IN_PLAN或PLUGIN_DISABLED_IN_PLAN;这些工具不会更改任何文件。重击运行 未经自动确认,可能会发生变异;在“询问”下等待 普通许可卡。不存在聊天模式错误或命令。 - 链接规格:
03-runtime/03-tools-and-permissions.md - 接受:E(Plan 政策)
- 里程碑:M3
- 状态:已记录(M6;未请求 E2E 执行)
E2E-019:工作空间外部路径遵循权限模式
- 先决条件:Agent 或 Plan 模式;项目开放;存在可读文件 在会话项目和暂存根之外。
- 步骤:1) 选择“询问”后,要求客服人员
Read外部文件并 观察内联许可卡。 2) 拒绝一次并确认没有内容 回来了。 3)重复并允许一次;验证工具结果携带root: "external"和规范的绝对路径。 4) 切换到自动和 重复Grep或Glob;验证没有卡片出现以及有界结果 返回。 5) 重复接受编辑;仍然验证外部 read/search 请求许可。 - 预期:在用户可以之前,显式外部路径永远不会发生硬故障 决定。询问并接受编辑请求权限;自动执行。否认, 超时或取消返回
TOOL_DENIED并且不执行任何操作。 - 链接规格:
03-runtime/03-tools-and-permissions.md,03-runtime/15-workspace-ignore-rules.md、04-ux/03-permission-ux.md - 接受:E(工作空间外部权限策略)
- 里程碑:M3
- 状态:自动(host-core protocol/unit 覆盖范围;桌面旅程待定)
E2E-019e:有界搜索参数在平台之间保持可移植性
- 先决条件:Agent 或 Plan 模式;项目开放;主机工具目录是 可在 macOS、Linux 或 Windows 上使用。
- 步骤:1) 延迟时激活 Agent/Plan 并检查其架构。 2) 使用工作区相关的
path、include、headLimit进行搜索outputMode: "filesWithMatches"或"count",首先是一个目录,然后 然后是一个显式文件。 3) 使用目录调用Read并遵循其 结构化的全局建议。 4) 使用平台的本机 shell 重复此操作 选择,而不更改工具参数。 - 预期:模式在每个 平台;
Read声明仅文件输入,Glob声明目录输入,Grep接受文件或目录。filesWithMatches被接受为 规范输出模式。目录读取返回INVALID_ARGUMENTsuggestedTool=Glob和有界参数;更正后的调用成功。搜索 结果使用项目内工作区相对路径和绝对路径 仅适用于经批准的外部地点。没有特定于 shell 的路径语法 所需的和超大的结果仍然有限;工作区相对路径使用/表示平台分隔符, POSIX 文件名中的字面量反斜杠保持不变。 - 链接规格:
03-runtime/03-tools-and-permissions.md,03-runtime/16-tool-result-limits.md、ADR 0057、ADR 0069 - 接受:E(有界跨平台搜索)
- 里程碑:M5
- 状态:单元覆盖(host-core 和代理运行时);多平台直播 协议捕获待处理。工作区相对路径这一预期在 Windows 上由
relative_display覆盖:它必须用解析器自身的拼写 (simple_canonicalize)规范化工作区根目录——标准库Path::canonicalize在 Windows 上会保留\\?\前缀,从而把所有标签 静默降级为绝对路径。
E2E-019a:暂存目录写入不在工作区中 (D114)
- 先决条件:Agent 模式;项目开放;会议开始。
- 步骤: 1) 要求代理生成 temporary/intermediate 文件(例如一次性脚本)。 2)观察其书写位置以及是否出现权限卡。 3) 检查
git status和工作面板状态。 4) 删除会话并检查<data_dir>/scratch/。 - 预期:文件在没有权限卡的情况下登陆
<data_dir>/scratch/<sessionId>/下;git status项目保持干净;没有文件或查看工件选项卡打开用于临时写入;删除会话会删除临时目录。 - 链接规格:
03-runtime/03-tools-and-permissions.md §4b、03-runtime/04-data-storage.md - 接受:E(与工作区隔离的临时文件)
- 里程碑:M5
- 状态:部分自动化(host-core 单元测试:双根解析、暂存 write/read、PI_SCRATCH_DIR、扫描)
E2E-019b:划痕遏制与工作空间防御相匹配 (D114)
- 先决条件:Agent 模式;项目开放。
- 步骤:1) 尝试使用
..从头开始遍历进行写入。 2)尝试通过植入内部指向外部的符号链接进行写入。 3) 在 Plan 中尝试相同的写入调用。 - 预期:两次转义均返回
PATH_OUTSIDE_WORKSPACE; Plan 返回WRITE_DISABLED_IN_PLAN在任何临时路径之前都可以使写入可用。 - 链接规格:
03-runtime/03-tools-and-permissions.md §4b - 接受:E(scratch root 无法逃脱)
- 里程碑:M5
- 状态:自动化(host-core 单元测试)
E2E-019c:权限模式管理高风险批准 (D115/D132)
- 先决条件:Agent 模式;项目开放;全局默认
ask。 - 步骤: 1) 使用新继承的会话和全局默认“每次询问”,打开编写器菜单 - 期望“每次询问”都被选中,且没有 global-default/inherit 标签 - 然后要求 Agent 写入工作区文件并期望获得权限卡。 2) 将会话芯片切换为接受编辑;重复一下——预计 Write/Edit 不会出现任何卡,但 Bash 仍然会出现卡。 3) 切换到“自动”——预计 Bash 也不会卡。 4) 在设置中将全局默认值设置为接受编辑后创建另一个继承会话 - 期望输入框芯片和菜单选择直接显示接受编辑并且自动允许 Write/Edit。 5) 将会话切换到 Plan,然后切换到 Goal,并设置自动 — 预计 Write/Edit/plugin 会被拒绝,但 Bash 会允许而无需确认。
- 预期:有效模式=会话覆盖→全局默认→询问; Plan 和 Goal Write/Edit/plugin 硬否认超越每种模式,而他们的 Bash 遵循所选模式;输入框芯片和菜单始终显示有效模式,无需 default/inherit 出处。
- 链接规格:
03-runtime/03-tools-and-permissions.md §6、03-runtime/04-data-storage.md、08-meta/decisions-log.md(D115/D132) - 接受:E(权限模式解析并强制主机端)
- 里程碑:M5
- 状态:部分自动化(host-core 单元测试:评估矩阵、Plan 策略优先级、询问下的会话授权;渲染器源测试:仅有效的输入框选项和选择)
E2E-019d:Bash 工具查看用户的登录 shell 工具链 (D181)
- 先决条件:Agent 模式;项目开放;操作系统用户有一个登录 shell (macOS 上的默认设置)其配置文件至少导出一个不在应用程序上的工具 最小 GUI 路径(例如 nvm/Homebrew)。
- 步骤:1) 要求代理打印
$PATH并运行工具链检查,例如 作为command -v node && node -v。 2) 与新终端的 PATH 进行比较 显示同一用户。 3) 可以选择暂时删除~/.bash_profile并在只有.zshrc初始化工具链的机器上重复。 - 预期:Bash 工具解析用户自己的登录 shell 的工具 即使命令通过 bash 运行,也会导出(nvm、pnpm、Homebrew);的 探测到的登录路径是有效子路径的子集(bash 配置文件可能 prepend/dedupe)。丢失或楔入的用户 shell 会降级到主机 PATH 不会使工具失效。
- 链接规格:
03-runtime/03-tools-and-permissions.md §5、08-meta/decisions-log.md(D181)、ADR 0045 - 接受:E(Bash 工具中可见的用户工具链)
- 里程碑:M5
- 状态:自动化(host-core 单元测试:登录路径探测 + 子路径注入)
会话持续性
E2E-020:会话在重新启动后仍然存在
- 先决条件:存在具有消息历史记录的会话。
- 步骤:1) 退出应用程序。 2)重新启动。 3) 打开会话列表。
- 预期:出现上一个会话;消息可恢复。
- 链接规格:
03-runtime/04-data-storage.md、03-runtime/10-session-state-machine.md - 接受:F(会话在重新启动后仍然存在)
- 里程碑:M2
- 状态:自动(协议烟雾:主机级持久性;完全重启通道手动)
E2E-021:删除会话作品
- 先决条件:会话存在。
- 步骤:1) 删除会话。 2) 观察会话列表。
- 预期:会话已从列表中删除;数据不见了。
- 链接规格:
03-runtime/04-data-storage.md - 接受:F(删除会话)
- 里程碑:M2
- 状态:草案
E2E-036:本地化导入分组开始折叠
- 先决条件:支持的本地代理存储包含跨至少两个项目路径和两个源的可导入会话,包括一个没有项目路径的会话;该应用程序可以使用英语系统区域设置启动一次,也可以使用简体中文系统区域设置启动一次。
- 步骤: 1) 以英文启动并打开“设置”→“导入”。 2) 扫描会话。 3) 检查初始源组。 4) 展开一个组并选择一个会话。 5) 将分组依据更改为项目路径。 6) 切换回源。 7) 使用简体中文系统区域设置启动后重复该流程。
- 预期:来源/来源是初始分组;扫描后以及任一分组更改后,所有组都会折叠;项目路径模式显示准确的项目路径和最终的“无项目/未关联项目组”;一组的扩张导致其他组的崩溃;选定的会话在分组更改中保持选定状态;计数、日期、选择标签、可访问名称和导入结果使用活动区域设置,无需原始键或未解析的双括号占位符。
- 链接规格:
04-ux/01-ui-ia.md、04-ux/02-i18n-english-first.md、04-ux/08-component-spec.md - 接受:F(会话导入审核)
- 里程碑:M2
- 状态:草案
E2E-037:导入创建持久的项目条目
- 前提条件:导入候选者包括两个路径 A 的会话、一个路径 B 的会话和一个没有项目路径的会话;这两个项目都不是活动工作区。
- 步骤:1) 导入所有候选。 2) 打开设置→项目存档。 3) 检查并扩展路径 A 和 B。 4) 返回家中并检查临时会话。 5) 重复导入。
- 预期:项目存档恰好包含 A 的一行和 B 的一行;匹配的导入会话出现在其确切的项目行下;无路径会话仅出现在临时会话下;活动工作区不会改变;重复导入既不会重复会话,也不会重复项目行;磁盘上不会创建丢失的文件系统路径。
- 链接规格:
03-runtime/04-data-storage.md、04-ux/01-ui-ia.md、04-ux/08-component-spec.md - 接受:F(session/project 持久性)
- 里程碑:M2
- 状态:草案
E2E-038:设置拥有项目存档目标
- 先决条件:应用程序运行时至少有一个已配置的提供程序、一个受支持的本地会话存储、一个保留的项目和一个存档的项目。
- 步骤:1) 打开设置。 2) 检查完整的设置导轨。 3) 打开Basics 并使用可搜索主题选择器更改其外观卡中的主题。 4) 打开全局 AI 并检查权限和默认项卡,包括命令 Shell 行;确认上下文管理没有设置卡。 5) 打开快捷方式并检查键盘快捷方式卡。 6) 打开指令并保存全局指令。 7) 打开模型配置并检查提供商工作室。 8) 按顺序打开“导入”、“项目存档”和“信息”。 9) 搜索设置“项目”或“存档”。 10) 在项目存档中,将每个分组条的计数与其呈现的行进行比较。 11) 将排序控件从“最近”切换到“名称”。 12) 搜索已知的会话标题,检查其展开的项目行,然后显示八个以上的会话;通过清晰的功能来清除搜索。 13) 打开行菜单,使用 Escape 并按下外部按钮将其关闭。 14) 恢复存档的项目,然后激活它。 15) 归档一个项目会话,从项目归档中打开它,然后返回项目归档。 16) 返回应用程序外壳并打开插件。
- 预期:该导轨按顺序包含基础知识、全局 AI/AI、快捷方式、说明、模型配置、导入、项目存档和信息,每个都有其语义 Lucide 图标(滑块/火花/键盘/文件文本/机器人/下载/存档/信息);行按四个柔和的非交互标题分组:Personal / 个人包含基础知识、全局 AI 和快捷键,Agent / 智能体包含说明和模型配置,Workspace / 工作区包含导入和项目存档,About / 关于包含信息;分组之间使用留白而不是分割线,搜索保持目的地结果扁平,并将空分组及其标题一起隐藏。外观保留在基础下,权限、默认项和命令 Shell 行位于全局 AI 下;可用的已选 Shell 由选择器表示,不重复显示“已配置”状态;默认、回退和无实际 Shell 状态仍明确展示;上下文管理没有设置卡;键盘快捷键和全局指令都有自己的目的地;开发者生活在 Info 下;项目存档显示活动、关闭和存档的持久行,无需可见性切换,将它们分组在一个面板内始终可见的固定/所有项目/存档分组条 (D168/D267) 下,并带有每个部分的计数。该目的地不呈现英雄区块,也没有页面级计数器串:引导区是一条安静的说明行,每个分组条的计数与其呈现的行一致;按名称排序会重新排序每个部分内的行,而不隐藏任何行;搜索匹配项目字段和会话标题并报告匹配计数,会话标题结果扩展其所属项目,按最新活动和相对更新时间列出会话,并以八个批次显示历史记录;清除搜索将恢复完整索引。行菜单在 Escape 和外部按下时关闭。恢复使存档保持打开状态,激活返回以与侧栏中保留的恢复项目聊天。打开归档会话时,应用会先成功选择该会话,再清除它的归档状态;聊天页选中该会话,项目侧边栏重新显示它,返回项目归档后它也不再处于归档状态。主页侧边栏和全局页面结果没有独立的项目目标;设置搜索找到项目存档;插件仍然是一个独立的应用程序外壳目的地。
- 链接规格:
04-ux/06-settings-ia.md、04-ux/01-ui-ia.md、03-runtime/11-provider-model-system.md - 接受:B(模型配置),F(会话导入)
- 里程碑:M4
- 状态:单位覆盖(
settings-project-archive.test.mjs、sidebar-navigation.test.mjs);渲染场景草稿
E2E-091:外观卡通过可搜索主题和语言选择器选择
- 先决条件:应用程序在 macOS 上运行;测试工具可以切换英语、简体中文、繁体中文、土耳其语、德语、西班牙语和法语系统区域设置。
- 步骤:
- 打开设置 → 常规。
- 打开外观卡中的主题选择器。确认系统、浅色、深色钉在顶部;选择深色并确认触发器显示「深色」,且 UI 切换为深色。
- 选择浅色并确认 UI 切换为浅色。
- 打开语言行的可搜索选择器。确认「跟随系统」钉在顶部并显示检测到的本地名称,且列表按本地名称列出 English、简体中文、繁體中文、Türkçe、Deutsch、Español、Français。操作系统为简体中文时选择「跟随系统」会应用简体中文;切换为繁体中文系统时会应用繁体中文。
- 依次选择 English、简体中文、繁體中文、Türkçe、Deutsch、Español 和 Français,确认外壳文案无需重新加载即可切换。确认
zh-Hant和zh-HK解析为繁体中文,de-DE解析为 Deutsch,es-MX解析为 Español,fr-CA解析为 Français。 - 在语言搜索中输入本地名称或英文名称,确认不匹配的语言消失。在主题搜索中输入主题名称,确认不匹配的选项消失。
- 预期:主题和语言都是可搜索的选择行(不是卡片网格,也不是原生 select);关闭时的触发器按当前文案收缩、不超过设置控件列且不溢出该行。主题列出系统、浅色、深色,插件主题在分隔线之后。「跟随系统」通过主进程 (
app.getLocale()) 解析操作系统区域设置,安全地通过沙盒 preload 桥传递,并在菜单内嵌显示检测到的本地名称;zh-TW、土耳其语、德语、西班牙语和法语都是包含发版日志文案在内的完整外壳目录;切换选项会立即更新 UI,无需重新加载。 - 链接规格:
04-ux/06-settings-ia.md、04-ux/02-i18n-english-first.md - 验收:A(核心壳)、H(本地化)
- 里程碑:M4
- 状态:已记录
E2E-SETTINGS-ai-tab-pickers-use-in-app-menus:全局 AI 下拉使用应用内菜单
- 先决条件:应用正在运行,主机至少报告一个已配置的命令 Shell,以及至少一个在当前平台不可用的目录内 Shell。
- 步骤:
- 打开设置 → 常规,展开主题和语言选择器,记下药丸触发器与展开后的表面。
- 打开全局 AI。
- 展开权限卡的权限模式控件,依次选择询问、接受编辑、自动。
- 展开默认项卡的命令 Shell 控件;查看不可用条目并选择一个可用的 Shell。
- 先用 Escape、再用外部点击分别关闭打开的菜单。
- 关闭设置后重新打开,读取这两行。
- 预期:两行都展开与外观选择器相同的锚定菜单表面 —— 应用绘制的边框,使用共享的圆角、阴影、描边和主题 token,当前选项带勾选标记,并有悬浮/键盘高亮 —— 而不是平台绘制的
<select>弹层。关闭时的触发器按当前文案收缩,不超过设置控件列。不可用的 Shell 仍会列出并带后缀,不可选择,也不会成为当前值。Escape 和外部点击都会关闭菜单并把焦点还给触发器;方向键在可选选项之间循环移动。所选权限模式和命令 Shell 在关闭并重新打开设置后保持,且选定的 Shell 仍是唯一的配置状态提示。 - 链接规格:
04-ux/06-settings-ia.md、04-ux/09-interaction-patterns.md - 验收:A(核心壳)
- 里程碑:M4
- 状态:已记录
E2E-039:设置标题栏拖动移动窗口
- 先决条件:应用程序在 macOS 上以窗口方式运行,并且打开“设置”。
- 步骤: 1) 记录窗口位置。 2) 将空的 46 像素带拖动到设置栏上方。 3) 将同一条带拖动到内容窗格上方。 4) 使用后退、搜索、项目存档和导航控件。
- 预期:顶带拖动会移动本机窗口;返回、搜索和导航保持交互,并且从不启动窗口拖动。
- 链接规格:
04-ux/06-settings-ia.md、04-ux/01-ui-ia.md - 验收:质量(关键操作感觉很精致)
- 里程碑:M5
- 状态:草案
E2E-043:设置内容跟随窗口宽度
- 先决条件:应用程序在 macOS 上以窗口方式运行,并且打开“设置”。
- 步骤: 1) 以默认窗口宽度打开Basics,并记录内容卡宽度。 2) 将窗口扩展到 1600px 宽。 3) 打开模型配置、导入和项目存档。 4) 将窗口缩小到支持的最小 1040px。
- 预期:右侧内容卡在每个测试宽度上随可用窗格展开和收缩; 275px 导轨和窗格排水沟保持稳定;控件保持可见,无需剪切或水平页面滚动。
- 链接规格:
04-ux/06-settings-ia.md、04-ux/07-ui-design-system.md - 验收:质量(关键操作感觉很精致)
- 里程碑:M5
- 状态:单位覆盖(
settings-responsive-layout.test.mjs);场景已记录
E2E-040:嵌套工具活动在转录重新加载后仍然存在
- 先决条件:已配置提供商并打开项目;回合可以包含进度文字、多个搜索/工具/思考 条目,以及失败或被拒工具。
- 步骤:1) 在详细模式流式输出进度段落 A、多项搜索片段、进度段落 B 和多项命令 片段。2) 检查已展开的整体过程和活动组;在更多输出到达时手动收起活动组。3) 完成 回合并比较未操作与用户接管的组。4) 展开一个已完成组和其中一个条目,复制输出, 收起并重新展开该组,再独立打开同级组。5) 分别检查单项片段,以及最后一个活动组的 字面最后一项为工具/搜索、思考、失败工具和被拒工具。6) 切换到紧凑模式,检查活动 思考、失败后恢复及完成状态。7) 在保留窗格内重新挂载行,然后重载渲染器并重新打开 会话。
- 预期:两种模式都使用一个整体过程披露,末尾回答位于其外。详细模式的活动中和 已完成过程默认展开;活动多项组展开,并且只在用户未操作时于完成后收起。紧凑模式 的过程、组和所有载荷默认收起,推理正文隐藏;记录过失败/被拒工具的未操作活动过程 会在后续恢复期间保持展开。单项没有组包装。详细模式只在最后一个活动组的字面最后 一项是符合条件的工具/搜索时自动展开载荷;不会越过思考向前查找,失败/被拒保护会 保持叶子关闭。父级、子级和同级选择彼此独立;收起并重新展开父级会保留下级状态, 流式更新和完成不会覆盖用户接管的选择。保留窗格中的重新挂载保留选择;渲染器重启 重新应用默认值,但工具名称、参数、结果和状态仍会恢复。组/过程标题显示有界运行和 问题摘要,不会把整个回合标记为失败。
- 链接规格:
04-ux/01-ui-ia.md、04-ux/07-ui-design-system.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md - 验收:C(聊天流)、E(工具)、F(持久性)
- 里程碑:M3
- 状态:草案。2026-09-20 的嵌套披露变更仅把本场景作为静态源码/设计审查的 预期行为;该范围不新增或运行单元、组件、集成、浏览器、Electron 或 E2E 测试。
E2E-041:对话小地图导航长记录
- 先决条件:会话包含足够的用户和助理轮流 滚动超出一个视口并密集填充小地图,包括一个 AI 围绕工具活动作为多个辅助片段发出的响应;一个 第二个会话至少有两个符合条件的回合标记,并且仍适合其中一个 视口。
- 步骤: 1) 打开长会话。 2) 滚动浏览文字记录并 观察活动的小地图标记。 3) 将鼠标悬停在标记上并检查其预览。 4) 使用键盘焦点到达另一个标记。 5) 激活标记。 6) 打开一个 少于两个合格回合标记的会话。 7) 打开 仍然适合一个视口的多消息会话。 8) 调整长会话的大小 窗口变高,直到内容不再溢出,然后再次变短。 9) 在一个 窗口高度短,检查并激活窗口附近的第一个和最后一个标记 小地图的垂直边界,包括面向标题栏的一侧。
- 预期:导轨包含每个可见用户回合一个标记和每个可见用户回合一个标记 人工智能响应。两个用户消息之间的多个助手片段共享一个 单个标记和组合有界预览,而仅工具行不会创建 标记并且不拆分响应。靠近上三分之一读数的标记 锚点暴露
aria-current;悬停和焦点显示相同的本地化发件人 和预览;附近的标记水平放大而不移动堆栈; 激活平滑地滚动到该响应中的第一条内容丰富的消息; 当存在少于两个合格标记时,导轨不存在或当 内容不会溢出一个视口;一旦溢出,导轨就会重新出现 调整大小后返回。密集标记保持在无障碍区域的中心 46px 标题栏和停靠的编辑器之间的跨度,均匀压缩,以及 无需进入本机窗口拖动区域即可保持交互。 - 链接规格:
04-ux/08-component-spec.md - 验收:C(聊天流)、质量(键盘和长线程导航)
- 里程碑:M3
- 状态:草案
E2E-042:v7 之前的版本通过中断重置存储档案;记录存在于会话文件中
- 先决条件:夹具数据目录包含一个
pi.sqlite,其PRAGMA user_version介于 1 和 6 之间(D119 之前的内容数据库模式) 具有代表性的行。 - 步骤: 1) 针对灯具启动 host-core。 2)创建一个会话并 通过主机 RPC 附加消息。 3) 停止并重新启动 host-core。 4) 重新加载 通过 RPC 进行会话并检查数据目录。
- 预期:主机核心将旧文件重命名为 1
pi.sqlite.v6.bak,引导一个新的 schema-v7 数据库(仅索引messages),使用会话头行加上sessions/<id>.jsonl来写入 每条消息一行,重新启动后从文件中重新加载记录 具有相同的逻辑结果,并且删除会话会同时删除 索引行和会话文件。没有 Electron 拥有的持久性文件 权威的。 - 链接规格:
03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md、ADR 0014 - 接受:F(持久性),H(重置失败是可诊断的)
- 里程碑:M2
- 状态:单位覆盖(
db::tests::archives_pre_v7_database_and_starts_fresh,sessions::tests::transcript_survives_reopen_from_file,sessions::tests::delete_session_removes_transcript_files);完整的夹具 场景草稿
插件加载/命令/禁用
E2E-022:加载本地插件
- 前提条件:应用程序运行;本地路径提供示例插件。
- 步骤:1) 从侧边栏页脚插件图标打开扩展。 2) 从标题溢出菜单中选择加载本地插件。 3) 使用行开关启用它。
- 预期:插件加载;清单已验证;捐款登记;该行显示在带有本地标记的“活动”下。
- 链接规格:
07-plugins/01-plugin-system.md、07-plugins/05-plugin-lifecycle.md - 接受:G(加载本地插件)
- 里程碑:M4
- 状态:自动(协议烟雾:plugins.loadDev)
E2E-022A:从模板创建插件
- 前提条件:应用程序运行;一个可用的空文件夹。
- 步骤:1) 打开扩展。 2) 在标题溢出菜单中选择从模板新建插件(或使用空状态按钮)。 3) 依次选择四个模板并阅读其说明。 4) 选择文件夹。 5) 第二次尝试时取消文件夹选取器。
- 预期:选取器准确列出
panel-basic、agent-tool-basic、skill-pack、full-demo,每个值均在活动区域设置中命名和描述;选择一个文件夹写入模板文件,加载插件作为开发插件,刷新列表,然后打开该文件夹作为活动项目 - 应用程序以新文件夹作为工作区进行聊天,它出现在侧边栏项目列表中,并且 toast 上显示“已创建、加载并打开用于开发”;该插件的贡献可以立即使用,并且内置插件开发技能在新工作区中处于活动状态。该技能教授当前全局piAPI(onLoad()加pi.commands.register)和固定window.pluginBridge边界,但不教授已退役的onLoad(pi)/pi.registerCommand形状。取消的文件夹选择器不会改变任何内容,也不会报告任何错误。 - 链接规格:
07-plugins/10-plugin-devex.md、../../plugin-development.md、ADR 0039 - 接受:G(从模板创建插件)
- 里程碑:后 MVP
- 状态:部分自动化(
apps/desktop/test/plugin-template-action.test.mjs:通道、模板 ID 与 devkit 的奇偶校验、区域设置覆盖、取消挑选排序、项目激活); UI 演练已记录
E2E-022B:开发插件热重载
- 先决条件:从本地文件夹加载并启用的插件。
- 步骤: 1) 编辑
main.js以更改命令标题并保存。 2) 一次保存多个文件。 3)引入语法错误并保存。 4) 修正错误并保存。 5) 为manifest.json添加新权限并保存。 6) 单击开发插件卡上的“重新加载”。 7)再次编辑并重新启动应用程序。 - 预期:单次编辑重新加载插件,无需重新选择文件夹,并且命令面板显示新标题;保存突发会产生一次重新加载,而在
dist/或node_modules/下写入不会产生任何结果;语法错误报告重新加载失败,但不会导致应用程序崩溃,并且修复保存会恢复插件;添加的权限拒绝使用PERMISSION_DENIED自动重新加载,而显式重新加载操作会加载已注册的文件夹,刷新权限上限,并报告成功;稍后的编辑使用刷新的上限,并且在重新启动后仍会监视该文件夹。 - 链接规格:
07-plugins/10-plugin-devex.md§7、07-plugins/13-plugin-permissions-matrix.md、ADR 0039、ADR 0075 - 接受:G(热重载),D(未经审查不得扩大权限)
- 里程碑:后 MVP
- 状态:部分自动化(
apps/desktop/test/plugin-hot-reload.test.mjs:反跳、忽略列表、权限上限、恢复、拆卸);手动编辑循环记录
E2E-022C:检查、打包、安装往返
- 先决条件:脚手架插件目录。
- 步骤:1)
pnpm pi-plugin check <dir>。 2) 删除main命名的文件并再次运行check。 3) 恢复它,声明contributes.skills而不声明agent.prompt.inject,然后再次运行check。 4)pnpm pi-plugin pack <dir>。 5) 从插件页面安装生成的.piplug。 6) 要求代理在同一目录上运行PluginCheck和PluginPack。 - 预期:脚手架插件检查干净并报告其文件计数和大小;缺少
main是一个阻止pack的错误; inert-skills 情况是一个不会阻止的警告;pack使用仅存储条目写入dist/<id>-<version>.piplug并打印其 sha256;该软件包通过正常的权限审查进行安装,并显示在“活动”下;代理工具会产生相同的判断并拒绝会话工作区之外的任何目录。 - 链接规格:
07-plugins/10-plugin-devex.md§5–§6、07-plugins/06-plugin-packaging.md、ADR 0039 - 承兑:G(本地包装往返)
- 里程碑:后 MVP
- 状态:部分自动化(
packages/plugin-devkitvitest:脚手架→检查→按模板打包,存储方法标头,每个检查规则);安装步骤已记录
E2E-023:全局搜索中的插件命令并执行
- 前提条件:插件已加载并启用。
- 步骤: 1) 打开全局搜索(Cmd/Ctrl+K 或 Cmd/Ctrl+Shift+P)。 2) 在“命令”部分下找到插件命令。 3)执行。
- 预期:命令出现在全局搜索结果中;执行产生预期结果。
- 链接规格:
07-plugins/09-plugin-command-palette.md - 接受:G(插件命令出现并执行)
- 里程碑:M4
- 状态:草案
E2E-024:插件注册并调用代理工具
- 先决条件:插件已加载;插件声明一个代理工具。
- 步骤: 1) 要求代理使用插件工具。 2) 如果需要,请遵守许可卡。 3)允许。
- 预期:使用强制前缀 (
plugin_<id>_<name>) 注册的工具;调用成功。 - 链接规格:
07-plugins/03-plugin-api.md、07-plugins/13-plugin-permissions-matrix.md - 接受:G(插件代理工具)
- 里程碑:M4
- 状态:自动化(协议烟雾:调度往返主机->运行器->主机;通过PluginRuntime执行应用内JS)
E2E-024G:市场详细信息表显示自述文件、权限、版本
先决条件:提供官方市场目录。
步骤:1) 打开扩展 → 市场。 2) 打开
demo.workspace-summary的详细信息。 3) 检查自述文件/风险分组权限/版本行。 4) 选择版本并审核权限后安装。 5) 使用 Escape 并单击稀松布来解除纸张。预期:通过
market.getDetail加载详细信息表;自述文件、安全说明和每个风险的权限解释;所选版本驱动粘性安装操作; Escape 和 scrim 都会关闭工作表,但不会关闭下面的权限对话框。规格链接:
07-plugins/07-plugin-marketplace.md接受:G(市场详细信息用户体验)
状态:已记录
焦点回归:在浅色和深色主题下打开市场卡片,再按 Escape。与鼠标关闭一样,Escape 关闭后卡片不残留横线或焦点框。再用 Tab 和 Shift+Tab 导航:详情按钮显示完整、未裁剪的焦点框,安装按钮仍可单独获得焦点。 自动化焦点状态回归:
node scripts/e2e-plugin-detail-focus.mjs;焦点框外观需进行视觉验证。
E2E-024W:插件剪贴板历史捕获有界的文本和图片
- 先决条件:应用正在运行;测试插件声明并获授
clipboard.read;Composer 可以接收 一段文本粘贴和一张图片粘贴。 - 步骤:1) 将文本粘贴到 Composer,再将图片粘贴到 Composer,然后调用
pi.clipboard.getHistory()。2) 再次调用并修改返回的图片字节。3) 连续粘贴相同文本并调用 API。4) 让应用在系统剪贴板保留图片的情况下空闲,确认没有剪贴板采样。5) 撤销clipboard.read后再次调用。6) 加入超过文本/图片上限以及超过保留时间的测试数据。 - 预期:结果按最新优先返回,文本和图片按顺序混排,包含 ISO 时间戳、PNG 字节和图片尺寸; 修改返回结果不会修改主机状态。连续重复项会合并并刷新时间戳。超过单条上限或已过期的条目 不会返回,总条数和总字节上限生效。粘贴只记录事件已经读取的内容;主机不会重新读取系统剪贴板, 也不会在空闲时采样。API 复用
clipboard.read,拒绝时返回PERMISSION_DENIED,成功调用的 审计记录包含返回条目数。 - 链接规格:
07-plugins/03-plugin-api.md、07-plugins/04-plugin-security.md、07-plugins/13-plugin-permissions-matrix.md、ADR 0115 - 接受:G(插件剪贴板历史)+ 安全
- 状态:单元覆盖(
clipboard-history.test.mjs);Electron 剪贴板捕获仍需手动验证
E2E-024F:刷新官方远程市场存储库
- 先决条件:网络可用于 GitHub 原始内容。
- 步骤:1) 打开扩展 → 市场。 2) 使用标题刷新市场操作。 3) 确认源代码行指向
vastsa/pi-desktop-plugins。 - 预期:目录从远程官方存储库刷新;卡片网格更新;如果获取失败,离线回退仍然有效。
- 链接规格:
07-plugins/07-plugin-marketplace.md - 接受:G(远程市场来源)
- 状态:已记录/涵盖 host-core 单元
E2E-024P:切换市场目录来源
- 先决条件:网络可访问
plugins.aiuo.net、raw.githubusercontent.com与cnb.cool。 - 步骤:1) 在全新配置下打开扩展 → 市场,确认来源行显示官方渠道。 2) 依次切换到海外备份、国内备份、填写地址的自定义,再切回官方渠道。 3) 每次切换后确认同一页面完成目录刷新。 4) 先从官方渠道安装一个插件,再从国内备份安装一个。 5) 选择自定义地址但留空。
- 预期:全新配置默认停在官方渠道,其目录来自
plugins.aiuo.net/catalog.json;四个选项按官方渠道 / 海外备份 / 国内备份 / 自定义的顺序显示;切换后自动触发刷新并报告新的插件数量;来源选择器仍是唯一的来源状态控件,不显示重复的提供商说明或当前来源状态行;官方渠道的安装通过平台 resolve,国内备份的安装仍从镜像下载并通过与以前相同的 shasum 校验,因此两条备份路径逐字节不变;切回某个来源时直接复用其缓存快照而不是删除,也不产生额外往返;已安装记录标明插件来自哪个渠道;选择自定义地址但留空时回退到官方默认地址,而不是空端点。 - 链接规格:
07-plugins/07-plugin-marketplace.md§2 - 接受:G(远程市场来源)
- 状态:已记录 / 涵盖 host-core 单元
E2E-024B:带有权限审查的市场安装
- 前提条件:应用程序运行;提供官方市场目录。
- 步骤:1) 打开扩展 → 市场。 2) 安装
demo.workspace-notes。 3) 阅读风险分级权限对话框。 4)接受高风险权限。 - 预期:在下载之前,权限分为高/中/低,并带有简单的语言解释;从市场包安装的插件,校验和已验证,已授予权限,panel/tools可用;安装的选项卡和风险分组行反映了新插件,没有单独的概述卡行。
- 链接规格:
07-plugins/07-plugin-marketplace.md、07-plugins/13-plugin-permissions-matrix.md - 接受:G(市场安装+权限审查)
- 状态:单元测试+协议方法已记录/host-core
E2E-024C:插件包安装和自动更新路径
- 先决条件:市场目录有较新版本或本地
.piplug。 - 步骤:1) 从标题溢出菜单安装包。 2) 从行溢出菜单启用自动更新。 3) 运行检查更新,然后应用自动更新。
- 预期:该行移至“可用更新”,更新横幅报告计数;新版本添加的权限在审阅对话框中被标记为“新建”;仅当权限差异为空或预先授予时才应用自动更新。
- 链接规格:
07-plugins/06-plugin-packaging.md、07-plugins/08-plugin-signing-updates.md - 接受:G(软件包安装+更新策略)
- 状态:已记录
E2E-024H:已安装插件的表面状态、风险和故障
- 前提条件:至少有一个已启用的插件、一个已禁用的插件以及一个加载失败的插件。
- 步骤:1) 打开扩展→已安装。 2) 读取选项卡和组计数。 3) 确认失败的插件位于需要注意及其错误消息下。 4) 按作者和许可进行搜索。 5) 清除搜索。
- 预期:行分组为需要注意/可用更新/活动/已关闭(带计数);
status: "error" | "load_error"内联呈现错误消息而不是静默;每行默认为两行 name/id/version 摘要,而详细信息披露则揭示了带有风险色彩的权限芯片、功能和常驻服务状态;单范围触发器打开解释关闭/此项目/到处选择和项目选择器;行图标操作公开 hover/focus 标签;结果计数反映了过滤的子集,清除会恢复每个组。 - 链接规格:
04-ux/01-ui-ia.md、07-plugins/13-plugin-permissions-matrix.md - 接受:G(已安装插件管理)
- 状态:已记录
E2E-024D:隔离插件面板主桥
- 先决条件:启用
ui.panel的插件。 - 步骤: 1) 打开插件面板。 2) 调用面板桥 API(
ui.showToast、可选的 fs/net(带授权))。 3) 通过胶囊关闭面板,以及通过禁用或卸载插件关闭面板。 - 预期:面板在沙盒 window/partition 中运行;桥接调用经过许可检查;主机在面板关闭时保持稳定,主进程不得抛出
TypeError: Object has been destroyed或弹出未捕获异常对话框。 - 链接规格:
07-plugins/01-plugin-system.md、07-plugins/03-plugin-api.md、07-plugins/04-plugin-security.md、07-plugins/12-plugin-ipc-and-host-services.md - 验收:G(隔离面板)
- 状态:已记录
E2E-024AA:插件自有界面跟随宿主语言
- 先决条件:已加载带面板或设置入口的插件,且插件进程订阅了
pi.events.on("appearance:changed")。 - 步骤:1) 调用
pi.app.getLocale/app.getAppearance,确认语言标签与设置中的语言一致。2) 插件保持加载时切换应用语言。3) 确认生成式contributes.settings标题仍是作者语言。4) 确认插件进程与打开的面板收到带新locale的appearance:changed并自行重标文案。 - 预期:宿主只发布语言。插件自有文案无需重载即可更新。生成式设置面板不解析
{ en, "zh-CN" }。宿主拥有的身份文案仍走manifest.i18n(ADR 0267)。 - 链接规格:
07-plugins/03-plugin-api.md、07-plugins/02-plugin-manifest-schema.md、04-ux/02-i18n-english-first.md、ADR 0280 - 验收:G(插件 i18n)
- 状态:部分自动化(
apps/desktop/test/plugin-settings.test.mjs、plugin-work-panel-views.test.mjs);UI 走查已记录
E2E-024E:高风险插件 API 需要拨款
- 先决条件:使用明确授权安装的 Notes 插件。
- 步骤: 1) 通过插件运行时或面板桥调用
fs.writeText/net.fetch/shell.openExternal。 2) 撤销一项权限并重试。 - 预期:授权调用通过审核成功; revoked/undeclared 调用失败并显示
PERMISSION_DENIED,并且不会使应用程序崩溃。 - 链接规格:
07-plugins/13-plugin-permissions-matrix.md、07-plugins/04-plugin-security.md - 接受:安全+G
- 状态:已记录
E2E-024I:插件技能到达代理并按需加载
- 先决条件:
examples/plugins/hello启用并授予agent.prompt.inject;未经许可的舱单第二份副本;一个工作区是插件目录,另一个工作区不是。 - 步骤: 1) 开始会话并询问客服人员拥有哪些技能。 2) 要求它遵循 Hello 演示技能,以便它调用
Skill工具。 3) 编辑技能文档并重复步骤2。 4) 禁用插件并开始新一轮。 5) 加载不带agent.prompt.inject的变体并重复步骤 1。 6) 声明一个大于每个技能上限的文档。 7) 依次打开两个工作区。 - 预期:目录列出了技能 ID、名称和修剪后的描述,但没有正文,位于内置技能之后、项目指令链之前;仅在请求时才通过
ToolSearch加载Skill架构,并读取已编辑的文件而无需重新启动;禁用插件会重建运行时,因此该技能从下一回合就会消失;未经许可的变种加载正常,无技能贡献;过大的文档会通过审核行跳过,而不是夹在提示中;内置的plugin-development技能在插件工作区中进行编目,而在其他工作区中则不存在,而PluginCheck则在两者的有界按需工具目录中列出。 - 链接规格:
07-plugins/02-plugin-manifest-schema.md、07-plugins/04-plugin-security.md§7.1、07-plugins/10-plugin-devex.md、ADR 0039、ADR 0037、D174 - 接受:G(技能激活)+ E(工具和权限)+ D(高风险权限门控)
- 状态:单元覆盖(
plugin-skills.test.mjs、代理运行时 prompt/digest 测试);面向代理的场景草稿
E2E-024J:插件主题适用并在撤回时回退
- 先决条件:在授予
ui.theme的情况下启用examples/plugins/hello; CSS 使用@import或远程url()可用于拒绝情况的插件,以及只在注释里提到这些关键字的同一插件变体;第三个变体的主题声明了图片资源与windowAppearance背景,并提供有/无ui.window.appearance两种版本。 - 步骤: 1) 打开设置 → 常规 → 主题并选择
Hello Midnight。 2)重新启动应用程序。 3) 禁用提供的插件。 4)重新启用它,然后卸载它。 5) 使用不安全的 CSS 加载插件。 6) 加载只在注释里提到关键字的变体。 7) 在 Windows/Linux 与 macOS 上分别选中资源变体的主题,并检查该插件打开的面板。 8) 去掉ui.window.appearance后取消选中该主题。 - 预期:插件主题与内置插件一起出现在选择器中并立即应用;该选择在重新启动后仍保留为
plugin:demo.hello:midnight;禁用或卸载提供程序会退回到system而不是无样式的 shell;不安全的 CSS 在加载时被拒绝,并记录了原因,并且没有注入<style>元素;只在注释里提到关键字的样式表正常加载并提供其主题,因为消毒器只检查浏览器实际生效的 CSS;声明的资源在宿主外壳与该插件自己的面板中都经plugin-asset:渲染,未声明的引用被拒绝并记录原因,声明的背景在 Windows/Linux 上给原生窗口着色、在 macOS 上不下发,取消选中该主题或收回该权限后窗口回到宿主背景;整个外壳都跟随主题,包括工作面板列、其标题栏以及浏览器/文件查看器条目栏 —— 它们的表面色读--ds-bg-dock/--ds-bg-dock-raised,而不是字面量。 - 链接规格:
07-plugins/04-plugin-security.md§3.1、04-ux/07-ui-design-system.md、D175 - 接受:G(主题贡献)+安全
- 状态:单位覆盖(
plugin-themes.test.mjs、theme-cssSDK 测试);视觉场景草稿
E2E-024K:插件 MCP 服务器工具到达代理
- 先决条件:针对可信局域网存根声明一个
stdio和一个非回环 HTTP MCP 服务器的插件;已授予mcp.server.local和mcp.server.remote;HTTP 主机已列入net.domains;保存存根凭证的设置密钥。 - 步骤: 1) 启用插件并确认尚未启动服务器进程。 2) 要求代理调用已发现的工具。 3) 检查存根收到的 environment/headers。 4) 使存根调用失败并超时。 5) 让 stdio 存根的目录超过旧的 64 个工具上限并重新发现。 6) 禁用插件。
- 预期:服务器在首次使用时延迟连接;工具在
risk: "medium"上显示为plugin_demo_*_<serverId>_<tool>,并进行每次调用审核;stdio 子级仅接收声明的env值加上 PATH/temp/locale,从不接收主机提供程序密钥;非回环 HTTP 端点只有在主机列入白名单后才会接受,未加密传输会在审查中显示;跳转到未声明主机时会在第二次请求前阻止;失败和超时会返回工具错误,而不会导致插件或主机崩溃;大于旧的 64 个工具上限的目录会完整到达,而突破某项每服务器护栏(数量、页数、游标、遍历时间)的服务器会被拒绝,而不是贡献其目录的一个前缀;禁用会断开两个服务器的连接。 - 链接规格:
07-plugins/02-plugin-manifest-schema.md、07-plugins/04-plugin-security.md§8.1、ADR 0038、ADR 0142、D176、D281、D452 - 接受:G(MCP 桥)+ E(工具和权限)+ 安全
- 状态:单位覆盖(
plugin-mcp.test.mjsstdio + HTTP 存根);面向代理的场景草稿
E2E-024L:常驻插件服务受监督且可见
- 先决条件:在授予
background.service的情况下启用examples/plugins/hello。 - 步骤: 1) 打开扩展→已安装并展开插件行上的详细信息以读取
Greeter heartbeat芯片。 2) 杀死插件的实用进程并观察芯片。 3) 反复杀死它超过重启上限。 4) 禁用并重新启用该插件。 5) 撤销background.service并重新加载。 - 预期:详细信息披露暴露了一个在加载后报告
running的芯片; Kill 显示failed,然后再次显示running,并在尝试之间增加重新启动计数和退避;在过去的五次尝试中,插件保持failed状态并停止重试;手动disable/enable取消挂起的定时器并重置计数器;未经许可,服务永远不会启动,并且跳过会被审核。 - 链接规格:
07-plugins/05-plugin-lifecycle.md§3.1、ADR 0040、D177 - 接受:G(居民服务)
- 状态:单位覆盖(
plugin-services.test.mjs监督 + 退避);手动杀掉场景草稿
E2E-024M:总线消息仅按照声明跨插件
- 先决条件:启用两个插件 - 一个发布
demo.*主题,一个订阅demo.**- 并授予bus.publish/bus.subscribe。 - 步骤: 1) 运行发布者的命令并观察订阅者。 2) 发布
contributes.bus.publish中缺少的主题。 3) 订阅contributes.bus.subscribe中缺少的模式。 4) 发布超过 64KB 的负载,并在 10 秒内发布超过 100 次。 5) 卸载订阅者并再次发布。 - 预期:订阅者收到
{ topic, from, payload, at }而发布者永远不会收到自己的消息;未声明的发布和订阅都失败PERMISSION_DENIED,并有一条命名主题的审核行;负载过大且速率突发失败LIMIT_EXCEEDED/RATE_LIMITED;向已离开的订阅者发布成功,扇出较小且没有主机错误。 - 链接规格:
07-plugins/02-plugin-manifest-schema.md§5.1、07-plugins/04-plugin-security.md§5.1、ADR 0040、D178 - 接受:G(消息总线)+安全
- 状态:单位覆盖(
plugin-bus.test.mjs交付、过滤、上限);两个插件手动场景草稿
E2E-024N:扩展页面密度和主题可读操作
- 先决条件:应用程序运行时至少有一个已安装的扩展程序和一个 可用的市场行动;提供深色和浅色主题。
- 步骤:1)在深色主题中打开扩展。 2) 确认标题并 仅有“已安装”和“市场”两个选项卡,且两者都能到达内容 没有四卡数字概览带。 3) 确认安装行开始 作为安静的两行摘要,然后展开一行上的详细信息并检查其 功能、服务状态和权限。 4) 使用紧凑范围 控件及其解释的范围菜单、上下文主要操作以及 辅助 update/action 按钮。 5) 切换到浅色主题并重复。 6) 键盘焦点详细信息披露, 范围状态和每个操作。
- 预期:四张数字概览卡不存在;标签计数, 已安装的组计数,并且任何更新警报在其中仍然可用 相关表面。安装的行保持其默认高度较低,而 披露暴露了完整的辅助读数。范围触发器保持不变 与行操作轨对齐,其菜单解释每个状态和图标 操作在静止时保持可见,同时在悬停和焦点时显示标签。 主要和次要按钮保留 可见的语义表面、文本、边框、悬停状态和焦点环 这两个主题和键盘焦点都不依赖于指针悬停。
- 链接规格:
04-ux/01-ui-ia.md、04-ux/07-ui-design-system.md、07-plugins/07-plugin-marketplace.md、ADR 0058、D196 - 接受:G(扩展页面)+质量
- 状态:单位覆盖(
extensions-page.test.mjs,plugins-page-style.test.mjs);视觉场景草稿
E2E-024O:市场隐藏仅供开发的示例插件
- 前提条件:应用程序运行;官方目录包含 开发装置
demo.hello或demo.workspace-summary且至少 一个产品插件。 - 步骤: 1) 打开扩展 → 市场并进行空搜索。 2) 搜索 依次为
Hello、Workspace Notes和Workspace Summary。 3)检查 类别过滤器和结果卡。 4) 打开已安装并验证 已安装的示例仍然易于管理。 - 预期:ID 以
demo.开头的条目永远不会出现在 市场卡、类别或搜索结果;保留产品插件 可发现的。已安装的示例仍列在“已安装”下 它可以被禁用或卸载,而不是成为非托管运行时。 - 链接规格:
07-plugins/07-plugin-marketplace.md,04-ux/01-ui-ia.md - 接受:G(扩展页面)+质量
- 状态:单位覆盖(
extensions-page.test.mjs);视觉场景草稿
E2E-025:禁用插件会删除贡献
- 先决条件:插件已启用并且贡献可见。
- 步骤: 1) 在“扩展”页面上禁用该插件。 2)检查全局搜索和代理工具。
- 预期:命令和工具消失;没有剩余捐款。
- 规格链接:
07-plugins/05-plugin-lifecycle.md - 接受:G(禁用删除贡献)
- 里程碑:M4
- 状态:自动(协议烟雾:禁用清除启用标志;全局搜索删除手动)
E2E-026:插件错误不会导致应用程序崩溃
- 先决条件:已加载插件。
- 步骤:1) 触发插件抛出错误的场景。 2) 观察应用程序行为。
- 预期:应用程序保持运行;捕获并报告错误;没有崩溃。
- 链接规格:
07-plugins/04-plugin-security.md - 接受:G(插件错误→没有崩溃)
- 里程碑:M4
- 状态:草案
安全——无秘密泄露
E2E-027:正常流程的日志中没有秘密
- 先决条件:使用 API 密钥配置的提供程序。
- 步骤:1) 执行聊天会话。 2) 检查日志文件。
- 预期:API 键/令牌不存在于正常流程的任何日志输出中。
- 链接规格:
05-security/01-security.md、03-runtime/09-logging-and-observability.md - 接受:H(不在日志中的秘密)
- 里程碑:M2
- 状态:自动化(协议烟雾:提供商列表不包含秘密材料)
E2E-028:Renderer 没有 Node 集成
- 先决条件:应用程序正在运行。
- 步骤:1) 检查渲染器进程标志。
- 预期:
nodeIntegration: false;contextIsolation: true; preload 是唯一的桥梁。 - 链接规格:
05-security/01-security.md - 接受:安全(渲染器中没有 Node)
- 里程碑:M1
- 状态:草案
E2E-029:无法调用未列入白名单的 IPC
- 先决条件:应用程序正在运行。
- 步骤:1) 尝试从渲染器调用不在白名单中的 IPC 方法。
- 预期:呼叫被阻止;没有返回数据;错误或无响应。
- 链接规格:
03-runtime/01-ipc-protocol.md、05-security/01-security.md - 接受:安全(强制实施 IPC 白名单)
- 里程碑:M1
- 状态:草案
E2E-030:插件无法读取 API 密钥
- 先决条件:插件已加载;提供商已配置。
- 步骤:1) 插件尝试通过任何 API 访问提供商机密。 2)观察结果。
- 预期:访问被拒绝;没有秘密数据返回到插件。
- 链接规格:
07-plugins/04-plugin-security.md、03-runtime/14-secrets-storage.md - 接受:安全(插件无法读取 API 密钥)
- 里程碑:M4
- 状态:草案
E2E-031:错误代码稳定且可读
- 先决条件:通过正常桌面开发启动应用程序 命令;提供商已配置。
- 步骤: 1) 选择或输入提供商拒绝的模型 ID。 2) 发送 提示。 3) 检查助手错误信息及其详细披露。 4) 切换会话并重新加载失败的会话。 5) 重复无效 提供商密钥。
- 预期:运行停止并且转录物包含一个持久的
role=assistant、status=error消息而不是 toast、浮动横幅, 或空白行。它显示了本地化的摘要和稳定的MODEL_NOT_CONFIGURED或PROVIDER_UNAUTHORIZED代码。细节暴露 已编辑的提供商响应加上 provider/model ID,并且可以复制;没有 API 出现密钥或授权值。配置失败链接到 设置,可重试的失败提供重试,输入框再次可用, 并重新加载保留错误消息。开发启动执行 sidecar 从当前运行时源重建。 - 链接规格:
03-runtime/02-agent-runtime.md,03-runtime/07-process-model.md、03-runtime/08-error-codes.md - 接受:C(失败的聊天解决),H(错误暴露稳定代码)
- 里程碑:M2
- 状态:单元已覆盖(代理运行时错误 message/redaction,主机 持久性、桌面脚本合同和预开发构建合同);满 Electron UI 场景草案
硬化 (M5)
E2E-032:后端崩溃触发受监督的重启
- 前提条件:应用程序运行; host-core 和 sidecar 健康。
- 步骤: 1) 从外部终止 host-core(或 sidecar)进程。 2) 观察应用程序行为。
- 预期:运行中的 RPC 快速失败(不会长时间挂起);
hostStatus显示降级然后恢复;孩子以退避方式重新开始;在 2 分钟内 3 次重新启动失败后,应用程序会保持降级状态,并出现明显的致命状态。 - 链接规格:
03-runtime/07-process-model.md - 验收:质量(主路径无崩溃)
- 里程碑:M5
- 状态:自动(
scripts/e2e-supervision.mjs— SIGKILL host-core,断言重新启动 + 正常的 RPC)
E2E-033:重新启动后窗口边界仍然存在
- 先决条件:应用程序以默认窗口大小运行。
- 步骤: 1) Resize/move 将窗口设置为不同的正常边界 A (≥1040×700),在 600 毫秒保存去抖动结束之前最大化,退出并重新启动。 2) 恢复,resize/move 到不同的边界 B,在去抖结束之前退出,然后再次重新启动。
- 预期:每次重新启动都会恢复最新的正常范围(A,然后 B),包括在最大化或等待保存时发生退出。 Maximized/fullscreen 几何图形永远不会存储为法线边界; invalid/tiny 保存的边界回退到默认值 1200×800。
- 链接规格:
04-ux/09-interaction-patterns.md - 验收:质量(关键操作感觉很精致)
- 里程碑:M5
- 状态:已记录
E2E-034:NDJSON 日志文件已写入并经过编辑
- 先决条件:新鲜的个人资料;提供商已配置;一轮聊天结束。
- 步骤:1) 通过工具调用运行提示。 2) 打开
~/.pi-desktop/logs/。 3) 检查app/、host/和agent/下的分类文件。 - 预期:NDJSON 记录与
ts/level/channel/category/event/message一起存在;正常工具调用只产生一条携带sessionId/toolCallId、安全工具元数据和有界结果/时长信息的完成或失败记录;中断工具仍可由同一 ID 追踪;没有出现 API key、Authorization 值、原始命令输出或本机绝对路径;每个类别文件在 5 MB 轮换。生命周期、权限、工具、provider、plugin、持久化、更新器和错误记录仍可用,且正常运行不会创建独立的 timing 类别文件。 - 链接规格:
03-runtime/09-logging-and-observability.md - 接受:H(诊断)
- 里程碑:M5
- 状态:已记录
E2E-194:断开的 stdout 不会让主进程崩溃
- 先决条件:已打包或开发版应用;会话可以发送提示。
- 步骤:1) 以关闭的 stdout/stderr 管道启动(从桌面项启动 Linux AppImage, 或 stdout 读取端已关闭)。2) 发送一条聊天消息。3) 确认主进程仍在运行,且 未出现未捕获异常对话框。4) 退出并重新启动。
- 预期:不会出现 Electron “A JavaScript error occurred in the main process” 对话框,也不会从
Logger.log抛出Error: write EPIPE。NDJSON 分类日志仍 会写入提示记录。重新启动仍能连上本地 host 服务,而不是因为上次主进程崩溃 而出现致命的 “Can't reach the local service” / “无法连接本地服务”。 - 链接规格:
03-runtime/09-logging-and-observability.md、03-runtime/07-process-model.md - 验收:H(诊断),质量(主路径无崩溃)
- 里程碑:M5
- 状态:单位覆盖(
logger-routing.test.mjs);打包 AppImage 旅程已记录
E2E-RUNTIME-non-ascii-http-header-does-not-show-main-exception-dialog
- 先决条件:已打包或开发版应用;系统 HTTP 代理或网关会注入非 Latin-1 响应头(例如以 U+661F「星」开头的值),或测试通过 Electron
net投递同样的TypeError: Cannot convert argument to a ByteString。 - 步骤:1) 启动,使自动更新检查或模型发现发出主进程
net.fetch/ electron-updater 请求。2) 确认没有原生异常对话框。3) 无需关闭任何框;等待 下一次更新或发现请求。4) 打开~/.pi-desktop/logs/app/runtime.log。 - 预期:不会出现 Electron “A JavaScript error occurred in the main process” 对话框。应用保持运行、不会退出。
runtime.log中有code: "NON_ASCII_HTTP_HEADER"且recoverable: true的错误记录。后续 主进程 HTTP 请求不会再次弹出该原生框。 - 链接规格:
03-runtime/07-process-model.md、03-runtime/09-logging-and-observability.md - 验收:H(诊断),质量(主路径无崩溃)
- 里程碑:M5
- 状态:单位覆盖(
main-process-errors.test.mjs);打包 Windows 代理旅程已记录
E2E-195:Linux glibc 低于 2.35 时列出支持的发行版
- 先决条件:Linux x64 打包应用;本机 glibc 低于 2.35(例如 Ubuntu 20.04 / Debian 11 / Fedora 35),或测试将
process.report设为2.31。 - 步骤:1) 启动 AppImage、deb 或 rpm。2) 观察主窗口和致命横幅。3) 确认 host-core 没有进入重启循环。
- 预期:Electron 仍能打开。没有未捕获的
write EPIPE对话框。致命横幅 说明构建需要 glibc 2.35 或更高版本,并列出 Ubuntu 22.04、Debian 12 和 Fedora 36+。监管重启不会空转。符号需要 glibc 2.39 的 host-core 二进制会 让scripts/check-linux-host-glibc.mjs失败。 - 链接规格:
03-runtime/07-process-model.md、01-product/01-product-scope.md、06-delivery/06-release-runbook.md - 验收:H(诊断),质量(主路径无崩溃)
- 里程碑:M5
- 状态:单位覆盖(
linux-glibc.test.mjs);打包发行版旅程已记录
E2E-199:子智能体编辑器提供预设模板和按提供商限定的模型选择器
- 先决条件:Agent 模式;设置中至少有一个已配置且可运行的提供商(含模型绑定)。
~/.agents/subagents目录为空。 - 步骤:1) 打开“设置 → 智能体 → 子智能体”,点击“新建子智能体”,确认表单上方有 “从模板开始”一行紧凑名称 chips(探索者、代码审查员、测试执行者、修复者、空白开始)。 chips 只显示名称,选中项的一句话说明出现在整行下方;没有长副标题、没有每张卡片上的“套用”, “高级”默认收起。带连字符的 id(
code-reviewer、test-runner)必须显示目录译名,而不是presetCode-reviewerName这类原始 key。 2) 选择探索者,确认名称、描述、Read / Glob / Grep / Bash 工具和完整提示词均已填入, 模型仍沿用当前会话。3) 重新打开并选择修复者,确认工具和提示词更新,且显示可修改工具的提示。 4) 展开高级并打开模型选择器,确认每个已配置的 可运行提供商的模型都出现(按提供商分组),选择一个模型,确认保存值为vendorKey-or-name/modelId。4a) 配置一个显示名含空格的自定义端点(例如「My Gateway」), 确认选择器会列出它;选中后确认表单能保存,固定值为<显示名>/<modelId>,且保存按钮保持可用—— 选择器与草稿校验绝不能对「什么能保存」产生分歧。 5) 确认选择器里没有“自定义(provider/model)…”入口,也没有任何可手填模型 ID 的输入框; 再选择“沿用当前会话的模型”,确认模型值为空。 5a) 在打开的模型菜单里确认列表在菜单内部滚动、不会越过窗口边缘被裁断, 且筛选框能按模型或厂商名收窄结果;确认它不是系统绘制的下拉弹层。 6) 禁用所有提供商后重新打开并展开高级,确认模型字段显示带操作按钮的空态, 点击后跳转到模型设置,而不是出现手填输入框。7) 切换到简体中文,确认预设和选择器标签 已翻译,且没有原始 i18n key。 - 预期:选择器只提供已配置、可运行的提供商模型;表单不提供任何手填模型 ID 的路径, 因此保存的 pin 总能被解析。没有可运行模型时给出可操作的出口(跳转模型设置), 而不是让用户输入一个运行时无法解析的值。选择预设只覆盖描述、工具和正文, 不会悄悄清除模型、推理强度或作用域;选择空白开始会清除已选预设的这些模板字段。
- 链接规格:
04-ux/06-settings-ia.md§7、03-runtime/13-model-catalog-and-selection.md§11、03-runtime/11-provider-model-system.md§6.4、ADR 0062、ADR 0089 - 验收:B(模型配置)、C(对话和流)、品质
- 里程碑:M6+
- 状态:单元/源代码契约已覆盖;完整 UI 旅程为草稿(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-SUBAGENT-settings-lists-builtin-defaults
- 前提条件:应用已运行。
~/.agents/subagents为空。五个内置定义存在,没有一个被关闭,且没有用户文档覆盖它们。 - 步骤:
- 打开设置 → 智能体 → 子智能体。确认内置分组列出
explorer、code-reviewer、test-runner、fixer、ui-designer,带本地化名称、Task(<handle>)、工具授权、「内置」徽标、 「复制为我的定义」,以及处于打开位置的启用开关。确认这些行没有在文件夹中显示或删除。 - 确认全局分组仍显示本地化的
settings.subagentsEmpty文案和「新建子智能体」。 - 在内置行上关掉
fixer。确认该行随开关关闭而变暗,toast 指出它的名字,~/.agents/subagents中没有出现任何东西,且该行仍留在列表里,因为那个开关就是重新打开的入口。 - 在
fixer关闭的情况下发送一次提示。确认Task目录不再提供它,而其余四个仍在;然后把它重新打开, 确认下一次提示又提供它。 - 在 explorer 上选择「复制为我的定义」。确认新建表单按该定义预填(名称、描述、工具、正文), 且选中的是探索者模板芯片而不是空白开始。保存后确认 explorer 只出现在全局用户行,并从内置分组 消失,且下一次提示的
Task目录使用该用户文档。 - 禁用该用户 explorer 并重新加载。确认用户行关闭,explorer 重新出现在内置分组(未启用的用户文档不会进入加载器,因此内置定义重新生效)。
- 打开设置 → 智能体 → 子智能体。确认内置分组列出
- 预期:设置页展示主智能体实际可委派的默认子智能体,且每一个都能从自己的行上关闭。内置项的启用状态是应用本地状态而不是文档,因此被关闭的默认项保留自己的行;复制内置项仍是改写它的方式;在文件夹中显示与删除仍然只作用于用户自建的行,因为它们以文件为后端。
- 链接规格:
04-ux/06-settings-ia.md§2、03-runtime/01-ipc-protocol.md§12c、03-runtime/02-agent-runtime.md§5f、ADR 0062、ADR 0063、ADR 0270 - 验收:E(工具与权限)、品质
- 里程碑:M6+
- 状态:源代码/单元已覆盖(
apps/desktop/test/agent-capability-settings.test.mjs、apps/desktop/test/subagent-wiring.test.mjs、packages/agent-runtime/src/subagent-definitions.test.ts、packages/shared/src/subagent-presets.test.ts);完整 UI 旅程为草稿
E2E-200:Linux RPM 保留 Wayland 桌面身份
- 先决条件:Linux x64 软件包验证运行或标签发布可以在 Ubuntu 22.04 上完成;有一台干净的 Fedora 44 KDE/Wayland 机器用于安装和启动。
- 步骤:1) 构建 Linux 目标,并使用
rpm -qip和rpm -qpl检查 RPM。 2) 确认软件包包含应用归档、host-core、pi-desktop.desktop和 512px 的pi-desktop图标。3) 确认 RPM 没有全局/usr/lib/.build-id链接。 4) 在 Fedora KDE/Wayland 机器上安装 RPM,并从桌面项启动 PI-Desktop。 5) 检查任务栏分组和已安装的桌面项。 - 预期:生成具有文档所述名称的 x64 RPM,并与发布工件一起上传。桌面项 包含
Icon=pi-desktop和StartupWMClass=pi-desktop;运行中的 Wayland 窗口与 PI-Desktop 启动器正确分组并显示其图标,而不是通用 Electron 图标。 软件包更新仍采用通知并打开链接的模式,捆绑的 Electron 二进制文件不会生成 全局 build-id 链接。 - 链接规格:
01-product/01-product-scope.md、04-ux/09-interaction-patterns.md、06-delivery/06-release-runbook.md - 验收:质量(发布打包和桌面集成)
- 里程碑:M6+
- 状态:单元/源代码契约已覆盖(
auto-update.test.mjs、development-branding.test.mjs、ci-workflow.test.mjs);Fedora KDE/Wayland 安装仍需运行器验证
E2E-035:Bash 工具使用有效的目录 shell
- 前提条件:工作区打开;代理模式。
- 步骤:1) 选择可用的目录 shell 并运行
Bash(例如echo ok)。 2) 使持久选择不可用,并在运行下一轮之前检查有效目录。 3) 使用上一回合快照运行。 - 预期:未更改的
Bash协议调用使用选定的目录条目。稍后不可用的持久选择会回退到第一个可用的平台 shell 并标记目录回退;上一个回合的快照被COMMAND_SHELL_CHANGED拒绝为过时的,而不是默默地更改 shell。不会发生部分执行; E2E-113 覆盖过时的身份路径。 - 链接规格:
03-runtime/03-tools-and-permissions.md、03-runtime/06-host-rpc-protocol.md、03-runtime/08-error-codes.md、ADR 0054 - 接受:H(错误暴露稳定代码)
- 里程碑:M5
- 状态:单位覆盖(
tools::shell::tests);场景已记录
E2E-044:开发启动使用 PI-Desktop Dock 品牌
- 先决条件:macOS 开发与规范的
build/icon_1024.png进行检验。 - 步骤:1) 运行
pnpm dev。 2) 检查正在运行的应用程序的 Dock 图标。 - 预期:Dock 显示 PI-Desktop 品牌图标,而不是 Electron 的默认图标;打包版本继续使用
build/icon.icns。 - 链接规格:
06-delivery/06-release-runbook.md - 验收:质量(开发外壳与发布品牌相匹配)
- 里程碑:M5
- 状态:单位覆盖(
development-branding.test.mjs);视觉场景记录
E2E-045:全局文本选择保留编辑和复制
- 先决条件:应用程序运行时包含用户的聊天记录 消息、带有代码块的助理 Markdown 响应以及扩展的 工具结果。
- 步骤:1) 拖动 sidebar/titlebar chrome 和按钮标签。 2) 在 user/assistant 散文、代码和工具输出之间拖动。 3)聚焦 输入框和 settings/search 输入,然后使用
Cmd/Ctrl+A并替换 选定的文本。 4) 复制选定的成绩单和代码文本。 - 预期:Chrome 不会留下意外的文本选择;留言 散文、代码、工具 input/output 和可编辑控件仍然是可选的 可复制;本机编辑快捷方式、焦点可见环和窗口拖动 行为保持不变。
- 链接规格:
04-ux/07-ui-design-system.md,04-ux/09-interaction-patterns.md - 验收:质量(关键操作感觉很精致)
- 里程碑:M5
- 状态:单位覆盖(
user-select.test.mjs);场景已记录
E2E-046:PI-Desktop 渲染器品牌和输入框图标边界
- 先决条件:应用程序在英语和中中文语言环境中运行,并带有 空荡荡的家和可用的停靠成绩单。
- 步骤:1) 检查展开和折叠的侧边栏。 2) 检查 空荡荡的英雄和停靠的输入框。 3) 观察八帧吉祥物 GIF 原地循环, 将指针移到其上并确认节奏与几何形状不变。开启减少动态 效果并确认显示静止首帧。 4)关注页脚设置和插件 图标,然后每个 project/Temporary 会话创建控件。 5)打开设置 和输入框输入。
- 预期:可见 shell 标识为
PI-Desktop;空荡荡的家英雄 渲染与当前主题匹配的 100pxHomeMascotLogoGIF,首帧短暂停留后循环挥手; 指针悬停不改变节奏或几何形状,减少运动时显示对应静止首帧。 expanded/collapsed 侧边栏通过BrandLogo呈现派生的src/assets/brand/logo-*.png资源 并且停靠的输入框提示行没有前导 品牌图标或保留图标槽及其文本直接与输入对齐 天沟。页脚设置和插件操作是紧凑的图标按钮; 插件紧邻“设置”右侧,并公开本地化的 可访问的名称。每个作用域会话创建控件都使用专用的 带有本地化标签和可访问名称的消息加号图标。Codex仅在以下情况下保持可见 外部导入源标签或非运行时设计参考文本。 - 链接规格:
04-ux/01-ui-ia.md、04-ux/07-ui-design-system.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、08-meta/decisions-log.md(D094/D160),../../adr/0031-icon-free-composer-prompt-row.md - 验收:质量(品牌一致性和关键操作感觉很精致)
- 里程碑:M5
- 状态:单位覆盖(
renderer-branding.test.mjs);场景已记录
E2E-047:保留、折叠、切换和关闭多个项目选项卡
- 先决条件:项目 A 和 B 各至少有一个持久会议; 两个路径均未存档;还存在临时会话。
- 步骤: 1) 从“设置”→“项目存档”打开项目 A。 2)打开项目B而不关闭 A.3) 单击 A 的 V 形、文件夹、标签和尾随目录行 依次披露击中区域到 collapse/expand 即可;使用 B 的目录行 激活和崩溃B;验证
+和溢出不会切换 B. 4) 悬停并 键盘焦点 A 的项目标题并确认完整路径已公开;打开 A的项目溢出或者右键菜单选择打开文件夹;确认 对话溢出不再提供打开文件夹。 5) 选择A的对话。 6) 关闭 B。 7) 重新启动应用程序。 8) 从“设置”→“项目存档”重新打开 B。 - 预期:A 和 B 在 a 中呈现为单独的精确路径侧边栏组 紧凑的连续列表,每个目录公开时有一个键盘停止; A 行中的每个非操作点仅切换 A,项目操作出现在 hover/focus 不带移位标签,项目标题 hover/focus 路径显示 A 的完整绝对路径,打开文件夹仅是项目菜单操作并打开 A 在系统文件管理器中,崩溃并重新启动后仍然存在; 激活群组或其对话 清除以前可见的记录,更新选定的工作区并 会话绑定,然后仅加载所选项目的会话; 暂时保持分离;关闭 B 仅删除其保留的选项卡,并且 既不删除其项目行也不删除会话;重新打开B恢复相同 会话无需重复。
- 链接规格:
04-ux/01-ui-ia.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、ADR 0016 - 接受:C(切换会话)、D(工作空间)、F(本地演示) 坚持)
- 里程碑:M5
- 状态:单元覆盖(
sidebar-preferences.test.mjs用于保留路径 和崩溃持久性);完整的 UI 场景草稿
E2E-048:固定、存档、恢复和排序 project/conversation 行
- 先决条件:两个保留的项目包含不同的对话 标题和 created/updated 时间戳;存档视图最初被禁用。
- 步骤:1) 检查静态的会话和项目标题操作。 2) 将鼠标悬停在每个标题上并通过键盘聚焦每个操作来确认控件 无需移动标签即可显示。 3) 检查
Sessions工具栏并验证 该排序出现在新聊天之前。 4) 打开排序并检查其位置, 然后依次选择“最近更新”、“创建日期”、“最早的在前”和“名称”。
- 固定一个项目和一个对话。 6) 归档另一个对话 和项目。 7) 启用显示存档并恢复两者。 8) 重新启动应用程序。
- 通过不同的删除操作删除一次性对话。
- 预期:部分创建和排序控件在休息时视觉上很安静, 在工具栏悬停或键盘焦点上显示;项目
+和溢出操作 遵循相同的规则而不改变标签。排序优先于新聊天 会话工具栏;排序菜单保持内容大小,打开触发器 向右而不向左翻转,并且 session/project/section 主体级菜单使用与 窄视口宽度上限。置顶项目行的彩色字形使用实心 Star,未置顶项目保持 Folder,项目名称旁仍显示本地化的置顶标签。固定行保持在前面 每个选定的二级订单下未固定的行数;每种排序都会产生 有记录的稳定订单;归档行从默认视图中消失,但 保留 transcripts/project 记录并重新出现在 Show archived 中;恢复 将它们返回到选定的顺序;归档活动行选择一个可见的 非存档后备或创建记录的空后备而不是 留下隐藏的活动上下文; pin/archive/sort 选择在重启后仍有效;仅 删除会删除一次性持久会话。旧版manual首选项 安全加载,不会暴露或暗示拖动重新排序工作流程。 - 链接规格:
03-runtime/04-data-storage.md、04-ux/01-ui-ia.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md - 接受:C(会话组织),F(持久性)
- 里程碑:M5
- 状态:单位覆盖(元数据为
sidebar-preferences.test.mjs, 过滤和排序行为);完整的 UI 场景草稿
E2E-048b:编辑逻辑项目名称和文件夹根目录
- 前提:一个逻辑项目同时出现在侧边栏和设置 → 项目存档中,包含一个 Primary 文件夹和一个附加文件夹。
- 步骤:1) 打开侧边栏项目溢出菜单并选择“编辑项目”。2) 修改名称,移除 附加文件夹,再通过原生文件夹选择器将其添加回来。3) 确认 Primary 行不能移除。 4) 保存并检查侧边栏、项目存档中的根目录和当前工作区。5) 重启应用并再次检查项目组。
- 后台更新回归:从设置打开编辑项目,修改名称并移除附加文件夹,然后在保存前 完成一个后台任务。两项草稿修改必须保留,并一起保存。隔离渲染测试为
node scripts/e2e-project-edit.mjs;使用真实页面和 store,仅控制 host IPC 边界, 不代表已验证磁盘持久化。 - 预期:两个项目菜单都提供编辑项目;编辑器保持焦点、去除首尾空格并限制为 80 个 Unicode 字符,Primary 文件夹始终是第一行,根目录数量更新且不会误删其他行。 保存后仍是一个逻辑项目组,调整后的根目录会持久化;名称在重启后保留,规范化 路径、工作区身份、会话和磁盘文件夹均不改变。已有聊天的根目录会被拒绝移除, 不会产生孤立会话。
- 链接规格:
04-ux/01-ui-ia.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md - 验收:D(工作区身份)、F(本地演示持久性)
- 里程碑:M5
- 状态:单位覆盖(
project-edit.test.mjs、sidebar-preferences.test.mjs);完整的 UI 场景草稿
E2E-048A:项目会话在最近十行之后列出折叠
- 前提:一个保留项目包含十个以上耐久 具有不同更新时间戳的会话;侧边栏使用默认值 最近更新的排序;另一个保留的项目有十个或更少的会话。
- 步骤:1) 检查大型项目的会话行并对其进行计数。 2) 选择 加载 N 更多... 控件。 3)将排序切换为名称并检查 扩展前的同一组。 4) 重新启动应用程序并检查组 再次。
- 预期:该组默认显示正好十个会话行加上一个 再加载 N 个… 控件(N = 剩余会话数)样式类似于 按时间分组的溢出;这十行是活动排序中的第一行 顺序,因此固定行永远不会被推到未固定行和名称排序后面 按字母顺序折叠前十个之后的所有内容;选择加载更多 展开完整的时间分组列表 (Yesterday/Previous 7 days/Previous 14 days/Older 标头出现(如适用),并且控件消失; 扩展按组进行,并在重新启动时重置(不持久);该项目 十个或更少的会话显示没有折叠控制。
- 链接规格:
04-ux/01-ui-ia.md、04-ux/09-interaction-patterns.md - 接受:C(会议组织)
- 里程碑:M5
- 状态:场景已记录
E2E-049:后台会话保留其原始工作空间
- 前提条件:保留项目A和B;每个都包含一个会话 Agent 模式;两个工作区都包含具有相同内容的不同标记文件 相对名称。
- 步骤:1) 在会话 A 中,开始一轮读取标记并执行 权限控制的长期运行工具。 2)当A运行时,激活项目 B 并打开会话 B。 3) 读取 B 的标记并仅允许 B 中的工具。 4) 等待 两个回合都完成。 5) 打开临时会话并尝试 工作区所需的工具。
- 预期:切换选项卡不会中止任何转动; A的工具cwd/path沙箱 保留项目 A,B 保留项目 B; A 的活动和赠款从来没有 出现在B的transcript/session中;每个侧边栏行报告自己的 running/completed 状态;临时会话不继承任何项目并且 接收
WORKSPACE_REQUIRED;返回 A 恢复 A 的已完成状态 成绩单。 - 链接规格:
02-architecture/01-architecture.md,03-runtime/02-agent-runtime.md、03-runtime/03-tools-and-permissions.md、03-runtime/06-host-rpc-protocol.md,03-runtime/10-session-state-machine.md、ADR 0016 - 接受:C(并行会话)、D(工作区)、E (tool/permission 隔离)、安全(工作空间边界)
- 里程碑:M5
- 状态:单位覆盖(
rpc::tests表示项目绑定、临时和 缺少会话工作区解决方案);完整的多回合UI场景草稿
E2E-050:Composer 模型 × 推理菜单遵循精确能力
- 先决条件:一种编目推理模型、一种非推理模型,以及一个未知的自由格式模型 ID。
- 步骤:1) 打开 Composer 模型 × 推理芯片。2) 确认根层包含带当前值的模型和推理等级条目,以及推理等级条目正下方轨道上每个支持等级一个刻度点的滑杆;确认每个等级在轨道下方都保留可见标签、各自对齐到自己的刻度点。3) 拖动并点击滑杆跨过多档,再点击一个刻度标签,确认芯片更新且菜单留在根层、选中档的刻度点位于滑块正下方。4) 打开模型,搜索并从一个提供商分组选择模型;确认菜单仍在根层打开。5) 打开推理等级并从单选列表选择一档。6) 对非推理提供商和未知自由格式模型 ID 重复;练习 Escape、外部点击、上/下、Enter、左方向键和滑杆方向键。
- 预期:芯片在右侧工具栏,带 Bot 图标,位于独立提示词增强 Sparkles 动作和发送/中止之前;Off 省略等级文本。单个锚定菜单把根层原地替换成返回行和子菜单,从不打开标签页或第二个弹出层,再打开总是从根层开始。模型搜索过滤粘性提供商分组;推理等级来自
omit然后绑定已启用档位的规范顺序。当列出一个以上等级时,根层在推理等级条目正下方承载拖动滑杆(单档绑定隐藏滑杆)。拖过多个刻度只持久化最后一次待提交的档位;刻度标签不是 Tab 停靠点。滑杆和刻度提交立即更新芯片且菜单留在根层。推理等级条目打开单选列表,使用单选语义、末尾勾选和当前模型支持说明。选择任一值立即更新芯片和根层值、清除模型过滤并保持菜单打开。非推理或未知模型从off开始,但显式 Settings 绑定可以提供其配置档位;发现不会自动提升。刷新发现的模型数据不能覆盖绑定。第一条消息创建会话之前,Composer 使用模型菜单里选中的精确模型而不是提供商默认模型;物化后仍保持同一精确模型能力。 - 链接规格:
03-runtime/11-provider-model-system.md,03-runtime/12-provider-config-schema.md,03-runtime/13-model-catalog-and-selection.md、ADR 0018、ADR 0027 - 验收:B(模型配置),质量
- 里程碑:M5
- 状态:单元覆盖(
thinking-ui.test.mjs、composer-model-thinking-menu.test.mjs、thinking-commit-queue.test.mjs,代理运行时能力测试);完整 UI 场景草稿
E2E-051:思考水平在会话中持续存在
- 先决条件:具有推理能力的会话空闲。
- 步骤: 1) 选择
high。 2) 改变Plan/Goal/Agent模式而不改变 思维水平。 3) 重新启动应用程序并重新打开会话。 4)切换到 另一个会议然后回来。 - 预期:每次配置更新都会发送完整的会话配置;
high在权限模式更改、会话切换、主机重新加载和 应用程序重新启动。 v2 数据库将相同字段迁移到off,而不会丢失转录本。 - 链接规格:
03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md、04-ux/08-component-spec.md、ADR 0018 - 接受:F(坚持)
- 里程碑:M5
- 状态:单位覆盖(主机 schema/session 测试、
thinking-ui.test.mjs);完全重启场景草稿
E2E-052:思维水平达到圆周率要求
- 先决条件:具有稀疏性的具有仪器推理能力的提供商 级别设置和请求捕获;一个会话配置在上方和下方的间隙。
- 步骤:1) 选择每个可用级别并运行提示。 2) 种子 不支持的存储级别并再次运行。 3) 用 pi 目录重复 非推理模型。
- 预期:主要使用会话的实际模型 ID 解析功能; Composer、main、sidecar 和 pi 使用相同的 upward-first/downward-second 夹子; pi 接收有效级别,非推理模型接收
off。特定于模型的请求语义,包括自适应思维和off是否可表达,匹配固定的 pi 记录,无需桌面 重写。 - 链接规格:
03-runtime/01-ipc-protocol.md,03-runtime/02-agent-runtime.md、03-runtime/13-model-catalog-and-selection.md、ADR 0018、ADR 0027 - 接受:B(模型配置),C(聊天和流)
- 里程碑:M5
- 状态:单元覆盖(代理运行时 prompt/clamp 测试);集成场景草案
E2E-053:思考与答案分开
- 先决条件:提供商在回答之前和之间发出思考增量 三角洲。
- 步骤:1) 开始交替使用浅色和深色主题。 2) 观察 仅思考阶段。 3)让答案完整。 4)切换披露, 测试键盘焦点、启用减少运动并使用复制答案。
- 预期:在仅思考流媒体期间打开文字记录;一开 思考披露更新,没有空答案泡沫或重复 工作指示器。该披露使用了文字记录表面、主题标记、 Sparkles/chevron 触发器,以及左规则而不是插入卡; 折叠的内容留下焦点遍历,减少运动会禁用闪烁 和过渡。最终答案markdown单独渲染;复制答案 不包含思考文本。
- 链接规格:
03-runtime/01-ipc-protocol.md,04-ux/07-ui-design-system.md、04-ux/08-component-spec.md、ADR 0018 - 验收:C(聊天和直播),质量
- 里程碑:M5
- 状态:单元覆盖(
thinking-ui.test.mjs,代理运行时事件测试);全流场景草稿
E2E-054:存储的思维无损重新加载
- 先决条件:完整的辅助消息包含推理和 最终答案块;另一个仅包含推理。
- 步骤:1) 完成两个回合。 2) 重新启动 host/app。 3)重新打开 会议。 4)检查搜索结果和答案副本。
- 预期:主机返回相同的单独
thinking和contentreload/import/replace 往返后的值;两条消息均保留 可见;搜索和答案副本排除推理。 - 链接规格:
03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md、04-ux/08-component-spec.md、ADR 0018 - 接受:C(聊天和流),F(持久)
- 里程碑:M5
- 状态:单位覆盖(主机 message/import 测试、
thinking-ui.test.mjs);完全重载场景草稿
E2E-055:不受支持的提供商安全过渡夹
- 先决条件:
max上推理提供商的会议;目标 提供商包括非推理和稀疏级别的变体。 - 步骤:1)切换到非推理提供商。 2)跑一圈。 3)开关 到前一个级别周围的稀疏变体。 4)发送malformed/legacy 有效载荷缺乏能力或思维领域。
- 预期:非推理持续存在并发送
off;稀疏变体选择 到处都是相同的最近水平;失踪的田地安全回落; 畸形思维不会被渲染,也不会污染答案内容。 - 链接规格:
03-runtime/01-ipc-protocol.md,03-runtime/13-model-catalog-and-selection.md、04-ux/08-component-spec.md、ADR 0018 - 验收:B(模型配置)、C(聊天和直播)、质量
- 里程碑:M5
- 状态:单位覆盖(
thinking-ui.test.mjs,主机验证测试);完整的 UI 场景草稿
E2E-056:工作面板外壳对接和持久化
- 先决条件:应用程序在任何工作区状态下运行。
- 步骤: 1) 重新启动并检查标题栏和应用程序菜单;确认 面板开始关闭。按 Cmd/Ctrl+J 并检查 New 启动器, 然后再次按下以确认打开状态是幂等的 并且没有创建任何选项卡。 2)打开两个不同的文件工件,再次打开相同的第一个文件, URL 预览,并准备一条已完成的 Bash 行。 3)验证 header 是可横向滚动的 tablist:打开足够多的标签,确认只有标签条滚动且
+始终可见,并使用 hover/focus×与中键关闭。 4)打开+的 Tools & panels 菜单,验证 浏览器和当前范围内的插件视图各出现一次,并处于活动状态, 打开-非活动和关闭状态,并且出现转录打开的资源 仅在第二部分。使用指针和键盘打开/选择每个可用视图, 重新打开已有 URL 的浏览器并确认 URL 仍然存在,然后执行 具有 ArrowDown/ArrowUp/Home/End 的行(焦点必须跳过关闭按钮),关闭 删除非活动行并确认菜单保持打开状态并将焦点放在 邻居,按 Esc 键并确认焦点返回到触发器,然后关闭 标题中的活动标签。确认+停留在 最短和最长标签的标题右边缘。 4) 关闭活动的中间和边缘项目 并验证邻居选择;关闭最后一个标签后面板仍显示 New。 5) 使用唯一的会话窗格折叠控件和 触发另一个神器。 6) 在会话 A 中,让面板保持打开状态,并显示多个 选项卡和浏览器资源;切换到会话 B,创建不同的选项卡集, 然后在A和B之间反复切换并选择一个没有活动的项目 谈话。在不可见会话中生成背景工件。 7) 将左边缘手柄拖到 244px 以下以及超过右栏当前动态上限的位置;验证指针向下 不跳过分隔线,使用 Escape 取消一个手势,然后聚焦 处理并执行 Arrow/Shift+Arrow/Home/End。提交不同的宽度 浏览器处于活动状态。 8) 在有足够工作区域的显示器上,记录MainChat宽度, 本机边界和打开时的window/setWorkPanelReservation结果, 重复相同的开放目标,提交分隔线宽度,折叠, 重新打开并关闭最终资源。浏览器在 Windows 上处于活动状态时, 一边观察整个无框窗户,一边重复折叠。 9) 带面板 打开,从左右边缘调整本机窗口的大小;之后重复 切换侧边栏。 10) 在太窄的工作区域上重复 open/resize/collapse 提供完整的预订。 11) 最大化时打开或折叠 全屏,然后恢复正常。 12) 在显示之间移动普通窗口 不同的工作区域,更改活动显示的工作区域几何形状,以及 在一个不变的工作区域内执行普通动作;包括过渡 窗口管理器压缩并重新定位外部窗口之前的位置 显示更改回调。 13) 在打开时注入一项被拒绝的预订 以及折叠时的一个,然后重试每个操作。 14) 发送字符串、布尔值、 空、小数和超出范围的预留有效负载。 15) 重新启动。 - 预期:启动时不显示面板、欢迎选择器、固定工具按钮或 titlebar/menu 启动器。 Cmd/Ctrl+J 打开活动会话的面板 无需创建资源选项卡即可提交宽度;重复它是幂等的, 如果没有活动会话或“设置”处于活动状态,则快捷方式不会执行任何操作 打开。每个工件自动打开停靠的第三列并创建或 激活一种资源;文件资源是路径键控且重复的资源 去重复。打开、折叠和 关闭动画面板的 width/flex 分配及其有界 opacity/slide,因此 MainChat 连续重排,无需预动画跳转。 打开面板、折叠面板或调整分隔线大小 更改操作系统窗口大小 - 仅 MainChat 在内部回流 固定客户区 (ADR 0033)。一旦面板打开,标题栏显示可滚动的标签条和固定
+;+菜单只列出 Review 与当前范围内的插件视图,不重复已打开的标签。标签 溢出时只有标签条滚动,活动标签滚动到可见范围;菜单淡入超过 ≤4px,并在 reduced motion 下保持静态。Arrow/Home/End 在标签或菜单项目间移动, Delete/Backspace 与中键关闭标签,Escape/Tab/选择将菜单焦点恢复到+。 重新打开已存在的工具会激活它并保留浏览器 URL。打开菜单时原生插件表面 裁剪到不透明菜单边界下方,因此不会遮挡内容。 折叠控件位于会话窗格的右上角,而不是内容标题中。 主动关闭选择右邻居,然后选择左邻居;关闭最后一个选项卡会保持面板打开并显示 New。 折叠保留运行时间 选项卡,但隐藏面板,直到另一个工件重新打开它。宽度夹至 三栏共享预算(无固定像素上限),将这些 current/minimum/maximum 值暴露给 辅助技术,并支持记录的键盘步骤。 指针向下保留起始宽度,移动跟随指针 连续,只有当宽度改变时release才提交一次。一个 零移动释放、转义或取消保留了先前的 首选宽度和原生保留。浏览器预览不会拦截 活动分隔线拖动。 A 和 B 独立恢复其运行时打开状态、有序选项卡、活动状态 选项卡和浏览器资源;在没有积极对话的情况下选择一个项目 隐藏面板,并且没有相关资源跨越 session/workspace 上下文。 后台工件仅更新其保留的上下文,而不会更改 可见预订。 在退出动作之前,本机浏览器预览会与窗口分离。开 Windows 码头通过其有界的出口幻灯片保持不透明,并折叠 在本机边界时不产生 white/full-pane 闪存或过时的预览帧 返回到基础宽度。 重启后仅恢复{width};每个会话的打开状态、选项卡、 活动选项卡和浏览器资源重置。打开的面板保持在原来的位置 通过原生边缘和侧边栏更改来提交宽度;这些手势调整大小 仅限 MainChat,永远不会重写首选项。正常状态下,打开返回{requested: committedWidth, reserved: committedWidth}和 grows/shifts 本机窗口位于工作区域内,因此 MainChat 宽度保持不变。重复 目标是空操作。分隔符提交更新目标一次。崩溃和 最终关闭返回{requested: 0, reserved: 0}并对称恢复 基边界和 x 位置。在受限工作区域,reserved报告所有requested以下可添加宽度;面板保持固定并且仅 MainChat 弥补了缺口。 Maximized/fullscreen 调用保留最新的 请求的目标而不改变几何形状,然后在返回时协调一次 正常。 Display/work-area 更改使同一目标与当前目标一致 可用宽度并更新本机最小值;一内的普通运动 未更改的工作区域不会重新应用几何体。系统压缩或 显示转换期间的重定位不会覆盖已确认的基数 边界,返回到更宽敞的显示会恢复之前的聊天宽度。 重新启动恢复用户的窗口大小(本机预留始终为0, ADR 0033)。格式错误的预留有效负载失败并显示INVALID_ARGUMENT和 从不强迫。被拒绝的预订保留 最后一次确认的小组演示,直到后来成功请求;一个 被取代的成功不能带来陈旧的表现。没有过渡产生 第二次调整大小或位置漂移。 预览模式隐藏 MainChat,并将客户区宽度交给面板;本机窗口尺寸不变,分隔线不可操作。 预览外壳仍保留新建任务、侧边栏和系统窗口操作。macOS 非全屏且侧边栏折叠时,预览行把交通灯引导内缩 (--ds-window-lead-inset,即灯簇右缘 76px 加 12px 间隙,共 88px)作为自身的左内边距渲染出来, 左侧第一个预览操作从该渲染值起或之后开始;检查读取的是解析后的内边距,而不是重述这个数字。 打开真实工作面板标签后,其第一个标签从预览操作组右侧至少 8px 处开始;全屏时使用 8px 原生内缩但保留该操作带。 以前的上下文面板覆盖不再存在。 - 链接规格:
03-runtime/01-ipc-protocol.md、04-ux/01-ui-ia.md、04-ux/07-ui-design-system.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、ADR 0032、ADR 0068、D163、D207 - 接受:F(坚持),质量
- 里程碑:M5
- 状态:单位覆盖(
work-panel-resize.test.mjs,work-panel-window.test.mjs、work-panel-presentation.test.mjs、work-panel.test.mjs);完整的 UI 场景草稿
E2E-057:消息拥有的审阅历史记录可以安全提交并回滚
- 先决条件:具有可写工作空间的项目绑定 Agent 会话; 不需要 Git 存储库。
- 步骤: 1) 要求活动代理编辑现有文件并创建新文件 在会话 A 中归档。2) 展开活动组并检查每张复习卡 直接在其相应的工具行之后;验证每张卡的启动情况 崩溃,其 added/modified 状态和 +/- 计数在 标头,然后展开它以验证确切的帅哥。 3)提交文件 在应用程序外部,关闭并重新打开“审阅”面板,然后重新加载会话 A。 4)验证相同的卡 计数仍然存在,因为它们来自转录消息。 5)使用 卡的回滚操作并验证创建的文件是否已删除或以前的 文件字节被恢复。 6) 在应用程序外部再次编辑该文件并重试 回滚;检查冲突结果并验证后面的字节是否保留。 7) 切换到会话 B 和后台项目会话,然后返回到 A。8) 重复失败、拒绝和临时写入。
- 预期:每个成功的工作区 Write/Edit 都会创建一个消息拥有的 查看记录和一张相邻的键盘可访问卡;该卡从来都不是 bottom/global 条目。完成时不打开任何东西:面板保持用户离开时的样子, 审阅只在用户打开后出现。每张评论卡,内联和评论选项卡中,都是 默认折叠并按需扩展。 Review 选项卡列出了 A 按时间顺序记录的更改,独立于 Git 状态、存储库 存在、提交状态、焦点刷新、 或工作空间切换。添加、修改和删除状态加行 当有有限证据可用时,会显示 additions/deletions 和帅哥。 成功回滚会将卡更新为已回滚并在重新启动后仍然有效。 工具后文件更改会返回冲突并且不会覆盖它。失败的, 被拒绝,并且刮擦写入不会创建卡;会话 B 不能继承 A 的会话 记录。二进制或超大快照显示有限元数据并禁用 当先前的字节没有保留时回滚。
- 链接规格:
03-runtime/01-ipc-protocol.md§13a,03-runtime/03-tools-and-permissions.md§4c,04-ux/08-component-spec.md§5、ADR 0043 - 验收:D(工作空间),质量
- 里程碑:M5
- 状态:单位覆盖(
chat-review-entry.test.mjs);完整的 UI 场景草稿
E2E-058:内置交互式终端已移除
- 前提:已打开工作区,Agent 已完成一次 Bash 工具调用。
- 步骤:1) 使用 Cmd/Ctrl+J 打开工作面板并检查空状态和上下文菜单。 2) 确认没有终端选项卡、启动器行、终端专属文案或终端 IPC。3) 确认 已完成的 Bash 行仍显示命令、输出、状态和复制操作,并保留终端图标。 4) 确认交互式 shell 由用户的外部终端承担,而不是由工作面板打开。 5) 构建/打包桌面应用并检查依赖项和解压资源列表。
- 预期:工作面板提供审阅启动器行、浏览器和当前范围内的插件视图;审阅只在用户 主动打开时出现,文件资源由对话打开;不创建 PTY,也无法打开终端选项卡。 Agent Bash 仍是非交互式的,完整显示在对话中。 打包结果不含 PTY/xterm 依赖、终端 IPC 或终端原生负载。
- 链接规格:
02-architecture/02-tech-stack.md、03-runtime/01-ipc-protocol.md、04-ux/08-component-spec.md§5、ADR 0108 - 验收:D(工作空间),质量
- 里程碑:M5
- 状态:单位覆盖(
work-panel.test.mjs、packaging-footprint.test.mjs);完整 UI 场景草稿
E2E-059:嵌入式浏览器预览隔离和覆盖
- 前提条件:本地开发服务器正在运行;存在 URL 或 BrowserPreview 工件。
- 步骤: 1) 激活工件,输入
localhost:<port>(不带方案),然后提交。 2) 导航站点链接;使用 back/forward/reload/stop。 3) 触发window.open弹出窗口和权限请求页面(例如通知 提示)。 4) 打开全局搜索,然后打开设置。返回聊天 并触发内联工具权限卡。 5) 切换到另一个面板选项卡 然后回来;关闭面板。 6) 使用开放式外部。 - 预期:无方案输入标准化为 http;导航状态(URL 栏, back/forward 启用、加载微调器)镜像页面。弹出窗口仅在 URL 解析为 http(s) 或 mailto 时打开默认浏览器(绝不在应用程序内);
file:、javascript:和自定义 scheme 被拒绝。许可请求被拒绝; 非 http(s) 导航被阻止,根内file:同级文件除外。预览隐藏在每个遮挡下 覆盖并卸载时,随后以正确的边界重新出现。 内嵌权限卡不会隐藏或重新挂载预览;resize/drag 期间原生视图保持可见, 并与占位符矩形对齐,不再闪出黑色面板底色。打开工作面板上下文下拉框时, 原生视图在不透明菜单边界下方保持可见,菜单关闭后恢复完整表面矩形。 Open-external 对 http(s) 页走系统浏览器,对根内文件预览走openPath。 视图使用隔离的持久分区(应用程序外壳中没有会话流失)。 - 链接规格:
03-runtime/01-ipc-protocol.md§13a、ADR 0019、ADR 0168 - 验收:质量、安全
- 里程碑:M5
- 状态:草案(手动)
E2E-BROWSER-session-preview-race:切换会话不显示过期预览
- 前置条件:两个会话具有不同 HTML 预览,浏览器面板上下文分别保留。夹具可以独立 延迟目录查询和文档加载。
- 步骤:预览 A,在后台生成 B 的预览;折叠 A 的面板后打开 B,延迟 B 的目录查询和 加载,然后切回 A。快速切换 B → C,最后才完成旧 B 的目录查询。再分别验证已经开始的 旧加载、空会话、加载失败和 guest 销毁。
- 预期:后台 B 不导航 A。切换后,B 的面板不显示 A 的文档,后续尺寸与可见性更新 也不能显示它。只有最新导航可以显示 guest,旧 B 查询迟到不能覆盖 C。切回 A 恢复 A, 空会话保持空白。迟到完成不能撤销关闭或销毁。同会话正常导航保留当前页面。 非法目标、失败或超时不能把上一个文档报告为目标会话已经就绪。切换导航超过既有 15 秒等待上限后保持隐藏,需重试,不承诺迟到完成自动显示。
- 关联规范:
04-ux/08-component-spec.md§5.3;ADR 0028、ADR 0170。 - 状态:
browser-host-session.test.mjs与browser-pane-navigation.test.mjs覆盖生产 BrowserHost/BrowserPane 服务路径,控制原生浏览器与 Host 边界,采用确定性 时钟。尚不覆盖原生 Electron 合成显示或报告者的真实会话。
E2E-BROWSER-in-page-navigation:浏览器工具栏跟随同文档导航
- 前置条件:启用 Browser 插件;本地网页包含锚点链接及 History API 路由控件。
- 步骤:打开网页;点击锚点链接;通过
history.pushState切换路由; 使用后退、前进和刷新。切换至另一会话,检查旧页面的迟到事件不能更新新预览。 - 预期:地址与主文档 URL 一致,历史按钮状态正确;加载结束后恢复刷新按钮。 子框架、已替换框架及失效会话的事件不能改变工具栏。
- 覆盖:
browser-pane-navigation.test.mjs验证状态发布与事件隔离; 原生 Electron 操作验证真实同文档导航事件。 - 关联规范:
04-ux/08-component-spec.md§5.3。
E2E-060:文件选项卡浏览停留在工作区中
- 先决条件:具有嵌套源文件工件的工作区,大型 (>512KB)、图像和二进制文件。
- 步骤:1) 激活每个文件工件并验证不同的路径键控 标头切换器中的资源;浏览树,展开嵌套文件夹。 2)开源 文件、图像、二进制文件和大文件。 3) 使用在 Finder 中显示。 4) 尝试通过 devtools IPC 进行遍历读取 (
../outside)。 5) 开关 工作区。 - 预期:目录懒惰地列出,文件夹首先,带有
.git/node_modules/ 隐藏构建输出;文本以语法高亮显示 (上限为 5000 行),图像预览内联、二进制和超大文件 显示后备并显示仍然可用。遍历尝试是 被INVALID_ARGUMENT拒绝;无工作空间→空状态;切换 工作区重置树和查看器。 - 链接规格:
03-runtime/01-ipc-protocol.md§13a、ADR 0019、03-runtime/15-workspace-ignore-rules.md - 验收:D(工作空间),安全
- 里程碑:M5
- 状态:单位覆盖(
fs-panel-guard.test.mjs);完整的 UI 场景草稿
E2E-059a:记录消息板遵循 WorkBuddy 密度
- 前提条件:一个会话至少包含一个简短的用户提示,一个 更长的用户提示和完整的助理答案;浅色和深色主题 可用。
- 步骤:1)以深色主题打开会话。 2)检查用户和助手 静止和悬停时的行。 3)启动流媒体助理应答。 4) 开关 点亮主题并重复。 5) 使用键盘聚焦复制控件。
- 预期:用户回合右对齐,主题中性软板封顶 接近 560px,源自每个主题的主要文本墨迹而不是重音 色调,带有微妙的边框;助理答案保持透明全角 720px 内容带中的散文。行间距更密集(~10px)。复制芯片有 静止时隐藏,出现在 hover/focus-within 上,并在下面保持右对齐 用户轮流。流媒体助理的答案显示出轻微的口音左规则,没有 拳击整个答案。两个主题都对用户保持可读性对比 板。
- 链接规格:
04-ux/07-ui-design-system.md,04-ux/08-component-spec.md、04-ux/10-workbuddy-benchmark-ux.md - 验收:C(聊天流),质量
- 里程碑:M5
- 状态:单位覆盖(
transcript-style.test.mjs);全视觉场景草稿
E2E-CHAT-transcript-context-menu:右键消息或记录
前提条件:会话中有一条完成的用户提示和一条完成的助手回答;对话面板已聚焦。
步骤:1)右键用户板。2)选择复制,再选择选中消息文本。3)在用户板里选中一段文字,再右键该板并选择复制;收起选区后再右键复制一次。4)右键助手回合并选择复制。5)右键最后一回合下方空白处,选择复制整个对话。6)在打开的菜单上按 Escape,再按 Tab。7)右键回答里的 markdown 链接。8)编辑用户消息、替换内容、选中一段草稿并右键复制;再验证无选区复制、选中消息文本和取消编辑后的复制。
预期:用户菜单列出复制、选中消息文本、编辑,以及分隔后的删除;助手菜单列出复制、选中消息文本、重新生成和分叉。复制写入打开菜单时该行内的选中文本;光标折叠或选区在行外时退回整段。复制整个对话仍写入带说话人标签的整段对话。两者都显示 toast。选中文本会高亮气泡。Escape 和 Tab 关闭菜单且不执行项。链接仍提供在默认浏览器打开、在工作面板打开、复制链接地址。引用、批注、打开侧边聊天不出现(ADR 0268)。表面是视口固定的文档级层,不会改变记录高度。
链接规格:
04-ux/08-component-spec.md§8.3 / §8.5,04-ux/09-interaction-patterns.md(浮动下拉表面), ADR 0268验收:C(聊天流),质量
里程碑:M5
状态:单位覆盖(
chat-context-menu.test.mjs、chat-context-menu-items.test.mjs、chat-context-menu-surface.test.mjs); 完整 UI 场景草稿完整历史回归:打开包含 140 条消息的会话,不向上翻页,选择复制整个对话;剪贴板必须包含第 1–140 条消息。从搜索上下文窗口和长消息截断预览重复验证。保留当前可见的生成中文字;读取失败时提示错误,剪贴板保持不变。仅在选择复制后读取完整历史,不改变阅读位置。运行
node scripts/e2e-copy-conversation.mjs。编辑时复制应使用草稿选区,无选区时复制整份草稿;选中消息文本应选中草稿。菜单不提供编辑、删除或版本切换;取消后原消息及其菜单保持不变。自动化验证:
node scripts/e2e-message-edit-copy.mjs。
E2E-CHAT-copy-formula-as-tex:复制渲染后的公式得到源码
复制之后公式的边界仍然可解析:相邻的行内围栏之间补一个分隔符;正文里的每个美元 符号都会被转义,本会把围栏转义掉的反斜杠串同理。annotation 里的空白原样保留; 加宽过的多行行内公式用一个字面的 <span> 包住,以免它粘在行首时开出块级公式。 TeX 里的换行不做压平,因为它可能用来终止 % 注释。这个包裹是 text/plain 里的 Markdown 源码,不是 text/html 负载;对禁用行内 HTML 的外部编辑器不作兼容承诺。 回归覆盖两个复制入口,以及相邻公式、围栏两侧的正文美元符号、格式化包装、行与块 的边界、补白,以及含 TeX 注释的多行公式各自的 Markdown 往返。 含 - x、+ x、* x、> x 或内部空行的块级公式,复制之后必须仍是同一个公式 节点。共享的 remark 语法把尚未闭合的公式留在流式尾块里直到围栏闭合,其后的正文 及其源码偏移保持不变。脚注定义出现在引用之后——紧随其后,或稍后才流式到达——会 渲染成真正的引用与脚注区,而不是字面的 [^1];这正是把切片脱离整条消息单独解析 时,按块切分要付的代价。CRLF、普通列表、引用块、GFM 表格、围栏代码,以及那些 必须继续切分的源文,由 markdown-blocks.test.mjs 覆盖。
- 前提条件:助手回答里既有句中的行内公式,也有独占若干行的块级公式,还有句中的
\[ … \]公式与独占若干行的\[ … \]公式、一个\( … \)公式、一个含字面$的公式、一个值里带换行的行内公式与另一个值的两端各带一个换行的行内公式,以及一段不含公式的正文;另有与公式同处一个选区的:源码跨多行折行的段落、无序列表、表格、一个单元格里放着\[ … \]公式的表格、内含空行的围栏代码块,以及一个正文被 chrome 打断的回合,该 chrome 仅因继承外壳的user-select: none而不可选(工具行的分节标题)。 - 步骤:1)选中含行内公式的整句并按 Ctrl/Cmd+C。2)选中块级公式并复制。3)从公式中间选到该句末尾并复制。4)把一段正文、一个公式和另一段正文一起选中并复制。5)选中不含公式的正文并复制。6)分别复制折行段落、列表、两个表格和代码块。7)分别复制两个
\[ … \]公式、那个\( … \)公式、含字面$的公式,以及值里带换行的那两个公式。8)从句末反向选到句首选中一个公式并复制。9)在公式处于选中状态时右键该回合并选择复制,再右键另一个回合并选择复制。10)按「选中文本」菜单项的方式选中整个回合的内容——选区锚在回合上,而复制事件落在回合内部的某个段落上——并复制。11)选中一个始终不越出该公式的范围——在它的渲染内部拖选——并复制。12)把每次结果重新交给同一个回答渲染器渲染一遍——即粘回输入框并发送后得到的东西——再把它画出的公式与选区覆盖的公式逐一比对。 - 预期:每个公式都以它被写下时的 TeX 进入剪贴板——行内
$…$,块级$$…$$独占行——而不是 KaTeX 画出的字形,也不是两棵树带来的同一个表达式的两份。含字面$的公式会像代码段的围栏那样把定界符加长到盖过它,粘回去仍是公式而不是普通文字;值里带换行的行内公式则保持窄形式,因为$$落在行首会开出块级公式并吞掉整段;值的两端各带一个换行的那个,复制时会自己补上一对空白,因为语法在这里吃掉一个换行就像吃掉一个空格。从公式中间开始的选区会复制整个公式,而始终不越出某个公式的选区只复制出该公式本身。选区里其余内容与平台原本复制到的逐字节一致:折行段落仍是一整行,列表每项一行,表格单元格之间是制表符、每行一行并保留平台为末行补上的换行,代码块保留自己的空行。复制本就会略过的 chrome 不会进入剪贴板:无论base.css是按选择器把它标为user-select: none(代码块的语言标签),还是它仅因继承外壳默认值而不可选(工具行的分节标题、紧凑思考行)。不含公式的选区交回平台原样写入;含公式的选区则无论复制事件落在它内部多深的位置,都以源码进入剪贴板——所以锚在整个回合上的选区(即「选中文本」产生的那种)也会复制成 TeX,哪怕 Chromium 把事件抬在了回合内部的某个段落上。右键复制与 Ctrl/Cmd+C 对同一个选区得到同一个字符串,而右键一个并不持有该选区的回合读不到任何摘录,因此复制退回该回合自己的源码。粘回的公式渲染回它来处的那个公式——TeX 相同,块级仍是块级——\[ … \]也不例外:它被画在所属句子的段落里,复制出来时$$独占一行,足以在粘回去时开出块级公式;\( … \)保持行内。表格单元格同样适用,且这一条是实测而非推断:.katex-display盒子对平台自己的读法同样会断开该行,因此围栏旁边不会出现制表符,公式仍以块级形式复制,相邻单元格完整地落在自己的行上。反向选区在复制之后仍然是反向的。复制只写text/plain——不会把text/htmlflavour 放上剪贴板。 - 链接规格:
04-ux/08-component-spec.md§8.7,ADR 0268, 决策日志 D619,issue #414 - 验收:C(聊天流),质量
- 里程碑:M5
- 状态:已自动化(
pnpm test:e2e:copy-tex,真实 Chromium),另有selection-tex.test.mjs
E2E-060b:镀铬中性灰色调
- 先决条件:应用程序在深色和浅色主题中运行;插件页面和 可以使用 Markdown links/blockquotes 聊天。
- 步骤:1) 检查对焦环、主按钮、切换开关、选定的 会话环,插件市场主要 CTA。 2)打开助理答案 链接和块引用。 3)切换主题并重新检查。
- 预期:不再保留蓝色品牌口音。交互式口音、Markdown links/rules,插件主要操作通过中性灰色解析 重音标记(
white/gray深色、深色墨水浅色)。插件 installed/market UI(选项卡、搜索、卡片、权限模式和主要/ 辅助按钮)仅使用--ds-*令牌,在 任一主题;按钮表面和墨水在深色模式下仍然可见。语义学 success/warning/error 颜色保持不变。 - 链接规格:
04-ux/07-ui-design-system.md,04-ux/08-component-spec.md - 验收:质量
- 里程碑:M5
- 状态:单位覆盖(
neutral-accent.test.mjs,plugins-page-style.test.mjs);视觉场景草稿
E2E-060c:辅助 Markdown 散文层次结构和代码 chrome
- 先决条件:完整的助理答案,包含标题、 blockquote,GFM 表,带有语言标签的围栏代码块,内联 代码、任务列表和远程图像链接;提供浅色和深色主题。
- 步骤:1)以深色主题打开会话并滚动答案。 2) 将鼠标悬停在代码块复制控件和表行上。 3)拓展思维 包含降价的披露。 4)切换到浅色主题并重新检查 内联代码、块引用规则和代码卡的对比。
- 预期:答案散文使用
.prose-chat层次结构(h1–h6 斜坡, 带重音的块引用、细线边框内联代码、zebra/hover 表 shell,带有等宽语言标签的插入代码卡)。宽 GFM 表格留在转录列宽内: 表头与单元格换行,而不是横向溢出。思考散文留下来 secondary/smaller 并且不会合并到答案中。两个主题都保留 可读对比度;复制仍然复制原始栅栏文本。 - 链接规格:
04-ux/07-ui-design-system.md,04-ux/08-component-spec.md§8.7 - 验收:C(聊天流),质量
- 里程碑:M5
- 状态:单位覆盖(
user-select.test.mjs、thinking-ui.test.mjs、markdown-prose-style.test.mjs);全视觉场景草稿
E2E-061:用户消息纯文本布局在包装和重新加载中仍然存在
- 先决条件:已配置提供商;输入框可以接受多行输入 通过 Shift+Enter(或禁用 Enter-发送)。
- 步骤:1) 编写一个三行提示符,其中包含两个硬换行符和一个 URL 其编码路径比用户板宽。 2)发送。 3)检查用户 文字记录中出现气泡。 4) 复制用户消息并粘贴到外部 编辑。 5) 重新加载会话。
- 预期:用户板显示三个不同的行(未折叠成 单段)。链接的 URL 包裹在板内,没有水平 溢出,并且每个连续行保持逻辑开始与 第一行而不是居中。复制的文本保留原始内容 换行符。重新加载后,相同的换行符仍然存在。
- 链接规格:
04-ux/08-component-spec.md - 验收:C(聊天流),质量
- 里程碑:M5
- 状态:单位覆盖(
transcript-style.test.mjs);全视觉场景草稿
E2E-060d:助理元芯片、紧凑上下文摘要和重试操作
- 前提:完整的辅助消息包含 modelId 和 token 用法;另一条已完成的 助理消息有内容但没有用量。所选模型发布了 1m 级上下文窗口,但 provider binding 仍含有历史 128k 通用种子。
- 步骤:1) 打开会话。2) 确认已完成回合答案下方只有模型徽章、没有 上下文检查器。3) 将鼠标悬停在输入框工具栏的检查器触发器上,确认面板 不会打开,然后点击它。4) 检查剩余令牌 + 百分比标题、已用/窗口计数、 无卡片的本轮/速度数值、内联模型用量摘要和聚合工具摘要,确认标题下 没有双线、弹层内部没有分隔线。5) 面板打开时滚动记录、调整窗口大小。 6) 将鼠标移出面板,然后点击触发器、面板外部,并使用 Escape 关闭。 7) 在浏览器预览产物上打开工作面板,打开上下文检查器,确认该摘要仍留在 会话面板内:完全可见、不进入面板列,且在较窄面板上收窄而不是被裁剪。 8) 空闲时点击该回合的重试。9) 确认没有用量的会话仍提供重试但不显示 输入框检查器。
- 预期:有模型 id 时答案下方显示模型徽章。有用量后,紧凑上下文 检查器出现在输入框右侧工具栏、模型选择器左侧,并始终对应最新一条 已报告用量的助手回合。触发器显示圆环和剩余容量百分比(无 Context 文字)以及低容量 warning/error 状态,点击或键盘激活打开同一摘要, 而单纯悬停不会打开或关闭。摘要保留精确的提供商数值,并用
~标识 估算的工具合计;默认不渲染逐工具列表、来源徽章、进度条、解释性估算 段落或内部分隔线。占用、本轮合计和模型缓存/输入/输出/推理/命中率取 最后一条已报告用量的助手消息,而不是工具循环加总,因此缓存读取与 上下文窗口同量级。没有缓存读取元数据时省略缓存命中率而不是推算。 发布的 1m 级窗口(例如gpt-5.6-luna的 1,050,000 tokens)显示为 实际窗口而不是 128k,且与 agent runtime 使用同一个 effective window; 非默认 Advanced 覆盖仍然生效。生成速度只代表已完成回合。重试重新 发送最近的用户提示,并在回合运行时禁用;body 级面板留在会话面板内,因此 工作面板的原生浏览器或插件表面永远不会盖住它;会话面板较窄时它会随面板 收窄,而不是越过该边界,并跟随滚动或调整大小;复制仍不包含思考文本。 - 链接规格:
04-ux/08-component-spec.md、04-ux/10-workbuddy-benchmark-ux.md、03-runtime/01-ipc-protocol.md - 验收:C(聊天流),质量
- 里程碑:M5
- 状态:单位覆盖(
transcript-style.test.mjs、context-usage.test.mjs、latest-turn-context.test.mjs、运行时用量映射);完整场景草稿
E2E-061a:重新生成替换当前回合
- 前提:一个会话有用户 A → 助理 A → 用户 B → 助理 B。
- 步骤: 1) 将鼠标悬停在助手 A 上,然后单击“重新生成”。 2)等待新的 转动完成。 3) 重新加载会话。
- 预期:脚本截断了助理 A / 用户 B / 助理 B 在重做开始之前;只剩下用户 A 加上新的 assistant/tool 尾部。 重新生成的答案不会留下其上方的旧分支。重新加载保持 仅截断的分支。
- 链接规格:
04-ux/08-component-spec.md,03-runtime/01-ipc-protocol.md、03-runtime/04-data-storage.md - 接受:C(聊天流),F(持久)
- 里程碑:M5
- 状态:单元覆盖(store/main 截断接线测试);完整场景草稿
E2E-062:重新生成历史寻呼机恢复之前的变体
- 先决条件:助理答案至少重新生成一次的会话。
- 步骤: 1) 在完成的辅助回合上单击 Retry/Regenerate。 2) 替换回合开始时观察可见的 root 用户气泡, 完成后。 3) 切换到以前的版本。 4) 向前切换 再次。 5) 重新加载会话。
- 预期:根用户气泡仍然可见并显示 一旦该行悬停或在其操作工具栏中显示
current / total寻呼机 专注;默认情况下,工具栏(包括寻呼机)是隐藏的。重试确实如此 不要移动选择器或将其与气泡分离。切换恢复 已存档 assistant/tool 分支到位。重新加载保留活动变体 以及完整的修订集。 - 链接规格:
04-ux/08-component-spec.md、03-runtime/04-data-storage.md - 接受:C(聊天流),F(持久)
- 里程碑:M5
- 状态:单元覆盖(
sessions::tests::save_and_activate_message_revision,架构 v4 迁移);完整场景草稿
E2E-063:空荡荡的家让主要任务表面保持专注
- 先决条件:应用程序在空聊天主页(无文字记录)上运行 和黑暗主题;窗口大小可以调整为~1200×690 和~900×640。
- 步骤:1)打开空房子。 2)确认英雄包含安静标志, 本地化标题和简短的支持台词。 3)确认没有开发者启动器 渲染卡片或上下文快速操作行。 4) 拒绝新手引导并 再次检查。 5)在另一个主题中重复。 6) 调整为较短的高度并 如果需要,滚动内容区域。
- 预期:默认的空状态使英雄和底部输入框保持不变 视觉锚点,可选的入门作为唯一的附加内容。 忽略检查表不会留下任何空白间隔。输入框仍留在 底部不覆盖英雄或清单,并且短窗口保留了每个 可通过滚动到达内容块。
- 链接规格:
04-ux/01-ui-ia.md、04-ux/07-ui-design-system.md、04-ux/08-component-spec.md、08-meta/decisions-log.md(D111/D131/D204/D206) - 验收:质量(布局完整性)
- 里程碑:M5
- 状态:单位覆盖(
home-empty-layout.test.mjs);完整的 UI 场景草稿
E2E-094:主动回合保持下转录本表面清晰
- 前提:确定性代理可以发出思考,工具start/end, 流式传输应答、许可和终端事件以实现持久会话;一个 第二个会话可以在后台运行。
- 步骤: 1) 在可见会话中开始一轮。 2)观察成绩单 当代理思考、使用工具并传输答案时。 3) 触发 许可请求并检查批准卡。 4)切换到第二个 会议,而第一个会议仍在继续。 5) 之后返回第一个会话 完成。
- 预期:没有一般理解、工作、检查或完成 当回合处于活动状态时,卡片会出现在成绩单下方。助理和 工具行保持内联;整轮保留一行紧凑底部状态,包括部分回答暂停和工具 完成后的空档。具体阶段优先于规划/目标或工作中,等待用户操作时隐藏。 只有实际的权限请求才会呈现可操作的权限卡。后台活动永远不会改变可见会话, 成绩单、输入框焦点或项目。
- 链接规格:
04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md、03-runtime/10-session-state-machine.md - 验收:C(聊天流)、质量(交互和可访问性)
- 里程碑:M5
- 状态:单位覆盖(
active-turn-surface.test.mjs);完整的 UI 场景草稿
E2E-095:终端故障暴露恢复而没有成功卡
- 先决条件:确定性提供商可以从失败中恢复 目录
Read和Glob,通过工作区编辑完成一圈,然后失败 另一轮出现可重试错误;该会话有一个可见的输入框。 - 步骤: 1) 运行失败-读取然后成功-Glob 恢复轮次并 检查其活动组。 2) 完成工作区编辑回合。 3) 确认 没有出现成功结果卡并检查内联审核卡 紧接在更改工具行之后。 4)展开内联卡并验证 它的帅哥。 5)承诺 编辑好的文件并确认录音卡仍然存在,然后使用回滚 一次。 6) 触发可重试失败。 7) 检查故障卡,然后 选择重试。 8) 启动另一个新提示并检查旧卡。
- 预期:恢复的回合使失败的读取在其自己的行上可见, 将包含组标记为已处理,完成其会话结果,并且 显示无故障卡。完成使用成绩单和内联审核卡 作为其证据,无需添加“任务完成”卡。文件状态、计数、 提交后,帅哥仍保留在相邻卡上,并受保护的回滚 恢复工具前的状态。失败表明现有的工作仍然存在, 公开重试和继续,重试保留最新的提示。新的转机 清除之前的故障卡;中止不会创建失败结果副本。
- 链接规格:
04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md、03-runtime/10-session-state-machine.md、 ADR 0069 - 验收:C(聊天流)、质量(完成和恢复)
- 里程碑:M5
- 状态:单位覆盖(
assistant-turns.test.mjs,interaction-performance.test.mjs、turn-outcome-card.test.mjs);完整的用户界面 场景草稿
E2E-064:持久通知收件箱记录终端任务结果
- 先决条件:存在两个持久会话;确定性提供商可以 完成一回合,失败一回合并出现稳定的错误代码,以及中止一回合 转;通知收件箱开始为空。
- 步骤:1) 聚焦并查看会话 A,然后在 A 中完成一个回合。2) 当 仍然专注于 A,在后台会话 B 中失败。 3) 取消聚焦 窗口并完成 A 中的另一轮。 4) 中止第四轮。 5) 重复每一个 终端 RPC。 6)确认主标题栏没有响铃,然后打开响铃 扩展的侧边栏页脚并在“全部”和“未读”之间切换。 7)标记一 row 读取并确认其会话没有终端侧边栏标记,然后 close/reopen 弹出窗口并重新启动应用程序。 8) 选择其他会话 从其带有终端标记的侧边栏行。 9) 用205生成一个主机装置 符合资格的终端回合。 10) 使用“标记全部已读”,然后“清除”。
- 预期:A 的可见当前完成不会创建任何行。正好两行 存在,最新的优先:未聚焦的 A 完成和背景 B 失败, 带有本地化标签、快照会话标题和 B 的稳定代码。 Abort/repeated 终端调用不创建行。前页脚帮助快捷方式 缺席; 32px 页脚钟形件及其向上开口的弹出框取代了它。 徽章和未读显示确切的未读计数,无需隐式打开 读取行。读取状态和两者 记录在重新启动后仍然有效。行选择标记其已读取并激活其绑定 project/session。该装置准确地保留了最新的 200 行。全部标记 保留零个未读行。选择其他会话会清除其 终端侧边栏标记并标记其任务通知已读;都没有标记 刷新或重启后返回。清除仅清空收件箱并离开 会议、回合和成绩单完好无损。
- 链接规格:
03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md、03-runtime/01-ipc-protocol.md、04-ux/07-ui-design-system.md、04-ux/08-component-spec.md、08-meta/decisions-log.md(D117/D130) - 验收:C(回合完成)、F(持久)、质量
- 里程碑:M5
- 状态:草案
E2E-065:本机任务通知仅无焦点并激活会话
- 前提:支持原生通知;会议 A 和 B 存在;主窗口可以聚焦、不聚焦、隐藏和最小化。一个 Windows 运行使用 NSIS 安装的应用程序或标准开发命令。
- 步骤:1) 让应用程序专注于 A 并在 A 中完成一个回合。2) 当 仍然专注于 A,在 B 中完成一个回合。3) 在 A 仍然存在时取消应用程序的焦点 当前并完成 A 中的另一回合。4) 等待 A 的通知进入操作系统通知中心,然后 单击它。5) 最小化应用程序,再次失败,然后单击其本机通知。 6) 取消应用程序的焦点并中止回合。 7) 重复并抑制本机传递 由操作系统。 8) 在 Windows 上,检查本机通知属性, 通知设置条目、任务栏组、已安装的可执行文件和开始 菜单快捷方式。
- 预期:当前焦点 A 既不创建收件箱行,也不创建本机横幅。 聚焦背景 B 创建一个没有本机横幅的收件箱行。不专心 当前 A 和最小化故障分别创建一个持久行和一个 本地化的本机通知。单击可恢复、显示并聚焦 激活匹配会话之前的主窗口,即使通知已经进入 Windows 操作中心; 没有事件打开错误当前选定的会话。中止不显示两个表面。操作系统抑制确实 不会丢失持久行或出现误导性应用程序错误。每检查一次 Windows系统表面识别
PI-Desktop;无库存 Electron 应用程序 姓名或身份被暴露。 - 链接规格:
03-runtime/01-ipc-protocol.md,04-ux/07-ui-design-system.md、04-ux/09-interaction-patterns.md、08-meta/decisions-log.md(D117/D141) - 验收:C(回合完成),质量
- 里程碑:M5
- 状态:已覆盖源代码契约(
notification-contract.test.mjs);打包版 Windows 操作中心激活仍需运行环境验证;完整 UI 场景草案
E2E-065a:本机交互询问通知按会话感知
- 前提:支持本机通知;会话 A 和 B 存在;可以暂停并解决确定性的 asktool、工具权限和 Plan 审批询问。
- 步骤:1) 聚焦并查看会话 A。2) 在后台会话 B 中触发 asktool、工具权限 或 Plan 审批询问。3) 在当前聚焦的会话 A 中触发相同类型的询问。4) 取消应用 的焦点或将其最小化,在 A 中触发询问。5) 点击 B 或 A 的本机通知并检查当前 选中的会话。6) 解决每张行内卡片并检查持久通知收件箱。
- 预期:聚焦背景会话 B 的交互询问产生一条本地化本机横幅,但不会创建 持久任务收件箱行。聚焦当前会话 A 的询问只显示行内卡片。未聚焦或最小化时 在 A 中触发的询问产生一条本机横幅。点击横幅会在选择询问所属会话之前恢复、 显示并聚焦主窗口;不会激活点击前碰巧选中的会话。解决询问不会留下合成的 任务完成/失败行。
- 链接规格:
03-runtime/01-ipc-protocol.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、08-meta/decisions-log.md(D117/D350)、ADR 0187 - 验收:C(对话与流)、质量
- 里程碑:M5
- 状态:草案
E2E-066:提供商模型目录在重启和离线刷新后保持可用
- 先决条件:已保存的提供商匹配 models.dev 的提供商/API URL,并包含至少两个 目录模型;确定性测试夹具能分别让
https://models.dev/api.json和提供商发现端点不可用。 - 步骤:1)打开提供商模型选择器,确认显示 models.dev 模型名称、限制、能力标记和来源。 2)检查网络夹具,确认提供商 API key 从未发送给 models.dev。3)退出并重启应用。 4)断开 models.dev 和提供商端点。5)打开 Composer 模型菜单并等待刷新回退。 6)仅恢复提供商端点,返回一个自定义模型,然后重新打开选择器。 7)预热一个已知模型和一个未知模型的查询,再刷新目录夹具,使其包含更新后的元数据 和此前未知的模型。8)让目录刷新失败后重复检查。
- 预期:首次打开选择器即呈现已配置的模型/提供商缓存,不从空列表开始。 重启时无需网络即可使用随应用提供的 models.dev 发布快照。设置页刷新失败会保留该内存 快照;自定义提供商仅针对 models.dev 未收录的 ID 回退到其端点,最后回退到已配置的绑定。 离线刷新保留所有缓存和配置条目。提供商发现成功时可将规范化 ID 持久化到 Rust 拥有的 SQLite,但不能替换 models.dev 元数据或用户定义的绑定。目录刷新成功会同时替换缓存中 命中和未命中的结果;刷新失败保留之前的结果。修改模型绑定或默认提供商/模型无需重启 应用即可生效。
- 链接规格:
03-runtime/04-data-storage.md,03-runtime/12-provider-config-schema.md,03-runtime/13-model-catalog-and-selection.md、04-ux/08-component-spec.md - 验收:B(模型配置)、F(持久性)、质量、安全性
- 里程碑:M5
- 状态:单元测试覆盖(
providers::tests、model-cache.test.mjs、models-dev-catalog.test.mjs);完整重启/离线 UI 场景为草案
E2E-080:Claude Opus 5 从固定的 pi-ai 目录中解析
- 先决条件:桌面位于
@earendil-works/pi-ai/pi-agent-core^0.82.1+;自定义或与 Anthropic 兼容的提供程序使用 apiStyleanthropic_messages(或与 OpenAI 兼容的chat_completionsanthropic/claude-opus-5)和模型 IDclaude-opus-5。 - 步骤:1) 打开 Composer 模型 × 推理菜单,在“模型”子菜单中选择
claude-opus-5。2)打开“推理级别”子菜单。3)开始一个短回合并检查 sidecar 模型 快照/请求元数据(或单位等效解析路径)。 - 预期:id 映射到固定的 pi 目录记录(1M
contextWindow,适应性思维兼容,已发布的思维水平包括contextWindow/xhigh)。思维控件出现并仅列出这些级别。 提供商设置不能覆盖限制。 pin 中仍然不存在 ID 停留在通用的非推理后备上。 - 链接规格:
02-architecture/02-tech-stack.md,03-runtime/11-provider-model-system.md,03-runtime/13-model-catalog-and-selection.md、ADR 0027、D136 - 验收:B(模型配置)
- 里程碑:M5
- 状态:单位覆盖(
model-capabilities.test.ts);完整的 UI 场景草稿
E2E-067:平台应用程序菜单和窗口镶边
- 先决条件:原生 macOS、Windows 和 Linux 运行程序;内置桌面 应用程序;提供英语和中文语言环境。 Windows/Linux线束可设置
PI_DESKTOP_START_MAXIMIZED=1在启动之前,以便 Main 最大化隐藏 渲染器安装之前的本机窗口。 - 步骤:1) 在 macOS 上,启动
pnpm dev和打包版本。确认 应用程序菜单标题为 PI-Desktop,打开“关于 PI-Desktop”,然后检查 它的名称、版本和图标。然后打开每个系统菜单并调用“新建任务”、“打开” 项目、设置、全局搜索、侧边栏切换、编辑、 zoom/fullscreen、窗口、帮助、日志和检查更新操作。验证 更新状态报告当前夹具版本是最新的。 2) 在 Windows/Linux 上,确认没有出现 File/Edit/View/Window/Help 菜单栏 窗口内部和左侧导航占用了回收的空间 标题栏空间。验证 F10 和 Shift+F10 不被 shell chrome 消耗; 练习新任务、打开项目、设置、关闭窗口、缩放、全屏、 全局搜索(Cmd/Ctrl+K 和 Cmd/Ctrl+Shift+P)、侧边栏和标准编辑快捷方式。调用 从“设置”->“信息”检查更新是否具有相同的状态结果。 3) 关闭macOS窗口,立即调用两个原生菜单 命令,并在替换加载后确认渲染器准备就绪。 验证每个命令的一个窗口和一个传递。 4) 在 Windows/Linux 上,重复 从主聊天、设置和打开的工作面板。与工作面板 打开,确认固定于视口的切换按钮与原生窗口控件停留在窗口右边缘、位于 面板标题带之上,资源切换和关闭操作仍可从其左侧的面板菜单进入,并且 原生窗口控件本身仍能响应悬停与点击——点击控件不应移动窗口。在 主聊天,发送第一条用户消息并确认其完整的气泡在下面开始 46px 标题栏控制带。打开扩展页面并确认其标题 操作,然后是详细信息表的关闭按钮,也从该区域下方开始, 他们自己点击而不是移动窗口。单击中心加 顶部、底部和 每个右侧控件面向标题栏的边缘可最小化、最大化、 恢复,然后关闭窗口。 5) 开始 当其本机窗口已经最大化时渲染器并检查 初始查询glyph/state。 6) 尝试未知的 menu/window IPC 操作 当窗口存在时以及窗口关闭后。 7) 在其基础上构建每个目标 来自干净的发布主机目录的本机运行器。在 Windows 上检查已安装应用的 任务栏按钮和“开始”菜单快捷方式图标。 - 预期:macOS 开发和打包发布显示 PI-Desktop 作为 本机应用程序标识,并且“关于”面板使用规范 PI-Desktop 图标;两个表面都不会暴露库存 Electron 名称或图标。 macOS 遵循本机菜单约定和加速器。 Windows/Linux 窗口内不显示应用程序菜单;导航和 右侧控件不会与拖动区域、键盘快捷键发生冲突 保持运行状态,并且工作面板切换按钮存在于非设置路由。打开面板时, 本机控件带和切换按钮覆盖面板标题;标题带在其盒子于控制带之前结束, 因此窗口控件与资源关闭按钮都不会落在拖拽矩形之下,面板菜单也不与 Windows 原生关闭控件重复呈现标题关闭按钮。检查更新 从 macOS 系统菜单和 设置表面并显示结果的最新状态。更换窗 命令等待渲染器准备就绪,无需 创建重复的窗口或丢失事件。没有主面板、设置面板或工作面板 拖动矩形与保留的控制区域重叠。窗口控件保留 可在其完整的 46 像素高的命中目标上单击,匹配本机状态,并且 有易于理解的名称;第一个用户或助理成绩单行从不 在它们下面绘制,扩展页面标题操作或 插件详细信息表关闭按钮。未知操作失败关闭。已安装 Windows 应用的任务栏按钮 和“开始”菜单快捷方式使用 PI-Desktop 图标,而不是 Electron 默认图标。每个包装 包含目标本机主机二进制文件(
.exe仅在 Windows 上)。通过这个场景 Windows/Linux 证明 shell 已准备就绪,而不是首次发布资格。 - 链接规格:
03-runtime/01-ipc-protocol.md,04-ux/01-ui-ia.md、04-ux/02-i18n-english-first.md、04-ux/07-ui-design-system.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、06-delivery/06-release-runbook.md、08-meta/decisions-log.md(D118、D121、D129) - 验收:A(应用程序启动),质量
- 里程碑:macOS 上的 M5; Windows/Linux 上 MVP 后发布资格
- 状态:单位覆盖(
window-menu.test.mjs,development-branding.test.mjs); Electron 引导探针盖 平台桥、本机菜单安装和预渲染最大化 Windows/Linux 上的固定装置;原生视觉场景草稿
E2E-143:关闭行为只问一次并且始终可配置(D230)
- 先决条件:Windows/Linux,数据目录干净(没有
close-behavior.json); 主窗口可见;可以进入设置 → 通用。macOS 不在范围内:它保持原生 Dock 生命周期。 - 步骤:1)在偏好未设置时点击关闭按钮(或按关闭窗口快捷键)并回答提示: 取消保持窗口打开且偏好仍未设置;关闭到托盘隐藏窗口、显示托盘图标,应用 继续运行(进行中的一轮保持存活);退出则退出应用。2)逐个重跑每种选择, 验证它在完整重启后仍被记住,并且一旦存在选择就再也不会出现第二次提示。 3)在设为
tray时点击托盘图标:窗口恢复、显示并获得焦点;托盘右键菜单 提供打开和退出,退出会退出应用。4)在设置 → 通用中在关闭到托盘 / 退出应用 之间切换,验证下一次关闭遵循新的选择、两种选择下 D216 的托盘图标都常驻、 未设置的偏好不显示任何选中项,并且搜索能命中该行。5)在存有quit时重启 并关闭窗口:即使托盘图标存在,应用也会退出。6)在任意设置下最小化,验证 窗口隐藏进托盘(D216),并且点击托盘可以把它带回来。7)向pi-desktop/window/closeBehavior/set传入未知值和"ask",验证它们失败 关闭,并验证该通道在 macOS 上直接被拒绝。 - 预期:每个未设置状态下首次关闭恰好提示一次,取消永远不会持久化选择。 托盘模式让应用带着本地化的提示与菜单继续存活且不丢数据;切换到退出应用 不会移除托盘图标,因为最小化到托盘仍然把它当作回到隐藏窗口的唯一途径。 偏好在重启后仍然有效,窗口控制的关闭按钮和关闭快捷键都遵循它,存有
quit时通过有序的before-quit关闭路径退出,而不是依赖window-all-closed。最小化在每种模式下都隐藏到托盘,边界看门狗永远不会 强制恢复最小化或隐藏到托盘的窗口。自动化启动探针(app.quit)不经提示 即退出。 - 链接规格:
03-runtime/01-ipc-protocol.md、04-ux/01-ui-ia.md、04-ux/09-interaction-patterns.md、08-meta/decisions-log.md(D216、 D230)、ADR 0078、ADR 0090 - 验收:A(应用程序启动)、质量
- 里程碑:Windows/Linux 上的 M5(发布资格)
- 状态:草稿
E2E-067A:预发行安装发现更新的稳定版本 (D120)
- 先决条件:打包构建,其嵌入式版本是预发布版本,例如 作为
0.2.0-rc.6; GitHub Releases 最新稳定标签较新(例如0.2.2)以及已发布的latest*.yml源。 - 步骤:1) 启动打包的预发行安装。 2) 等待 自动检查或从应用程序菜单调用检查更新/ 设置 → 信息。
- 预期:更新状态报告
available(手动平台)或 通过应用内下载 Windows NSIS / Linux AppImage 取得进展availableVersion等于较新的稳定标签。Windows 便携版 ZIP 运行保持手动通知加链接路径, 不得下载或运行 NSIS 安装程序;旧便携版 exe 在存在PORTABLE_EXECUTABLE_FILE时同样 保持手动更新。客户不得举报 最新只是因为没有较新的版本共享相同的rc预发行版 频道。 - 链接规格:
04-ux/09-interaction-patterns.md,05-security/01-security.md、08-meta/decisions-log.md(D120)、 ADR 0022 - 验收:A(应用程序启动),质量
- 里程碑:M5
- 状态:单位覆盖(
auto-update.test.mjs断言allowPrerelease = false);打包发现场景草稿
E2E-067B:已发货语言更新说明和完整变更日志对话框(D164/D345)
- 先决条件:发货的
packages/sharedCHANGELOG 包含对齐的en、zh-CN和zh-TW稳定历史记录;可以切换产品语言。对于 紧凑的更新路径,使用打包或固定更新器状态 编目availableVersion。 - 步骤: 1) 如果没有可用更新,请打开“设置”→“信息”并打开“发布” 笔记。 2) 检查完整的历史记录、当前版本标记、滚动、 以及通过密切控制、逃脱和背景来密切行为。 3)强制或等待 对于更新发现,因此状态为手动
available、应用内downloading, 或downloaded;检查环境横幅和设置更新行,然后 重新打开发行说明。 4)切换 UI 语言为 zh-CN,再切换为 zh-TW 并重新检查, 无需调用新的检查。 5) 在没有版本的情况下重复紧凑更新路径 从目录中。 - 预期:
UpdateState.releaseNotes是普通的多线产品 Main 从已发货语言目录中选择的亮点 - 从来都不是 渲染器提供的 URL。两个表面均显示本地化的“新增内容”块 当注释存在时隐藏它,当注释不存在时隐藏它。区域设置更改刷新笔记 对于相同版本。发行说明操作在每个版本中仍然可用 更新器状态并打开一个本地化的、最新优先的模式,其中包含每个 发布了稳定的条目,并在以下时间确定了当前和可用版本 存在。模态捕获焦点,关闭后恢复焦点,并且不暴露 新的 IPC 域或 Feed 配置。 - 链接规格:
04-ux/06-settings-ia.md、04-ux/09-interaction-patterns.md、05-security/01-security.md、06-delivery/06-release-runbook.md、08-meta/decisions-log.md(D164)、ADR 0022 - 验收:A(应用程序启动),质量
- 里程碑:M5
- 状态:单位覆盖(
auto-update.test.mjs、changelog.test.ts); 打包的UI场景草稿
E2E-067C:发布版本面预检拦截不一致的标签(D260)
- 先决条件:工作树处于当前稳定版本且干净。候选版本 尚未创建发布标签。
- 步骤: 1) 在一致的工作树上运行
node scripts/check-release-docs.mjs。 2) 每次只回退一个位置——先删除en中最新的变更日志条目,再删除zh-CN中的条目,然后修改亮点条数 使两个语言不一致,接着把docs/package.json改回旧版本,最后让两个 README 仍声明上一个<major>.<minor>.x版本线——每次回退后重新运行预检。 3) 在仍有一个位置未修复时运行node scripts/release.mjs <next-version> --tag。 4) 恢复所有位置,重新运行 预检,并再次执行发布命令。 - 预期:一致的工作树报告已对齐并以 0 退出。每次回退都会按名称报告 出错文件与期望版本,并以非零码退出。存在未修复位置时,
release.mjs会升版本号但既不创建发布提交也不创建标签,并说明文档检查失败。恢复后 预检通过,发布命令继续提交并打标签。--skip-docs-check只跳过该检查, 并被记录为非发布用途。 - 链接规格:
06-delivery/06-release-runbook.md§4.1、06-delivery/05-change-checklist.md、06-delivery/03-ai-development-workflow.md、08-meta/decisions-log.md(D164、D260) - 验收:质量(发布流程)
- 里程碑:M5
- 状态:脚本覆盖(
scripts/check-release-docs.mjs);手动发布演练草稿
E2E-068:将对话分叉为独立会话
- 先决条件:空闲项目对话有用户、助理、 思维、工具历史以及至少一种再生变体。一秒钟 源对话正在运行。一个临时对话,两个保留 项目工作区包含不同的同名标记文件。闲置资源 拥有会话范围的工具授权。
- 步骤: 1)用键盘打开空闲对话溢出菜单。 2) 选择创建分支。 3) 附加提示并更改子项的 model/mode。 4)切换可见工作区,返回子工作区,读取标记。 5) 触发先前授予的工具并验证是否请求确认。 6) 重新打开源。 7) 重新启动应用程序并检查两个会话。 8) 打开 正在运行的对话溢出菜单。 9)分叉临时对话 并调用工作区所需的工具。
- 预期:本地化分支标题出现在同一项目组中,并且 以输入框焦点激活。其活性转录物可见且持久 project/provider/model/mode/thinking/permission 配置匹配 源快照,但不存在重新生成寻呼机历史记录。儿童消息和 以后的配置更改不会影响源;两者都可以在重新启动后生存。 正在运行的源操作被禁用。没有回合、通知、工件, 复制权限授予、修订或临时文件。 标记在子项继承的项目下解析;临时孩子 保持无路径并返回
WORKSPACE_REQUIRED。 - 链接规格:
03-runtime/01-ipc-protocol.md,03-runtime/04-data-storage.md、03-runtime/06-host-rpc-protocol.md、04-ux/01-ui-ia.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md - 验收:C(会话)、D(工作空间)、F(持久性)、质量
- 里程碑:M5
- 状态:单位覆盖(
sessions::tests::fork_session_clones_active_transcript_and_configuration,session-fork.test.mjs);完全重启 UI 场景草稿
E2E-071:分叉助理响应而不更改其来源
- 前提条件:一个空闲会话包含两个已完成的 user/assistant 交换,第二个助理响应具有缓存令牌使用元数据。
- 步骤:1) 将鼠标悬停在第一个助理响应上并检查其工具栏。 2) 单击叉子。 3) 确认激活的子项在该响应处结束并附加 提示。 4) 重新打开源并检查它。 5)向孩子附加提示, 重新启动,并检查源和子项。 6) 在源运行时重复此操作。
- 预期:完成的辅助工具栏包含复制、分叉和 仅重新生成 — 不删除也不编辑(D137 将编辑移至用户轮次)。叉子 在源转动期间被禁用。它激活一个单独标题的会话 其历史记录在所选响应处停止;后来的源轮不存在。 源文本、版本历史记录、令牌元数据、后续轮次、运行时和缓存 状态保持不变。继续该孩子仅影响该孩子并且 从它自己的重新映射的转录本中重新播种。
- 链接规格:
03-runtime/01-ipc-protocol.md,03-runtime/04-data-storage.md、03-runtime/06-host-rpc-protocol.md、04-ux/08-component-spec.md、08-meta/decisions-log.md(D134、D137) - 验收:C(聊天 stream/sessions)、F(坚持)、质量
- 里程碑:M5
- 状态:单位覆盖(
sessions::tests::message_scoped_fork_stops_at_selected_assistant_response,session-fork.test.mjs、transcript-style.test.mjs);完全重启 UI 场景草稿
E2E-SESSION-list-refresh-keeps-desktop-responsive:大规模会话列表刷新时 Electron 保持响应
- 前提条件:已构建的 Electron 桌面应用、随应用提供的 models.dev 目录,以及未配置 提供商的全新临时 profile。探针仅使用合成的
authKind: none提供商,不启动 Agent 回合。 - 步骤:1)通过 Rust Host API 创建 800 个空的持久化会话,分配到最多十三个已知模型 ID。 2)夹具创建完成后,通过渲染器 preload 桥并发请求八次会话列表。 3)读取期间测量 Electron Main 定时器间隔,以及渲染器到 Main 的版本 IPC 延迟。 4)比较所有返回的会话 ID 和能力字段。
- 预期:每份列表都包含所有夹具会话,模型和能力字段保持一致。Main 保持响应:测得的 定时器间隔和版本 IPC 往返耗时均不得达到一秒。探针记录每次列表读取、心跳的耗时和 Main 的最大间隔。原有沙箱启动、平台窗口和菜单断言仍通过。完成后丢弃临时 profile, 现有用户 profile 和正在运行的桌面进程不受影响。
- 链接规格:
03-runtime/01-ipc-protocol.md、03-runtime/13-model-catalog-and-selection.md、ADR 0134 - 验收:C(会话)、质量
- 里程碑:M6+
- 状态:已自动化(通过
pnpm test:e2e:boot运行scripts/e2e-electron-boot.mjs, 使用现有的PI_DESKTOP_BOOT_PROBE入口)。
E2E-071g:保留的会话面板有上限并逐出最旧的
- 先决条件:至少五个具有不同转录的会话,每个都长于一个视口, 因此滚动位置和最终记录可以无歧义地标识每一个。
- 步骤:1) 按 A、B、C 的顺序打开这三个会话,并把每一个都向上 滚动一段可测量的距离。2) 切回 A,再切到 B,确认每个都回到它 自己的偏移。3) 打开 D 然后打开 E,使 A 和 B 落在保留预算之外。 4) 返回 A。5) 返回 E,然后返回 D,确认它们仍然是热的。6) 把整个 循环再重复一遍,注意是否出现卡住的进度轨道、错误 toast 或空白聊天区。
- 预期:只有可见面板和最近访问的两个被保留;访问超出该预算会 逐出最旧的面板。热返回立即绘制保留的帧和偏移。返回一个被逐出的 会话表现得与冷启动完全一样:可见面板在细进度轨道下停留在它自己 的会话上,输入框在目标提交之前保持惰性,随后目标在其最新回合处 绘制,没有错误、没有空帧,也不会恢复逐出之前的偏移。任何时刻都 没有内容被调暗,隐藏的面板保持非交互并留在无障碍树之外,反复 循环既不会泄漏出越来越多的已挂载转录,也不会让某个面板显示另一个 会话的行。
- 链接规格:
04-ux/08-component-spec.md§1.6 / §3.5 / §7、04-ux/09-interaction-patterns.md§5、ADR 0130、ADR 0137 - 接受:C(切换会话),质量
- 里程碑:M5
- 状态:草案
E2E-071i:长会话在落定遮罩下打开,发送即刻清空
- 前置条件:一个包含数百条消息(含代码块和工具结果)的会话;已配置模型; 让主机变慢(例如限速的 sidecar 或一个很大的待处理工具输出),使一次提示 往返明显超过一帧。
- 步骤:1)从首页打开该长会话并记录最初绘制的几帧。2)等待转录出现。 3)输入多行提示让输入框变高,然后按 Enter。4)在主机仍忙时,对已清空的 输入框再按一次 Enter。5)让主机拒绝一次发送(例如禁用该模型的服务商), 带着新草稿按 Enter。6)打开一个少于十五条消息的会话。
- 预期:长会话的第一帧是输入框之上一层不透明的骨架屏,由交替的用户和 助手线条组成;在历史展开和行高落定的那些帧里看不到任何转录文字,骨架屏在 约 600ms 内淡出,露出的转录已经定位在最新回合。揭开之后行不再上下移动。 多行草稿增长时,最新回合随输入框一起上移而不是消失在其后面。按 Enter 在 同一帧内清空输入框并在转录底部显示用户行,早于主机应答;主机回显到达时该行 不重复也不跳动。对空输入框的第二次 Enter 不做任何事,也不会排队重复项。 发送被拒绝时,用户行消失,草稿回到输入框且光标位于末尾。短会话不显示骨架屏。
- 关联规格:
04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、08-meta/decisions-log.md(D287、D288) - 验收:C(聊天 stream)、质量
- 里程碑:M5
- 状态:单元覆盖(
transcript-settle.test.mjs、composer-send-state.test.mjssend clears the composer before the round trip and restores a rejected draft (D287)与the user row is inserted before the host round trip and echoed under the same id (D288)、session-transcript.test.mjs);UI 场景草稿
E2E-073:纯图标消息工具栏和编辑用户提示
- 前提条件:一个空闲会话包含两个已完成的 user/assistant 交流;一个用户回合作为斜杠模板调用发送。
- 步骤: 1) 将鼠标悬停在已完成的助手行和用户行上,然后将鼠标悬停在 键盘聚焦每个动作芯片。 2) 在第一个用户提示上选择编辑。 3) 按 Esc 键,重新打开编辑,在提示不变时重试。 4)重新打开编辑,更改 文本,然后使用 Cmd/Ctrl+Enter 重试。 5)新答案完成后, 使用
current / total寻呼机返回原始交换机并 再次前进。 6) 重新加载会话。 7) 在斜杠命令上选择编辑 转动并检查种子文本。 8) 在回合运行时尝试编辑。 - 预期:每个工具栏芯片仅显示其字形,并带有标签 在悬停和键盘焦点时显示为完全可见的工具提示,位于芯片上方 8px (#74); 无芯片渲染标题文本。工具提示使用紧凑的 raised 阴影而非 composer 光晕,因此其表面与
--ds-bg-hover在视觉上分开。 辅助工具栏提供复制、分叉、重新生成; 用户工具栏提供寻呼机(当存在变体时)、复制、编辑、删除。 编辑用更宽的内联文本区域替换提示气泡,并显示“重试”和“取消”控件; Escape 或取消恢复不变的气泡。重试会从该提示截断记录并传输新的应答, 无论文本是否变化,并在恢复的用户轮上留下current / total寻呼机, 使原始提示及其完整答案尾部就位——重新加载后仍然保留。 斜杠转为键入的/command表单播种并重新扩展模板 重试。回合运行时编辑被禁用。 - 链接规格:
04-ux/08-component-spec.md,03-runtime/01-ipc-protocol.md、03-runtime/04-data-storage.md、08-meta/decisions-log.md(D137、D274) - 接受:C(聊天流)、F(持久性)、质量
- 里程碑:M5
- 状态:单位覆盖(
transcript-style.test.mjs);完整的 UI 场景草稿
E2E-069:特定于平台的侧边栏标题行为
- 先决条件:PI-Desktop 已打开,并带有扩展的侧边栏和聊天室 会话处于活动状态。
- 步骤: 1) 在 macOS 窗口模式下打开扩展。 2)检查展开的 侧边栏标题栏。 3) 确认没有 PI-Desktop logo/title 可见,折叠 侧边栏出现在交通灯的右侧。 4) 输入 全屏并检查同一行。 5) 在Windows/Linux上,确认品牌 仍然可见;用指针激活它,然后用键盘焦点激活它 Enter/Space。
- 预期:macOS 使用一排 46px 的行,左侧带有原生灯光,这是一个可用的 拖动区域,并在右侧提供可访问的“折叠”按钮; Logo/Home 品牌在窗口模式和全屏模式下均不存在。全局搜索留在会话顶栏、快捷键和应用菜单,不在侧边栏标题。 Windows/Linux 在 15px shell 名称旁边渲染规范的 20px 徽标;这 完整的品牌有一个本地化的主页可访问名称,可见 hover/focus 反馈,并返回主窗格进行聊天,而不清除活动的 对话或工作空间。的 徽标本身具有主题感知:浅色模式显示
src/assets/brand/logo-light.png, 深色模式显示src/assets/brand/logo-dark.png,与data-theme实时交换 (无需重新加载)。 - 链接规格:
04-ux/01-ui-ia.md、04-ux/07-ui-design-system.md、04-ux/08-component-spec.md - 验收:质量
- 里程碑:M5
- 状态:单位覆盖(
renderer-branding.test.mjs,sidebar-navigation.test.mjs);渲染交互场景草稿
E2E-098:侧边栏折叠和展开动画作为停靠过渡
- 先决条件:PI-Desktop 打开并带有扩展的侧边栏和活动的 聊天会话;
prefers-reduced-motion已关闭。 - 步骤: 1) 单击展开的侧边栏标题中的折叠侧边栏(或按 侧边栏切换快捷方式)。 2)折叠时观察侧边栏。 3) 确认 主窗格展开,折叠的标题栏现在显示展开控件。 4) 从折叠状态再次按下侧边栏切换快捷键:每个 压机必须严格交替折叠和展开,因此第二次压机 重新展开侧边栏(回归:它绝不能重新折叠)。 5)崩溃 并通过快捷方式再次重新展开,然后重复完整的往返行程 使用指针控件。 6) 在 Windows/Linux 上重复。
- 预期:折叠播放
sidebar-out关键帧(不透明度 + ≤8px 幻灯片 加上 width/flex 分配),而旁路保留在树中,然后卸载 动画结束后;主窗格连续填充释放的空间。 Expand 播放sidebar-in关键帧并 控件返回到展开的标题。在 Windows 上,码头保持不透明 退出期间 (sidebar-out-windows),与工作面板停靠行为匹配。否 布局跳转在动画之前,焦点返回到 sidebar/Expand 控制可预测。 - 链接规格:
04-ux/08-component-spec.md、04-ux/07-ui-design-system.md - 验收:质量
- 里程碑:M5
- 状态:单位覆盖(
sidebar-collapse-animation.test.mjs);呈现 交互场景草稿
E2E-070:本机选择菜单在整个应用程序中遵循 Windows 主题
- 先决条件:PI-Desktop 在 Windows 上运行,有光和暗 可用的主题。
- 步骤:1)在浅色主题中,打开仍存在的本机选择(计划任务表单),并确认设置里的常规、全局 AI、模型配置和导入选择器都是应用内菜单而不是平台
<select>。2)以深色主题重复剩余本机列表。3)切换主题后无需重启即可再次打开每个列表。 - 预期:设置里的紧凑选择器使用共享锚定菜单。每个仍存在的关闭本机触发器和打开的本机选项列表都使用活动主题的可读 foreground/background 配对。没有黑暗主题列表退回到带有浅色文本的浅色 Windows 表面,没有浅色主题列表使用深色主题墨水,更改主题会更新后续打开。同样的结果适用于设置之外的本机选择。
- 链接规格:
04-ux/06-settings-ia.md,04-ux/07-ui-design-system.md - 接受:质量(跨平台主题可读性)
- 里程碑:M5
- 状态:单位覆盖(
settings-general.test.mjs); Windows 渲染 场景草稿
E2E-072:键盘快捷键映射持续存在并保持冲突安全
- 先决条件:应用程序在 macOS 和一个 Windows/Linux 目标上运行 设置打开;不存储自定义快捷方式覆盖。
- 步骤:1) 打开设置 → 快捷键并检查键盘快捷键。2) 将 Search 改为未使用的 修饰键组合。3) 调用新组合,再调用旧组合。4) 尝试将该组合分配给命令快捷键 (现在通过全局搜索打开)。5) 尝试裸字母和保留的编辑组合。6) 禁用 Search, 确认该行显示“未绑定”。7) 确认默认和自定义 Search 组合都不再打开 Search, 重启后仍保持禁用。8) 单独恢复 Search 并确认默认值返回,再选择恢复默认值, 确认所有行都恢复默认。9) 在 macOS 上检查每次保存/恢复后的本机菜单加速器。10) 在 Windows 上禁用插件启动器,确认旧的全局绑定、聚焦窗口后备和 Alt+Space host 后备都已关闭。11) 单独按下并释放 Ctrl/Command,确认 IME 候选,并长按 back/forward 组合以产生重复。12) 禁用 New task,把其默认组合分配给 Search, 再单独恢复 New task;确认出现冲突提示、两项绑定不变,且该组合仍能打开 Search。 将 New task 改为自定义绑定后重复测试;恢复 Search 释放组合后,再恢复 New task。
- 预期:操作分为导航、Agent 和窗口,并使用平台原生按键标签;录制有可见焦点 且
Escape取消;自定义 Search 组合立即生效、替换旧组合、跨重启保留并更新 macOS 菜单;重复、无修饰符和保留组合以内联错误拒绝;“未绑定”是明确的本地化 状态,不参与冲突、不响应旧或默认组合、可跨重启保存,并会移除 macOS 加速器和 Windows 启动器后备层;单项恢复遇到默认组合已被占用时拒绝保存,保留两项绑定; 无冲突的单项恢复和全局恢复都返回共享默认值。仅修饰符和 IME 按键不 会发送命令,长按历史组合每次物理按压只遍历一次。窗口可见性只有一个开关键Alt + Shift + W—— 可见且在前台的窗口隐藏到托盘,其余情况显示并获得焦点 —— 且绝不走关闭路径,因此不会弹出关闭行为询问、也不会退出应用;该键刻意避开Cmd/Ctrl + W,因为 macOS 把它用于自己的关闭窗口命令;已弃用的Cmd/Ctrl + Shift + W组合键不再注册,已存储的closeWindow/summonWindow覆盖项会并入该开关键(D438、D439)。 - 链接规格:
04-ux/06-settings-ia.md、04-ux/07-ui-design-system.md、03-runtime/01-ipc-protocol.md - 接受:F(设置持久性)、质量(键盘可访问性)
- 里程碑:M5
- 状态:单位覆盖(
keyboard-shortcuts.test.ts、settings-keyboard-shortcuts.test.mjs、window-toggle-shortcut.test.mjs、 host 设置 RPC 测试);渲染场景草稿
E2E-073a:开发者模式控制开发者工具控制台
- 先决条件:应用程序在 macOS 和一个 Windows/Linux 目标上运行; 持久设置和设置 -> 中开发人员模式不存在或为 false 信息已公开。
- 步骤: 1) 通过设置搜索找到开发者卡。 2) 确认 打开控制台操作被禁用并调用 F12 加上辅助平台 捷径。 3) 启用开发者模式并从“设置”中打开控制台。 4)关闭并按F12重新打开;在 Windows/Linux 上重复 Ctrl+Shift+I,然后在 macOS 上检查并调用视图菜单开发人员工具 项目。 5) 重新启动应用程序并调用已启用的入口点。 6) 禁用 控制台打开时的开发人员模式。 7) 直接尝试控制台IPC 禁用时。
- 预期:没有禁用的入口点打开开发人员工具,并且 macOS 省略 查看菜单项。启用持久化开关可解锁本地化 设置操作和适用的平台快捷键;每个切换都相同 重启后的窗口控制台。禁用开关会关闭控制台, 禁用“设置”操作,删除 macOS 菜单项,并使直接 IPC 请求无法关闭。
- 链接规格:
03-runtime/01-ipc-protocol.md,04-ux/06-settings-ia.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md - 验收:F(设置持久性),质量
- 里程碑:M5
- 状态:单位覆盖(
settings-general.test.mjs,window-menu.test.mjs);原生交互场景草稿
E2E-074:并发会话事件和权限永远不会夺走焦点
- 前提条件:会话A和B以Agent模式存在并且可以运行 同时; A 在其编辑器中可见并带有草稿。
- 步骤:1) 开始轮流 A 和 B,然后返回 A。 2) 让 B 发出流式传输 消息、工具活动、完成和权限请求。 3)确认A 保持可见并继续编辑其草稿。 4)触发权限 也在A中请求。 5)显式打开B,只解析B的请求,然后 返回A并解决A的请求。 6) 会话时快速选择 A 然后 B 详细信息以相反的完成顺序加载。 7) 当B加载时,解析A的 Write/Edit 请求,因此其工具完成时记录一张内联审阅卡,然后让 B 发出 当 A 可见时,BrowserPreview 伪影。切换回每个会话。
- 预期:B的后台事件仅更新B的行和保留状态; 他们不会更改 A 的活动 session/project/page、记录、草稿、卷轴、 或键盘焦点,并且不会出现全局模式。打开 B 仅显示 B 内联卡及其原始倒计时。两个请求保持独立 可操作,并且解决B并没有清除A。最终的快速选择保持不变 即使 A 的较旧负载稍后完成,也会在 B 上执行。仅明确通知或 会话激活可以导航。 A 的批准后审阅卡仅保留在 A 中 B 中没有瞬态 open/close 闪存; B 的 BrowserPreview 携带 B 的 会话身份,仅更新 B 保留的浏览器资源,并且从不打开, 导航、聚焦或调整 A 的面板大小。明确返回到任一 会话恢复其自己的打开状态、选项卡、活动选项卡和浏览器资源。
- 链接规格:
04-ux/01-ui-ia.md、04-ux/03-permission-ux.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md - 验收:C(会话隔离)、E(权限隔离)、质量
- 里程碑:M5
- 状态:单位覆盖(
permission-inline.test.mjs表示作用域状态, 内嵌渲染契约、绝对倒计时、最新选择守护; 用于会话范围的work-panel.test.mjs和browser-preview-tool.test.mjs工件保留和路由);完整的 UI 场景草稿
E2E-075:侧边栏部分上下文菜单创建会话和项目
- 状态:手动
- 优先级:P1
- 封面:A、C、D / US-UI-57
- 先决条件:应用程序运行时可见扩展的主页侧边栏。
- 步骤:
- 右键单击
Sessions标题标签(不是会话行)。 - 选择单个创建项目。
- 右键单击独立会话列表中的空镶边。
- 右键单击
Projects标题标签。
- 右键单击
- 选择单个创建项目并取消或完成项目选择器。
- 右键单击项目列表中的空镶边(任何项目组之外)。
- 预期:
- 会话上下文菜单 create/reuse 无路径临时会话和焦点 输入框。
- 项目上下文菜单打开与标题相同的文件夹选择器 文件夹加控件。
- 现有的行上下文菜单和标题字形按钮仍然可用;的 部分菜单保持一项且与其他侧边栏菜单主题匹配。
- 部分、会话行和项目行右键菜单在右侧打开 当空间允许时,指针完全保持在视口内 右边缘。
- 退出并外部单击关闭菜单而不创建任何内容。
E2E-076:出现启动画面,然后返回主 shell
- 状态:部分自动化(
startup-splash-motion.test.mjs涵盖启动标记、运动标记、简化运动和目录键;macos-sidebar-vibrancy.test.mjs覆盖 darwin 玻璃启动页与 shell 交叉淡入;全窗口计时仍为草稿) - 优先级:P1
- 封面:A,品质/US-UI 外壳抛光
- 先决条件:应用程序启动路径可用(开发或打包)。
- 步骤: 1.启动PI-Desktop。 2. 在引导完成之前观察第一个绘制的渲染器表面。 3. 等待 sessions/settings 引导程序完成。 4. 如果可用,请对 OS
prefers-reduced-motion: reduce重复此操作。 5. 在 macOS 上,将启动面与 shell 出现后的侧边栏玻璃态对比。 6. 在bootstrap一直被搁置、超过看门狗界限时重复一次,在 30 秒与超过 180 秒时观察启动表面(issue #831)。 - 预期:
- 准备之前:带有品牌标志、外壳名称、标语和可访问的启动状态 (
data-testid="startup-splash") 的全窗口启动画面。- 准备好后:启动画面会短暂淡出(或立即减少运动)退出,并且主外壳(或设置页面)在下面是交互式的。
- 如果初始状态始终没有到达,启动画面不是被一直保留而是被替换:启动表面在 30 秒(
STARTUP_SLOW_HINT_MS)时加上日志、诊断与退出且不报告失败,在 180 秒(STARTUP_STALLED_MS)时变成恢复表面,并额外提供重试(data-testid="startup-recovery");每个操作都不依赖后端,Windows/Linux 上渲染器绘制的窗口控制按钮保持在该表面之上,退出走pi-desktop/app/quit。 - macOS 上启动页与侧边栏使用同一套玻璃 tint/sheen,叠在原生
sidebarvibrancy 之上;已挂载的 shell 在退出淡出前保持隐藏,再交叉淡入。其他平台仍为不透明的--ds-bg-primary。 - 没有简单的无品牌“开始...”居中文本作为唯一的启动 UI。
- Overlay/dialog 输入动作使用共享令牌;减少的运动可以保持状态变化,而无需装饰持续时间。
- 链接规格:
04-ux/07-ui-design-system.md§8、04-ux/02-i18n-english-first.md、决策日志 D146 / D304 / D348 - 验收:A(应用程序启动),质量
- 里程碑:M5
E2E-099:品牌标志遵循活跃主题
- 状态:草案
- 优先级:P3
- 封面:品质/US-UI 外壳抛光
- 前提条件:应用程序运行;主题可以在浅色和深色(以及系统)之间切换,而无需重新启动。
- 步骤:
- 在轻型模式下,打开应用程序外壳、空的聊天主页和展开的侧边栏 (Windows/Linux) 或启动画面。
- 检查侧边栏和启动画面中渲染的
BrandLogo源, 并检查空首页英雄中的浅色八帧HomeMascotLogoGIF。 将鼠标悬停在吉祥物上并验证节奏不变。 - 将主题切换为深色(设置→基础→外观,或系统外观更改)。
- 重新检查相同的表面,无需重新加载。
- 切换回光源并重新检查。
- 预期:
- 明暗模式渲染
src/assets/brand/logo-light.png/src/assets/brand/logo-dark.png位于侧边栏和启动画面中,无需重新加载窗口。
- 明暗模式渲染
- 空首页英雄按当前主题渲染 100 像素的八帧吉祥物 GIF (
home-mascot-light.gif/home-mascot-dark.gif),首帧短暂停留后 循环挥手。切换主题时即时更换资源,无需重新加载窗口。指针悬停 不改变节奏;减少运动时对应静止首帧仍然可见。- 尺寸在主题变化时保持稳定(侧边栏 20 像素、英雄 100 像素、启动栏 64px),标记保持装饰性,无需点击、键盘或焦点 行为。
- 链接规格:
04-ux/08-component-spec.md§3.7、04-ux/07-ui-design-system.md - 验收:质量
- 里程碑:M5
E2E-077:主题感知选择和 CJK 部分标签
- 状态:部分自动化(
user-select.test.mjs、interaction-polish.test.mjs) - 优先级:P2
- 封面:A,品质/US-UI 外壳抛光
- 先决条件:应用程序运行时至少有一个可选择的转录本或输入;语言可以切换为
zh-CN。 - 步骤:
- 选择转录消息或输入框中的文本。
- 检查侧边栏 Sessions/Projects 部分的英文标签。
- 将应用程序语言切换为
zh-CN并重新检查相同的标签。 - 悬停跳转-最新(当可见时)、停止、搜索行和配置文件菜单项。
- 预期:
- 选择突出显示使用中性文本主色(不是浏览器默认的蓝色)。
- Caret/form 强调色保留在单色令牌坡道上。
- 英文部分标签可以使用大写+宽跟踪;
zh-CN标签使用正常跟踪,无需强制大写。 - 列出的 chrome 控件可通过共享运动令牌简化 background/color 更改。
- 链接规格:
04-ux/07-ui-design-system.md、04-ux/08-component-spec.md - 接受:D147
- 里程碑:M5
- 状态详细信息:CSS 合约的源代码级别覆盖范围;视觉选择油漆仍然是手动的。
E2E-078:工作面板和设置光表面抛光
- 状态:部分自动化 (
surface-polish.test.mjs) - 优先级:P2
- 封面:D,品质/US-UI 外壳抛光
- 前提条件:应用程序运行;主题可以切换为浅色;可以打开工作面板选项卡。
- 步骤: 1.切换到浅色主题。 2. 打开“设置”并检查表单字段、切换开关、分段控件和快捷键键帽。 3. 打开聊天会话旁边的工作面板(审阅/文件/浏览器)。 4. 将鼠标悬停在文件树行或 diff 标头上;聚焦浏览器 URL 字段。 5. 打开 confirmation/provider 对话框并检查稀松布。 6. 在明暗主题中分别检查设置导航轨、搜索、选中项、开启状态旋钮、输入框壳、插件/ 能力搜索、代码卡标题带、Mermaid 画布、工具输出、输入占位符与禁用发送芯片、 对话框遮罩与权限蒙层,以及 dock 内的问题卡及其选项行。应用自定义表面变量, 使用键盘聚焦两类搜索,再移除自定义主题。
- 预期:
- 工作面板主体读取为安静的
#fafafa插页纸,带有白色标题带。 - 设置字段、浏览器 URL、分段轨道和快捷键键帽使用浅色嵌入填充;聚焦场通过中性环提升。
- 切换开启状态使白色旋钮保持在近乎黑色的轨道上。
- 悬停可通过共享运动令牌轻松填充 file-tree/diff/resize;分隔线与左侧边栏一致,是 32px 居中短把手,不是通高细线;键盘焦点和拖动中用实色强调。
- 浅色对话稀松布比深色 45% 面纱(约 28% 墨水)更柔软。
- dock 内的问题卡在两种主题里都绘制 composer 板 —— 浅色
#ffffff配 composer 阴影,深色 96%#212121—— 它的选项行是无抬升阴影的内嵌--ds-tile-deep填充。 自定义--ds-bg-composer/--ds-tile-deep会同时改变两者,移除后恢复内置配色。 - 工具输出保持既有级联:浅色在错误输出与纯文本工具块上都绘制同一层较浅底纹,深色 显示错误色调并让纯文本块保持透明。
- 自定义变量改变对应表面、键帽墨色及搜索焦点填充;移除后恢复内置 8-bit RGBA 配色与原有阴影/焦点环。正文墨色混合跟随
--ds-text-primary,one-dark-pro/one-light的 Shiki 底板与其墨色按设计归 Shiki 主题所有。 本批不改插件主题 API。
- 工作面板主体读取为安静的
- 链接规格:
04-ux/07-ui-design-system.md、04-ux/08-component-spec.md - 接受:D148
- 里程碑:M5
- 状态详细信息:
pnpm test:e2e:theme-surfaces使用真实 Chromium、生产 CSS 和确定性 DOM 夹具覆盖上述普通表面、焦点状态及内置配色恢复,不覆盖插件安装/ 生命周期。分支执行不能替代合并后 E2E;本场景其余表面仍需人工检查。
E2E-079:面向用户的英文和中文目录副本
- 状态:部分自动化(
packages/i18n/test/user-facing-copy.test.mjs、catalogs.test.mjs) - 优先级:P2
- 封面:A,质量/US-UI 副本
- 前提条件:应用程序运行;语言可以在英语和中文之间切换。
- 步骤:
- 检查空首页提示、侧边栏临时聊天部分和 status/connection toast。
- 打开设置 → AI 提供商和市场刷新操作。
- 将应用程序语言切换为中文并重新检查相同的界面。
- 预期:
- 文案以简单的产品语言(AI 提供商、项目、市场、connected/limited)而非 host/backend/repo 行话解释结果。
- 英语和中文目录保留相同的键和插值变量。
- 崩溃的 chrome 和空主目录标题在两个区域设置中均保留目录支持。
- 链接规格:
04-ux/02-i18n-english-first.md - 接受:D149
- 里程碑:M5
- 状态详细信息:目录合同的源级覆盖范围;视觉措辞审查仍然是手动的。
E2E-081:发送重新固定脚本并跳转到底部
- 先决条件:溢出一个视口的长脚本;提供商已配置。
- 步骤:
- 从固定底部开始,使用触控板向上滚动 初始运动较小;观察第一个动作和 跳转到最新的控件。
- 在编辑器中键入新提示并发送。
- 在回合开始和流式传输时观察转录位置。
- 在流式传输时再次向上滚动触控板,等待 了解更多内容,然后单击跳转到最新内容。
- 预期:
- 第一次向上运动立即释放跟随模式并保持 稳定;它不会快速返回、反转方向或振荡 待处理的流或调整大小跟随帧完成。
- 新的流媒体内容不会移动手动定位的视口,并且 一旦释放跟随模式,就会出现跳转到最新内容。
- 发送时,记录会重新固定、隐藏跳转到最新内容并跳转到底部,以便新用户消息(和后续流)可见。
- 固定时流媒体继续关注。
- 手动滚动中流暂停并再次显示跳转到最新内容;单击它即可恢复跟随。
- 链接规格:
04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md - 验收:C(聊天流),质量/D151
- 里程碑:M5
- 状态:部分自动化(
apps/desktop/test/transcript-scroll.test.mjs); 完整的触控板交互仍为草稿
E2E-082:新推理会话默认为绑定的思考等级
- 先决条件:应用程序默认 provider/model 解析为具有推理能力, 发布稀疏思考等级集合,且绑定默认值不是最高已启用档;第二个默认 模型是非推理的。
- 步骤:
- 将具有推理能力的模型设置为应用程序默认模型并创建一个新会话。
- 检查发送到主机的思考触发器和会话配置。
- 选择较低级别或关闭,退出会话,然后重新打开。
- 将非推理模型设置为默认并创建另一个新会话。
- 预期:
- 第一个新会话持续存在并显示绑定存储的默认思考等级 (钳位到已启用档),即使提供商返回的稀疏级别乱序。 不会仅仅因为模型支持推理就跳到最高已启用档。
- 重新打开第一个会话会保留用户以后的显式选择。
- 非推理会话从
off开始,不渲染任何思考触发器; 丢失的功能元数据也会回退到off。
- 链接规格:
03-runtime/13-model-catalog-and-selection.md,04-ux/08-component-spec.md、ADR 0018、D303 - 验收:B(模型配置)、F(持久性)、质量
- 里程碑:M5
- 状态:部分自动化(
thinking-levels.test.ts,thinking-ui.test.mjs);完整的 UI 场景草稿
E2E-083:长流转使 shell 交互保持响应
- 先决条件:已配置提供商;活动会话有足够的用户, 助手和工具行可溢出多个视口;工作面板可以是 打开;正常和简化运动首选项均可用。
- 步骤:
- 启动长助理响应,发出频繁的流式更新。
- 在流式传输时,将鼠标悬停并聚焦侧边栏行,然后在以下情况下输入输入框: 启用后,open/collapse 工作面板,然后滚动文字记录 并返回到最新的。
- 观察小地图、完成的历史行、composer 表面和 shell 整个流中的镀铬。
- 导航至插件或设置并返回,然后以减少的动作重复。
- 预期:
- 当前助手行逐步显示内容并固定关注 保持在最晚,没有明显的振荡。
- 可替换的 message/tool 部分会合并到下一个油漆,同时 终端、许可、计划和错误状态仍然是即时的。
- 失败的工具行仍然带有错误色调并且可以局部扩展,但永远不会标记 包含的活动组最终失败。小组只报道 处理时间;终端回合样式来自终端代理 结果表面。
- 侧边栏、输入框、已完成的 message/activity 行、工作面板、标题栏、 并且全局覆盖不会明显重绘或丢失 pointer/keyboard 每个令牌更新的响应能力。
- 已完成的历史记录保留在其稳定的渲染边界中,而活动的历史记录保留在其稳定的渲染边界中 尾部变化;历史保持可选择、可复制并锚定在 小地图,无需为每个标记重建为 React 子树。
- 在同一活动回合内,内容未变化且不含委派的活动组,不因文本更新重建了 回合级委派 Map 而再次渲染。工具内容变化仍会渲染;后续 TaskWait 结果 仍会更新原 Task 组的终态和完成耗时。
- 按下和释放标准、图标、侧边栏、发送、停止和消息 动作控制使用一种缓和变换而不是捕捉比例; 活动流标签和加载骨架保留其 shimmer/pulse 循环时间等于或低于 1 秒。
- 小地图溢出和活动标记状态在没有标记的情况下保持正确 流媒体内容改变高度时出现抖动。
- 目的地、面板、焦点、按下、跳转、错误反馈使用一短路 有界过渡;抄本中没有输入框的模糊痕迹。
- 初始 shell 不会急切地评估次要目标模块; 第一个导航可能会显示一个紧凑的本地化加载指示器,然后 本地块解析后保留正常的页面交互。
- 减少运动保留每个状态变化并使用即时编程 以接近零的过渡持续时间进行滚动。
- 链接规格:
04-ux/07-ui-design-system.md,04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md - 验收:C(聊天流),质量
- 里程碑:M5
- 状态:单元覆盖(
interaction-performance.test.mjs);通过pnpm test:e2e:transcript自动验证 React/Chromium 渲染回归(无需提供商 凭据;需安装 Electron,并有图形会话,Linux 可用 Xvfb)。该测试挂载生产 聊天组件,统计 100 个已完成活动组在 20 次文本更新中的 ActivityGroup 渲染次数,并检查工具内容变化和跨活动段的 Task 终态及耗时更新。 测试不加载样式;完整提供商流式响应与 shell 交互响应性仍为草稿。
E2E-084:长工具循环在提供程序上下文限制之前压缩
- 先决条件:使用已知的 pi-ai context/output 配置的提供程序 限制;夹具可以产生重复的刀具转动和大型带帽刀具 结果没有完成代理运行。自动保护始终开启 并且没有任何设置。
- 步骤:
- 启动一项代理任务,其工具循环超出硬预算。
- 让至少三个
turn_end事件发生在agent_end之前;观察 composer/session 控制、处理行、转录和 toast。 - 继续直至安装检查点,然后完成任务。
- 发送进一步的提示,直到安装第二个检查点。
- 使用多个平行带帽刀具重复硬边界车削结果 压缩范围。
- 重新启动应用程序,重新打开会话,并发送依赖已总结旧工作的后续提示,确认已完成 的提示不会作为裸的历史用户消息被重放。
- 对返回 Bedrock 的提供商固定装置重复此操作
prompt is too long: N tokens > M maximum一次。 - 运行模型调用远低于硬预算的
new_context的回合。 - 空闲时手动调用
/compact。
- 预期:
- 每个
turn_end在另一个提供商请求之前都会被评估,并且永远不会 标记整体任务空闲; composer/config 控件保持阻塞状态,直到agent_end、error或仅手动的compaction_end。 - 每次成功的压缩都会在记录中添加一个分隔行, 位于检查点覆盖的最后一条消息之后,并且 恰好引发一个警告Toast。两个检查点产生两行, 顺序,并且两行都不会替换或隐藏消息。
new_context调用显示为正常工具活动行,返回 立即,并在下一个回合边界处创建检查点 而不是中途回合。
- 每个
- 在检查点后打开上下文使用检查器显示一行 压缩计数和最新摘要的令牌估计;在任何之前 检查点该线不存在。
- 在硬边界,在下一个模型之前创建持久检查点 请求。完整的可见记录没有改变,并且继续 任务保持低于模型感知的安全预算。
- 在检查点之后,下一个提供商请求不包含助手或工具 来自边界之前的消息——仅包含摘要;活动回合继续时, 另外包含最新的用户消息,该消息可能带有检查点截断标记。 已完成回合检查点的保留尾部为空,并且请求不包含没有结果的工具调用。 扩展原来的转录本行仍然显示其完整的持久结果。
- 重新启动可恢复摘要和记录的活动/已完成保留模式,并且每个更早的 压缩行仍被绘制。检查点之前的 regenerate/fork 具体记录的边界被丢弃; 以幸存消息为锚点的记录被保留/重新映射。
- 确切的提供程序溢出仅从模型中删除失败的助手 上下文,压缩后重试一次,并且不会在第二次循环 溢出。
- 如果自动摘要生成失败,则持久保留尾部回退 附加检查点,运行保持活动状态,并有一个警告解释 旧模型上下文被减少;该检查点的转录行显示 「摘要生成失败 · 已保留近期上下文」,绝不显示
摘要 ≈N tokens(ADR 0282)。 在回退之前,摘要请求会对瞬时的提供商失败最多重试三次(2s/4s/8s 退避), Stop 会取消退避,确定性失败不重试;序列化提示超出窗口的输入会把工具结果 截为短前缀后再发送一次,而不是跳过模型。如果后备持久性或安全 预算保护失败,CONTEXT_COMPACTION_FAILED被发出一次。 - 如果后续检查时最新的检查点已经是转录本叶子 提示超出硬预算,运行时会重建较小的尾部 完整的成绩单并向前推进现有的摘要,而不是 报告称没有新的上下文需要压缩。
- 每个检查点窗口的预算提醒最多出现一次,从不 在记录中,而不是在持久的系统提示中。
- 空闲
/compact成功并在顶部显示其自己的信息 toast 压缩警告,因为用户要求它。压实失败 通过CONTEXT_COMPACTION_FAILED表面一次,没有重复错误 干杯。 - 设置不包含上下文管理卡,并且设置搜索不返回任何内容 压实行。
- 链接规格:
03-runtime/01-ipc-protocol.md,03-runtime/02-agent-runtime.md、03-runtime/03-tools-and-permissions.md、03-runtime/04-data-storage.md、03-runtime/06-host-rpc-protocol.md、04-ux/06-settings-ia.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、ADR 0030、ADR 0049、ADR 0061、ADR 0064、 ADR 0136、D158、D203、D275 - 验收:C (chat/stream)、F(持久性)、质量
- 里程碑:M5
- 状态:部分自动化(
runtime.test.ts,context-compaction.test.mjs、assistant-turns.test.mjs、host-core transcript/session 单元测试);完整的 provider/UI 旅程草稿
E2E-AGENTS-001:项目指令链配置代理会话
- 先决条件:项目包含根
AGENTS.md,嵌套packages/api/AGENTS.md,并且配置了提供程序。 - 步骤:
- 启动Agent模式对话并提交root覆盖的任务 指示。
- 让代理读取或编辑
packages/api/handler.ts。 - 添加
packages/api/AGENTS.override.md,然后让代理访问另一个 该目录中的文件。 - 在会话空闲时编辑 root 指令,然后提交 后续任务。
- 预期:初始运行时接收根链。文件之前 工具执行时,嵌套指令将附加在其根源之后,并且 因此优先。在一个目录中,
AGENTS.override.md胜出AGENTS.md;CLAUDE.md和.claude/CLAUDE.md是后备名称。闲着的 后续使用更改的根内容而不是重用之前的运行时。 空的、不可读的、过大的和超出根的指令文件不会阻塞 转牌圈;组合的 UTF-8 内容上限为 32 KiB。如果路径特定 分辨率超过其两秒期限或主机不可用,则 文件工具继续使用基础链并且不保留同级链 目录的规则。同一目录中的重复文件工具一次 提示重用一种路径解析声明;下一个提示再次解决,所以 观察到更改的指令文件。解析器使用会话绑定 项目根在运行时启动时传递,并且不发出每个文件session.getRPC。 - 链接规格:
03-runtime/02-agent-runtime.md - 接受:C (chat/stream),F(持久)
- 里程碑:M5
- 状态:部分自动化(
project-instructions.test.ts,runtime.test.ts);满 provider/UI 旅程草案
E2E-AGENTS-002:全局设置和项目菜单管理指令文件
- 先决条件:PI-Desktop 正在运行;可以打开一个项目。
- 步骤:
- 在没有活动项目的情况下打开设置 -> 说明并保存全局 内容。
- 启动新的代理会话并验证其指令上下文包括 全球来源。
- 打开项目视图并使用项目的更多菜单来编辑和保存其 显示
AGENTS.md。 - 在新会话或空闲会话中提交提示。
- 预期:全局编辑器仅针对
~/.pi/agent/AGENTS.md。的 项目编辑器只能从已知项目的项目中使用 - 查看更多 菜单并仅针对该项目的根AGENTS.md。两位编辑均显示 他们解析的路径,保留键入的文本,并通过专用的保存 IPC 而不是一般文件写入 API。全球内容先于项目 下一个运行时的内容;保存的项目内容跟随它并采取 冲突的优先顺序。项目编辑器是一个视口级对话框。 - 链接规格:
03-runtime/02-agent-runtime.md、ADR 0037 - 接受:C (chat/stream)、D(工作空间)、F(持久性)
- 里程碑:M5
- 状态:单位覆盖(
project-instructions.test.ts); UI 之旅草案
E2E-085:扩展的侧边栏排版使列表内容保持紧凑
- 前提:展开的侧边栏至少包含一个独立的 会话、一个带有会话的保留项目和一个空项目组; 有浅色和深色主题可供选择。
- 步骤:
- 以默认窗口宽度打开应用程序并检查会话标题, project/group 标题、空状态副本、节标签和页脚 设置、插件和通知图标。
- 在浅色和深色主题之间切换,然后将窗口缩小到 支持的最小扩展侧边栏宽度。
- 将侧边栏层次结构与 14px 聊天正文文本进行比较并检查长 session/project 名称。
- 预期:
- 页脚操作图标使用共享的 32px 命中目标和紧凑的 14px 图标 尺寸;插件位于“设置”的右侧。
- 会话标题、project/group 标题和空状态副本使用
--text-md(13 像素);部分标签和辅助元数据保留在--text-sm(12px)。 - 两个主题中的层次结构都保持可读性,行距保持紧凑 大约 28–32px,长标签会在没有外壳回流的情况下截断。
- 链接规格:
04-ux/07-ui-design-system.md,04-ux/08-component-spec.md,D161 - 验收:质量
- 里程碑:M5
- 状态:单位覆盖(
sidebar-navigation.test.mjs);视觉呈现 场景草稿
E2E-086:助理美人鱼栅栏安全渲染而不会阻塞流
- 先决条件:提供商可以流式传输包含以下内容的助理答案: 有效的 Mermaid 流程图、无效的 Mermaid 栅栏和普通栅栏代码; 有浅色和深色主题可供选择。
- 步骤:
- 缓慢流式传输有效的
mermaid栅栏,并在栅栏前后观察它 关闭栅栏到达。 - 将完成的图表滚动到视图中,切换源,复制源, 并在浅色和深色主题之间切换。
- 向上滚动,以便暂停转录记录,然后带来另一个已完成的记录 视口附近的图表。
- 渲染无效且超过 20,000 个字符的美人鱼源以及负载 尝试 HTML 标签、外部图像、链接或 Mermaid 配置覆盖。
- 扩展包含
mermaid栅栏的思维内容。
- 缓慢流式传输有效的
- 预期:
- 部分流仍然是正常的源代码块;只有完整的 答案栅栏开始渲染,并且仅在视口附近。普通代码 栅栏和思考 美人鱼栅栏保留了其来源演示。
- 该图使用有界卡镶边,保持在转录本宽度内, 切换主题而不使用陈旧的颜色,并公开键盘可访问的 diagram/source 和复制控件。复制返回原始栅栏源。
- 图表高度变化将固定的记录保留在底部,但永远不会 用户向上滚动后恢复关注。
- 无效、过大或不安全的输入不会导致助手转动、执行失败 链接、加载嵌入媒体、添加外部 HTML 或削弱严格设置; 当渲染不可用时,它会回退到可读的可复制源。
- 链接规格:
04-ux/08-component-spec.md§8.7,04-ux/09-interaction-patterns.md§2、05-security/01-security.md§2、D165 - 验收:C(聊天和直播)、安全性、质量
- 里程碑:M5
- 状态:单位覆盖(
mermaid-rendering.test.mjs);提供安全性和 视觉场景草稿
E2E-196a:未签名的 macOS 调试通道
- 先决条件:手动运行 Release 工作流并设置
sign_macos: false;Windows 和 Linux 的发布凭据不受影响。该路径不得用于发布 GitHub Release 标签。 - 步骤:1) 以
sign_macos: false手动运行 Release 工作流。2) 确认两个 macOS 架构都完成普通的 DMG/ZIP 打包,且没有使用证书密钥。3) 检查工件和工作流步骤。 - 预期:macOS DMG/ZIP 工件生成并上传,文件名分别带有
-arm64和-x64架构标记,不包含 Developer ID 签名或公证;macOS 装订和 Gatekeeper 检查明确跳过。Windows/Linux 工件和合并后的更新源仍正常发布。该例外不满足 E2E-196c。 - 关联规格:
06-delivery/06-release-runbook.md - 验收:质量(调试打包)
- 里程碑:M6+
- 状态:可选调试通道;标签发布必须满足 E2E-196c。
E2E-196b:未签名的 macOS 软件包展示首次启动指引
- 先决条件:默认未签名的 macOS 发布已为至少一个本机架构生成 DMG 和 ZIP 工件; 测试 macOS 账户可以将应用复制到
/Applications或~/Applications。 - 步骤:1) 打开 DMG 并检查根目录和布局。2) 确认窗口里只有应用与 Applications 链接。3) 确认 DMG 不含 command 助手,也不含
If app won't open, read this.txt。 4) 不解压应用内容,检查 ZIP 根目录,并确认其中同时存在PI-Desktop-macOS-opening-help.txt和可执行的PI-Desktop-macOS-open.command。 5) 阅读说明,将应用移动到/Applications,然后双击 ZIP 中的助手。 - 预期:DMG 使用带品牌的 720×440 背景,只包含应用和 Applications 链接,不包含或 暴露 command 助手或打开说明。ZIP 根目录包含助手和同一份说明。说明包含
xattr -r -d com.apple.quarantine /Applications/PI-Desktop.app,并说明兜底方式仅适用 于 macOS 对可信未签名工件提示应用已损坏或应用打不开的场景;已签名/公证版本无需 执行。ZIP 助手只查找/Applications/PI-Desktop.app和~/Applications/PI-Desktop.app, 在存在时只删除com.apple.quarantine属性,然后打开应用,不使用sudo,也不接受 任意路径;助手会在修改属性前校验CFBundleIdentifier=net.aiuo.pi-desktop。说明不会 声称未签名工件已通过 Gatekeeper 资质验证。 - 关联规格:
06-delivery/06-release-runbook.md、05-security/01-security.md - 验收:质量、安全
- 里程碑:M6+
- 状态:
packaging-footprint.test.mjs已自动覆盖;本机归档检查仍由发布运行器验证
E2E-196c:macOS 标签工件通过 Gatekeeper 且无需移除隔离属性
- 先决条件:推送与
apps/desktop/package.json匹配的vX.Y.Z标签,或手动运行 Release 工作流并保持sign_macos: true(默认);GitHub Actions 已配置CSC_LINK、CSC_KEY_PASSWORD、APPLE_ID、APPLE_APP_SPECIFIC_PASSWORD和APPLE_TEAM_ID密钥;两个本机 macOS 运行器均可用。 - 步骤:1) 运行标签工作流。2) 对每个 macOS 架构检查解压后的应用,使用
codesign -dv --verbose=4确认权限为Developer ID Application: XingYu Liu (DUV63RKYTW)。3) 对应用运行codesign --verify --deep --strict --verbose=2、spctl --assess --type execute --verbose=4和xcrun stapler validate,并检查Contents/Resources/bin/pi-desktop-host-core。4) 确认工作流的 DMG 步骤报告 Apple 公证状态为Accepted,然后对对应的 DMG 运行xcrun stapler validate。5) 在干净的 macOS 配置文件中下载 DMG,将应用移到/Applications后不清除com.apple.quarantine直接打开。 - 预期:每个 macOS 应用通过签名完整性检查,Gatekeeper 报告
source=Notarized Developer ID,应用和 DMG 都包含有效的装订票据。DMG 有自己的提交:从未提交过的 DMG 没有票据,装订会以 error 65 失败,因此标签构建绝不能走到该状态。应用可正常打开,无需xattr命令或“安全性与隐私”覆盖。缺少密钥、提交被拒或装订重试耗尽都会让作业失败。 - 关联规格:
06-delivery/06-release-runbook.md、05-security/01-security.md、ADR 0289 - 验收:质量、安全
- 里程碑:M6+
- 状态:工作流脚本/单元已覆盖;每次发布仍需在干净机器上验证(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-212:GitHub Release 启动 CNB 镜像流水线
- 前提条件:
vastsa/PI-Desktop已配置仓库密钥CNB_MIRROR_TOKEN;aixk/Pi-Desktop上的 CNB 流水线监听api_trigger_mirror;已有带上传 工件的 GitHub Release 标签(例如vX.Y.Z)。 - 步骤:1) 发布或编辑该 GitHub Release,或对
mirror-to-cnb.yml传入 同一标签手动运行。2) 检查 Actions 日志中的解析标签以及对api.cnb.cool的 POST。3) 确认 CNB 流水线以该标签作为MIRROR_TAGS启动。 - 预期:作业仅在
vastsa/PI-Desktop上运行。没有vX.Y.Z标签的手动 运行会在调用 CNB 之前失败。缺少CNB_MIRROR_TOKEN时失败退出。JSON 请求体由jq构造(不是 YAML 字符串插值)。GitHub Release 工件和更新源 不变;CNB 只是同一标签的镜像。 - 链接规格:
06-delivery/06-release-runbook.md - 验收:质量(发布镜像)
- 里程碑:M6+
- 状态:源合同已覆盖(
ci-workflow.test.mjs);实际启动 CNB 仍为操作 验证(除非明确要求,否则不要在本地跑 E2E)
E2E-092:打包的运行时是独立的,没有重复的依赖项
- 先决条件:本机 macOS arm64 和 Intel x64、Windows x64 及 Linux x64 软件包从干净的发布主机目录构建;干净的应用程序配置文件; 提供英语和中文版本;可以禁用外部网络访问 环回仍然可用;与 OpenAI 兼容的确定性环回 夹具提供程序返回代码、KaTeX、Mermaid 和终端命令。
- 步骤:
- 记录每个压缩工件格式以及解压的应用程序, 每个本机上的 ASAR、Electron 运行时、区域设置和解压本机大小 跑步者。
- 检查 sidecar、主机、生产的 ASAR 和资源库存 模块、源映射、tests/examples/declarations、Chromium 语言环境以及 本机预构建目标。
- 对每个 macOS 软件包使用
file(或lipo -info)检查应用程序 可执行文件和Resources/bin/pi-desktop-host-core;确认 arm64 和 x86_64 软件包只包含声明的架构,且 Rust 主机与 Electron 应用一致。 确认共享的apps/desktop/package.jsonmacOS 配置生成 arm64 文件PI-Desktop-X.Y.Z-arm64.dmg和PI-Desktop-X.Y.Z-arm64-mac.zip, Intel 工件使用PI-Desktop-X.Y.Z-x64.dmg和PI-Desktop-X.Y.Z-x64-mac.zip;确认发布目录包含 DMG、ZIP 和合并后的latest-mac.yml更新源,且更新源中的 URL 和校验和与这些打包工件一致。 - 配置环回装置提供程序,禁用外部出口,然后 从干净的配置文件启动。英文和简体切换 中文,要求确定性响应,渲染通用 JavaScript/TypeScript、Python、Rust、shell、Mermaid 和未知语言 栅栏加上 KaTeX 和美人鱼图,打开终端并验证主机 和代理边车健康状况。
- 预期:每个 macOS 软件包仅包含一个捆绑代理 sidecar、一个与声明 架构匹配的 Rust 主机,并且仅包含配置的 Chromium 区域设置包。发布 输出包含两个本机 macOS 架构、DMG/ZIP 工件和一个合并的更新源。每个 macOS DMG 和 ZIP 都带有标准的
-arm64或-x64架构标记,发布输出中不会保留 通用 macOS DMG、ZIP 或 blockmap。按架构的更新元数据指向这些名称且不会发生 冲突。该 软件包仅包含一个捆绑代理 sidecar,即 目标本机 Rust 主机,并且仅配置了 Chromium 区域设置 包。 Renderer 依赖关系通过 Vite 输出存在,而不是重复 原始node_modules;依赖源映射、测试、示例、声明、 第二个代理运行时树,以及可靠的排他性非目标本机资产 缺席。精心策划的 Shiki 语法在未知的栅栏中突出显示本地 保持纯文本可读。离线 shell 启动并且所有装置 功能使用本地打包资产; provider/update 网络故障会导致 不阻止启动。 - 链接规格:
02-architecture/01-architecture.md,02-architecture/02-tech-stack.md、03-runtime/07-process-model.md、04-ux/02-i18n-english-first.md、05-security/01-security.md、06-delivery/06-release-runbook.md,D008 - 验收:A(应用程序启动),质量
- 里程碑:M5
- 状态:单位覆盖(
packaging-footprint.test.mjs验证静态 依赖项和构建器配置);原生库存并离线打包 启动草稿
E2E-093:变异工具序列化并从过时的编辑上下文中恢复
- 先决条件:项目绑定的 Agent 会话具有可写工作区; 提供商装置可以在一个中发出两个相同会话的 Agent/
Write调用 工具批次;第二次编辑可以被赋予一个过时的tag; Bash 命令可以返回带有诊断信息的非零退出代码。 - 步骤:
- 启动一个任务,为同一会话发出两个突变,同时还 发出独立的 read/search 调用。
- 在第一个突变运行时检查关键工具结果和转录本。
- 强制第二个
Edit携带一个已无法哈希出该文件的tag,且其锚点 无法被恢复重映射,然后允许代理重新读取文件并从当前内容重试。 - 运行以非零值退出的 Bash 命令并检查其工具结果 内联状态。
- 对范围互相重叠的
ops负载重复此操作。 - 如果任务使用广告工作区之外的专用工作树, 验证其受保护的 Bash 编辑和生成的
git diff。
- 预期:
- Read/search 调用可能会重叠,但一次只执行一个 Agent/
Write一次会话;排队的突变不会消耗另一个全局变量 等待时的变异槽。 - 过时 tag 的编辑在不更改文件的情况下失败,并返回携带实时 tag 与锚点处 当前内容的
EDIT_TAG_MISMATCH;范围重叠的负载在任何写入之前以EDIT_RANGE_INVALID失败。 - 非零 Bash 命令被标记为失败,同时保留其退出代码, 用于代理和诊断的 stdout 和 stderr。
- 重试执行一次新读取并对当前文件进行操作;一旦该路径用完恢复宽限,第 3 次计数的同路径 失败——或第 3 次失败的 shell patch 命令——返回终止工具结果以及一条可见的
MUTATION_RETRY_BUDGET_EXHAUSTED行,停止变异工作流程,并且不重复修改旧的补丁工件或其块 标头。 - 最终文件准确包含预期的更改以及 diff/review 数据 不包含部分或交错突变。
- Read/search 调用可能会重叠,但一次只执行一个 Agent/
- 链接规格:
03-runtime/03-tools-and-permissions.md,03-runtime/06-host-rpc-protocol.md、03-runtime/08-error-codes.md - 验收:E(工具和权限)、质量
- 里程碑:M5
- 状态:单位覆盖(
tool_budget.rs、tools/mod.rs、agent-runtime/runtime.test.ts);完整的 provider/UI 旅程草稿
E2E-096:就地恢复临时提供程序流故障
- 先决条件:项目绑定的 Agent 会话使用确定性 发出部分辅助流的提供商装置,终止一次, 然后下一个请求成功;第二场比赛可以终止 11 次; 第三个装置在一次尝试中于标头之前返回
OpenAI API error (502),并在下一次尝试中于流中返回它; 第四个装置返回连续 11 个 502;第五个装置返回带Retry-After的 503。 - 步骤:
- 使用单端接夹具开始 Agent 转动,并观察 部分助理回应。
- 等待有界重试并检查成绩单、会话状态和 恢复后的终端诊断。
- 对 11 次终止夹具重复并检查端子错误 message/event 及其诊断详细信息。
- 运行混合阶段 502 装置,并针对标头之前和流中的 502 检查请求计数与终端诊断。
- 运行持续 11 次 502 的装置并检查终端错误。
- 运行 503
Retry-After装置并检查观察到的等待。 - 重新加载会话并验证是否只有已完成的响应或 单终端故障助手依然耐用。
- 打开设置中的「无尽重试」后,故障超过默认次数仍继续;停止回合时不再发起后续请求,关闭后恢复有界上限。
- 预期:
terminated被分类为STREAM_FAILED,上游网关502/503/504被分类为可重试的PROVIDER_ERROR。- 非 429 瞬时故障共享一个有界预算:在初始尝试之后最多 重试 10 次,总共 11 次提供程序尝试,由请求设置和流式传输 交付共享。每次重试都等待一个可中止的有界退避,从模型 上下文中删除失败的助手,并且不产生重复的助手气泡或 终端错误通知。
- 流中的 502 会被重试,而不是立即显现。混合阶段装置在两个 阶段之间花费同一个计数器,总共进行 11 次尝试,而不是每个 阶段各重试一次。在没有
Retry-After标头时,观察到的等待 依次为 1 秒、2 秒、4 秒,然后是 8 秒,并且在两个阶段中完全 相同。 - 只有失败的请求会被重放:会话、它的转录本以及任何已完成的 工具调用在每一次重试中都保持不变。
- 恢复的回合发出一个终端生命周期并保持相同的可见 助理消息 ID。时序日志为每次重试记录
outcome=retry及其尝试编号,以及最终结果。 - 第 11 次终止发出一个终端
STREAM_FAILED辅助错误和 生命周期事件;持续 502 的装置发出一个终端PROVIDER_ERROR。两者都携带retryAttempt: 10。可用的详细 信息包括阶段、流计时和提供商状态,无需凭据或不受限制的 提供商正文。 - 503 装置等待服务器的
Retry-After,而不是客户端退避。非 429 的服务器等待和回退等待都以 8 秒为上限。 - 流中的 HTTP 429 由 429 预算单独的 10 次重试路径覆盖;两个 预算互不占用。
- 身份验证、模型选择、上下文和格式错误的请求失败 不进入任何提供程序重播路径,包括来自格式错误的 400/422 请求的不可重试
PROVIDER_ERROR。无尽模式不改变分类,只去掉已准入网络/瞬时故障的次数上限; 仍可中止,并以无界重试指示标出。
- 链接规格:
03-runtime/01-ipc-protocol.md,03-runtime/02-agent-runtime.md、03-runtime/08-error-codes.md、08-meta/decisions-log.md(D186、D259)、ADR 0050、ADR 0128 - 接受:C(聊天和直播)、F(持久性)、H(诊断)、质量
- 里程碑:M5
- 状态:单位覆盖(
agent-errors.test.ts、provider-retry.test.ts、runtime.test.ts、subagent.test.ts);完整 provider/UI 旅程草案
Synchronized update (#699, E2E-096 / E2E-149): Alternate one network failure with each of eleven successful Read responses, then fail once before the final answer. All twelve independent failures must recover, starting at retry 1 each time, with no duplicate tool execution. Complete successful responses replenish both budgets; headers, partial output, and phase changes do not. A new persistent outage after recovery still gets ten retries and reports retryAttempt: 10. Restore the provider and verify Continue succeeds. The same reset rule applies to rate limits and builtin subagents.
The socket-failure, interrupted-stream, Responses, exhaustion, Continue, and eleven-tool-round desktop paths are verified by scripts/e2e-provider-recovery.mjs; real agent-loop coverage is in provider-recovery-flow.test.ts. Other scenario variants remain Draft.
7A。 M6 Plan 和 shell 场景
E2E-104:旧合约值迁移到架构 v11
- 先决条件:Schema-v8 固定装置包含会话、应用程序默认值和 具有旧版
chat值以及成绩单和权限的预定记录; schema-v7 和 schema-v9 固定装置覆盖两个受保护的入口路径。 - 步骤:1) 启动 host-core 并允许受保护的迁移(首先是 v7) 达到 v8)。 2) 检查会话、设置、计划模式、
plan_approvalsfields/indexes,以及精确可读的 v8/v9 备份。 3) 重新启动并检查相同的记录。 4) 对格式错误的应用程序设置重复此操作, 格式错误的计划配置、无效的顶级操作模式以及未知的 或错误的平台默认 shell。 5)重复平台有效的持久化 shell 标记为暂时不可用并嵌套扩展mode字段。 - 预期:每个旧模式都是
plan,Agent 仍然是新会话,并且 新任务默认,transcripts/permissions 存活,plan_approvals保留 批准数据并具有 artifact/execution 字段,v8→v11 是一个原子 在其 WAL 检查点和 v8 备份、v9 和 v10 创建之后的事务 可读备份,迁移失败会离开源架构 权威的。每个畸形或 无效的固定装置在架构升级之前无法关闭。暂时的 不可用的平台有效 shell 仍保留用于运行时回退,并且 嵌套扩展模式保持不变。 - 链接规格:
00-baseline.md、03-runtime/04-data-storage.md、03-runtime/01-ipc-protocol.md、04-ux/06-settings-ia.md、ADR 0053 - 接受:F(持续),H(诊断)
- 里程碑:M6
- 状态:自动(2026-08-05通过):host-core 139/139,包括 15 个 专注于数据库测试,涵盖 schema-v7→v8→v11、v8→v11、v9→v11 和 v10→v11 守卫的路径, 精确可读的备份、故障关闭回滚、重新启动、转录、设置、 计划模式、批准字段和索引测试
E2E-105:Plan 策略仍然具有主机权威
- 先决条件:项目绑定会话在 Plan 和 BrowserPreview 中处于空闲状态, 一个插件工具和一个伪造的
requestedMode = "agent"夹具。 - 步骤:1) 检查可见的 Plan 工具。 2) 使用 Read/Glob/Grep 和 BrowserPreview。 3) 尝试编写、编辑、插入和 各种权限模式下通过主机的未知工具。 4) 在下面运行 Bash 询问、接受编辑和自动。
- 预期:Plan 拒绝 Write/Edit/plugin/unknown 工具,无论 伪造模式、补助金或自动; Bash 遵循所选的权限模式。的 运行时仍为 1 pi Agent 并且所有拒绝都会经过审核。
- 链接规格:
03-runtime/02-agent-runtime.md,03-runtime/03-tools-and-permissions.md、03-runtime/05-host-core-rust.md、03-runtime/06-host-rpc-protocol.md、05-security/01-security.md、ADR 0053 - 接受:E(工具和权限)、安全
- 里程碑:M6
- 状态:自动(2026-08-04 通过):
test:e2e:plan加 host-core permission/policy 和代理运行时工具组合测试
E2E-106:SubmitPlan 拒绝进入可编辑计划并重新提交新工件
- 先决条件:项目绑定会话在 Plan 中与提供商处于空闲状态;
.pi/plan/不存在或为空,并且工作区允许主机工件 创造。 - 步骤: 1) 让 Agent 使用固定标题、Markdown 和 问题。 2) 逐字节检查新的
.pi/plan/*.md文件和plan_approvals行。 3)检查卡牌的标题和神器开启者;确认开启者在内置文件 视图中打开(该视图不可启动时回退到宿主机文件标签,D452),且 question/description、validity/deadline 和状态不存在且仅提供批准和拒绝。 4) 打开审批模式菜单,选择自动, 并验证下一个批准默认为自动。 5) 拒绝 提案。 6) 确认持久模式为Plan,实时状态为可编辑planning, 批准门已清除,并接受稍后的提示。 7)让 Agent 在新回合中修改并调用SubmitPlan一次,并带有完整的 快照。 8) 使用记住的自动模式批准第二个提案。 - 预期:主机将准确提交的 Markdown 字节保留在新的文件中 独特的神器,记录其相关的 path/hash/size 与结构化 title/question,并且绝不让渲染器或 sidecar 写入或替换它。 标题衍生的工件文件名可以从标题中识别出来,包括 非 ASCII 标题字符。卡牌显示标题,并在内置文件视图中打开神器, 该视图不可启动时回退到宿主机文件标签(D452); 它不需要内联 question/Markdown/hash/size 或 validity/deadline 指标。选择的审批方式会被本地记住,以便下次使用 批准。 第一行的拒绝是终止的,离开持久模式 Plan,并返回 实时状态到可编辑规划。后来的 prompt/resubmission 创建了一个 第二个完整的 Markdown 快照和不同的
.pi/plan/*.md工件; 第一个工件字节保持不变。批准第二项提案 记住的自动模式仍然将相同的 Agent 更改为 Agent 并排队执行。 - 链接规格:
03-runtime/01-ipc-protocol.md,03-runtime/02-agent-runtime.md、03-runtime/04-data-storage.md、03-runtime/06-host-rpc-protocol.md、04-ux/03-permission-ux.md、04-ux/08-component-spec.md、05-security/01-security.md、ADR 0053 - 接受:C (conversation/stream)、E(权限)、F(持久性)
- 里程碑:M6
- 状态:自动(2026-08-05 通过):
test:e2e:plan验证主机 artifact/approval 生命周期。可选的实时test:e2e:plan-ui案例 需要环境提供的 OpenAI 兼容提供程序;授权运行 模型gpt-5.6-luna通过了 6/6,控制台诊断为零。它使用了 真正控制Composer和Send,现场Agent调用EnterPlanModeSubmitPlan,正常呈现 通过 preload/Main 请求批准解决, 批准的执行会发出精确的耐用标记,并且私人 env-gate WeakMap 检查之前证明了相同的DesktopAgentRuntime对象 并在批准后。 Main/Host/sidecar PID 保持稳定;凭据从不 输入 CDP 或输出。默认无钥匙运行仍为 5/5 与现场案例 明确跳过。
E2E-CHAT-opaque-floating-decision-and-retry-surfaces:停靠区、Plan 审批条与重试表面遮住正文
- 状态:已自动化(
pnpm test:e2e:composer-occlusion、pnpm test:e2e:theme-surfaces、apps/desktop/test/plan-mode-source-contract.test.mjs、apps/desktop/test/active-turn-surface.test.mjs) - 优先级:P2
- 覆盖:C、品质 / 浮动 Composer 与重试表面
- 先决条件:渲染器 CSS 为
apps/desktop/src/styles下的生产源。 - 步骤:
- 在两套内置主题和一套自定义主题中检查
.composer-dock-docked的计算背景。 - 滚动长会话,让一行正文经过悬浮 Composer 下方。
- 检查 Composer 停靠栏样式中的
.plan-approval-bar。 - 检查记录样式中的
.run-activity-error-popover.message-error。 - 在实时会话中悬停或聚焦正在重试的活动行。
- 在两套内置主题和一套自定义主题中检查
- 预期:
- 停靠区横跨整个宽度绘制不透明的
--ds-bg-primary工作区表面;正文在 Composer 边界处消失,不会留在输入框下方或圆角外侧。 - Plan/Goal 审批条使用
--ds-bg-composer加--ds-shadow-composer,而不是正文流里的--ds-tile薄洗,因此在透明停靠栏上仍可读。 - 重试 hover tooltip 把错误色混在
--ds-bg-elevated-opaque上,记录正文不会透出。 - 重试 tooltip 的高度被限制在尾部状态行上方的可用空间内,其余部分可滚动, 因此较长的 provider 消息会保留开头几行,不会被记录滚动容器或会话顶栏遮住。
- 停靠区横跨整个宽度绘制不透明的
- 链接规格:
04-ux/03-permission-ux.md、04-ux/08-component-spec.md - 接受:C、品质
- 里程碑:M6
- 状态细节:源码契约断言 CSS 标记。实时悬停仍为目视检查。
E2E-107:Plan 批准使用一个绝对 30 分钟到期时间
- 先决条件:存在带有可控时钟的待处理 Plan 请求。
- 步骤: 1) 记录
createdAt和expiresAt。 2)重新加载渲染器并 重新请求。 3)将时间提前到最后期限而不解决。 4) 到期后尝试批准。 - 预期:Renderer 重新加载仅重新水化仍待处理的行,而 主机仍然存活,显示的倒计时保留原来的绝对值 截止日期;拒绝、过期、approved/completed 和中断终端 卡不属于重新装水的一部分。过期记录
expired,留下 会话 Plan,返回PLAN_APPROVAL_TIMEOUT,并拒绝迟到的响应 无需更改模式或许可。 - 链接规格:
03-runtime/06-host-rpc-protocol.md,03-runtime/08-error-codes.md、03-runtime/10-session-state-machine.md、04-ux/03-permission-ux.md、ADR 0053 - 接受:E(权限)、H(诊断)、安全
- 里程碑:M6
- 状态:自动(2026-08-04 通过):
test:e2e:plan-ui涵盖待定 渲染器重新加载;test:e2e:plan和确定性 host-core 延迟到期 测试涵盖绝对期限、超时持久性和失败关闭 分辨率。没有声称终端卡重新加载水合。
E2E-108:启动栅栏中断待处理的 Plan 工作
- 先决条件:Plan 请求正在等待实时审批服务员,并且 运行计划回合;主机和渲染器可以独立重启。
- 步骤:1) 重新加载渲染器并列出实时请求。 2)重新启动 决议前的 host/app。 3) 检查
plan_approvals行,转动并 启动后的会话。 4) 提交重启前响应。 - 预期:Renderer 在主机保持活动状态时重新加载,保留 仍然悬而未决的行和原始截止日期。以事务方式完全重启 Host/app 标记待处理行并在 RPC 服务之前转动 interrupted/aborted,离开 会话 Plan,并为旧响应返回
PLAN_APPROVAL_STALE。不 可操作的陈旧卡或执行已恢复,并且 UI 不需要 重启后呈现中断的终端快照。无进程纪元 字段被保留或发送。 - 链接规格:
03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md、03-runtime/07-process-model.md、03-runtime/10-session-state-machine.md、04-ux/08-component-spec.md、ADR 0053 - 接受:F(持久性)、H(诊断)、安全性
- 里程碑:M6
- 状态:自动(2026-08-04 通过):
test:e2e:plan执行真实操作 主机重启和host-core恢复测试验证中断的持久状态; 待处理的渲染器重新加载断言由 E2E-107 UI 通道覆盖。
E2E-109:重新启动后不会重播已批准的 Plan 执行
- 先决条件:Plan 请求已通过 Ask 获得批准并被捕获 一次处于
queued状态,一次处于running状态。 - 步骤: 1) 在每个状态期间重新启动主机。 2) 检查
plan_approvals.execution_state并在启动后转记录。 3)观察 provider/tool 调用和会话模式。 4) 开始新的用户回合 明确地。 - 预期:Queued/running 执行字段变为
interrupted, 关联的转向中止,没有重播 provider/tool 调用,并且会话 仍为 Agent,因为批准已提交。仅接受新回合 用户启动后;没有中断的终端卡或过时的操作 重启后需要。 - 链接规格:
03-runtime/04-data-storage.md,03-runtime/06-host-rpc-protocol.md、03-runtime/07-process-model.md、03-runtime/10-session-state-machine.md、ADR 0053 - 接受:C (conversation/stream)、F(持久性)、H(诊断)、安全性
- 里程碑:M6
- 状态:自动(2026-08-04 通过):
test:e2e:plan重新启动实际排队 并声明执行并验证没有重播以及 Agent 保留
E2E-110:计划的 Plan 在任何工作之前被拒绝
- 先决条件:计划任务是 Plan,无人值守运行程序是 可用;可以观察提供商、工件和队列写入。
- 步骤: 1) 通过无人值守路径触发任务。 2) 检查 提供程序跟踪、
.pi/plan/和plan_approvals表。 3) 切换 task/session 显式更改为 Agent 并再次运行。 - 预期:Plan 在提供商、工件、批准或队列之前被拒绝 与
PLAN_REQUIRES_INTERACTIVE_SESSION合作;无后台自动批准 发生。显式 Agent 选择允许正常的无人值守策略。 - 链接规格:
03-runtime/04-data-storage.md,03-runtime/08-error-codes.md、04-ux/01-ui-ia.md、ADR 0053 - 接受:F(持久性)、H(诊断)、安全性
- 里程碑:M6
- 状态:自动(2026-08-04 通过):
test:e2e:plan验证 Plan 副作用之前的拒绝和显式 Agent 执行独立于 全局默认值
E2E-111:强制执行主动转向、待批准和配置边界
- 先决条件:一个会话有一个活动的 Agent 回合,并且另一个会话处于活动状态 闲置的; Plan 运行可以变为 pending/queued/running。
- 步骤:1) 尝试第二个提示,mode/provider/model/permission/shell 配置更改,以及活动轮期间的第二次 Plan 提交。 2) 让轮流变为等待批准并重复提示和配置 尝试。 3) 拒绝批准。 4) 提交稍后提示并让 Agent 创建修订后的 Plan 快照。 5) 会话结束后重复配置 可编辑的规划。
- 预期:Active-turn/configuration 更改、提示和第二个 Plan 当回合或活动待决时,提交失败并显示 pending/queued/running/Plan 存在批准;仅阻止原始会话。拒绝返回 Plan 的持久会话和规划的实时状态,清除大门,以及 允许后来的 prompt/new 工件。终端提案快照不 禁用输入、Composer 模式芯片或当前期间的模型选择 渲染器的生命周期。 Idle/planning配置成功且无跨会话 事件或工作区根泄漏。
- 链接规格:
03-runtime/01-ipc-protocol.md,03-runtime/02-agent-runtime.md、03-runtime/06-host-rpc-protocol.md、03-runtime/10-session-state-machine.md、04-ux/08-component-spec.md、ADR 0053 - 验收:C (conversation/stream)、E(权限)、质量
- 里程碑:M6
- 状态:自动(2026-08-04 通过):
test:e2e:plan验证主机 边界和test:e2e:plan-ui验证仅待处理的门控以及可编辑 当前渲染器生命周期内的 rejected/terminal 状态
E2E-112:可选择的 shell 目录保留默认值
- 前提:主机有一个可用的平台目录条目,一个固定装置可以 使持久选择不可用,并且项目绑定的 Agent 会话是 闲置。 Windows 通道练习多选排序。
- 步骤:1) 检查目录中的平台有效 ID
windows-powershell、windows-pwsh、cmd、git-bash和bash。 2) 验证设置 拒绝不可用或错误的平台 ID。 3)选择可用的shell 并坚持defaultCommandShell。 4)使坚持的选择不可用, 重新启动,并验证目录选择第一个可用的平台 shell 与fallback: true。 5) 执行未更改的Bash工具。 - 预期:设置仅保留有效的稳定 shell ID;不可用 条目在指导下仍然不可用,并且稍后仍然不可用 选择有意使用第一个可用的后备。主机调用 有效 shell,同时 tool/protocol 名称保留为
Bash,并且 shell 选择遵循空闲配置边界。 - 链接规格:
03-runtime/01-ipc-protocol.md,03-runtime/03-tools-and-permissions.md、03-runtime/06-host-rpc-protocol.md、04-ux/06-settings-ia.md、04-ux/08-component-spec.md、ADR 0054 - 接受:B (model/config)、E (tools/permissions)、F(持久)
- 里程碑:M6
- 状态:自动(2026-08-04 通过):
test:e2e:plan验证目录, 验证、持久化和重启;确定性 host-core 目录 当存储的 shell 不可用时,测试验证第一个可用的回退
E2E-113:过时的 shell 身份无法关闭
- 先决条件:Bash 回合具有固定的有效外壳 ID/dialect;这 固定装置可以在生成之前更改有效目录选择。
- 步骤: 1) 更改有效的 shell ID 或方言。 2) 执行 Bash 旧的预期 ID。 3) 检查流程创建、回退尝试、审核、 和用户界面错误。 4) 启动新一轮并重试。
- 预期:第一次调用返回
COMMAND_SHELL_CHANGED,开始没有 工艺,且转销后不换壳。审计记录了 选择的 ID 和方言。稍后运行需要新的回合快照。 - 链接规格:
03-runtime/03-tools-and-permissions.md,03-runtime/05-host-core-rust.md、03-runtime/06-host-rpc-protocol.md、03-runtime/08-error-codes.md、05-security/01-security.md、ADR 0054 - 接受:E (tools/permissions)、H(诊断)、安全
- 里程碑:M6
- 状态:自动(2026-08-04 通过):
test:e2e:plan验证过时 创建标记之前的方言拒绝加上 host-core 过时的 ID/dialect 测试
E2E-114:Bash 独立流 stdout 和 stderr
- 先决条件:选定的 shell 可用且确定性命令 写入交错的 stdout 和 stderr 块。
- 步骤: 1) 通过
Bash执行命令。 2)观察host/RPC/UI 输出事件。 3) 检查最终的有界结果和成绩单行。 - 预期:stdout 和 stderr 保持独立,按工具调用排序,并且 进程运行时可见。最终输出保留截断元数据; 没有跨会话或回合的块,并且 Bash 协议名称未更改。
- 链接规格:
03-runtime/06-host-rpc-protocol.md,03-runtime/09-logging-and-observability.md,03-runtime/16-tool-result-limits.md、04-ux/09-interaction-patterns.md、ADR 0054 - 验收:C(流)、E(工具)、质量
- 里程碑:M6
- 状态:自动(2026-08-04 通过):
test:e2e:plan验证不同的 stdout/stderr 通知和最终工具身份; host/runtime 流测试 涵盖有界累积和会话隔离
E2E-115:Bash 超时使用 60 秒和有界覆盖
- 前提条件:选定的 shell 可以运行超过 60 秒的命令; 主机时钟是可观察的。
- 步骤:1) 在没有超时覆盖的情况下运行。 2) 观察60秒 截止日期。 3) 以范围内超驰运行。 4) 提交零、负数和 超过 21,600 秒的覆盖。
- 预期:缺少超时正好使用 60 秒并返回 进程树关闭后的
TOOL_TIMEOUT。范围内的值起作用 1–21,600 秒;超出范围的值将无法验证并且永远不会生成。 - 链接规格:
03-runtime/03-tools-and-permissions.md,03-runtime/06-host-rpc-protocol.md、03-runtime/08-error-codes.md、03-runtime/16-tool-result-limits.md、05-security/01-security.md、ADR 0054、 ADR 0167 - 验收:E(工具)、H(诊断)、安全
- 里程碑:M6
- 状态:自动(2026-08-04 通过):长时间超时
test:e2e:plan测量无覆盖超时为 60,024 毫秒,并验证在范围内 没有延迟标记写入的无效边界
E2E-116:Bash 中止关闭整个进程树
- 先决条件:Bash 命令启动发出信号的子级和孙级 延迟输出;原始会话正在运行。
- 步骤: 1) 启动命令。 2) 中止当前回合。 3)检查过程 后代、输出事件、审核以及关闭宽限后的状态。
- 预期:进程 group/job 树终止,没有后代保留, 没有后续输出到达,本轮返回
TURN_ABORTED,工作空间为 不会自动回滚。 - 链接规格:
03-runtime/03-tools-and-permissions.md,03-runtime/07-process-model.md、03-runtime/08-error-codes.md、03-runtime/16-tool-result-limits.md、05-security/01-security.md、ADR 0054 - 接受:C(中止)、E(工具)、H(诊断)、安全
- 里程碑:M6
- 状态:自动(2026-08-04 通过):
test:e2e:plan中止真实的 后代进程树并验证没有迟到的 marker/output; host-core 测试 验证取消注册表清理
E2E-117:Agent/Plan/Goal UX 和区域设置不包含聊天控件
- 先决条件:应用程序可以在空闲会话下以英语和 zh-CN 运行, Plan 工件夹具、shell 设置和全局搜索的“命令”部分可用。
- 步骤:1) 检查英文 Agent/Plan/Goal、权限、工件批准和 shell 控件。 2) 进入 Plan,检查规划、审批、队列和终端状态,同时保持渲染器运行。 3) 批准并拒绝提案,确认主机确认后审批界面消失。4) 检查“命令”部分, 确认其中恰好包含
builtin.session.new、builtin.agent.compact和三个builtin.mode.*命令;内置/分组只有/new、/compact、/agent-mode、/plan-mode、/goal-mode。确认被移除的命令 ID 以及newChat/openProject/openSettingsdispatch 别名不存在。使用/plan-mode//agent-mode斜杠别名 切换活动空闲会话,确认 Composer 芯片立即改变。在同一草稿中的任一别名后键入 提示并发送;确认模式改变且提示仍是可见的用户回合。单独发送别名并确认它仍是 本地模式切换,不会创建空的转录回合。如果提示发送失败,确认完整草稿仍可编辑。 5) 终端提议后重新加载并检查会话,同时断言 Electron Main 和 Host 进程身份没有 改变。6) 在 zh-CN 中重复。7) 搜索可见命令,确认没有 Chat 模式和 request-changes 控件。Host/app 重启恢复由 E2E-108 和 E2E-109 分别执行。 - 预期:Agent 是默认值;输入左侧的 Composer 芯片是 唯一的活动会话 Agent/Plan/Goal 控制; Plan 和 Goal 展示 Ask/Accept edits/Auto,提交的标题,工件开启器, 记住批准模式,仅限 approve/reject,shell catalog/fallback 状态, 和局部失败关闭状态。否 聊天模式,
/chat-mode,请求更改操作,内联 Markdown/hash/size 要求,或暴露过时的可操作队列;终端检查站 元数据可能仅在当前渲染器生命周期内保持不可操作, 而输入框认可面则在主持人确认后被移除 分辨率。 Renderer 重新加载不会重新水合被拒绝、过期、approved/completed 或 终端卡中断。 Host/app 重新启动不会重播工作或恢复 陈旧的操作,并且 UI 不需要呈现中断的终端 快照;page = "chat"仍然是内部路由。 - 链接规格:
01-product/01-product-scope.md、04-ux/01-ui-ia.md、04-ux/04-builtin-commands.md、04-ux/03-permission-ux.md、04-ux/06-settings-ia.md、04-ux/08-component-spec.md、04-ux/02-i18n-english-first.md、ADR 0053、ADR 0054 - 验收:C(对话),质量
- 里程碑:M6
- 状态:自动化(2026年8月4日通过):raw-CDP
test:e2e:plan-ui针对现有的 Electron Main 探针使用环境门控 主机,断言稳定的 Electron/Host PID,涵盖挂起的恢复、实时终端 控制、拒绝和 approved/completed 终端卡缺席后 渲染器重新加载、EN/zh-CN 和 1280×800 / 900×700 渲染。 E2E-108/E2E-109 涵盖主机重启中断、陈旧动作拒绝和无重放。
E2E-118:重新生成的回合在分支存档后保留其最终答案
- 先决条件:项目绑定的 Agent 会话,其成绩单已包含 一个完整的交换,一个提供足够长的多工具轮流的提供商 最后的助理消息通过持久性发件箱登陆,以及 对
<data_dir>/sessions/<id>.jsonl的读取访问权限,<id>.revisions.jsonl和messages索引。 - 步骤: 1) 重新生成助手答案,以便 root 用户轮流携带
revisionCount/activeRevision和修订版 1 已存档。 2)让 重新运行完成一个回合,以工具调用结束,然后是最后的助手 消息。 3) 紧接着agent_end之后,检查转录文件,messages行,以及存档的修订有效负载。 4) 重新加载会话。 5) 将根气泡翻页至修订版 1,然后再次前进。 - 预期:最终的助理消息出现在转录文件中, 在索引中,并作为存档分支的最后一条消息。每一条消息 转牌保留其所属的
turn_id。根带有revisionCount = 2和activeRevision = 2,重新加载显示完整回合,分页恢复每个 枝全。没有对session.replaceMessages进行调用 回合完成路径。 - 链接规格:
03-runtime/04-data-storage.md§4.9/§7,03-runtime/06-host-rpc-protocol.md§4、ADR 0041、ADR 0060 - 接受:C(对话)、F(持久)、H(诊断)、质量
- 里程碑:M6
- 状态:由 host-core 单元测试覆盖(2026-08-06):
save_active_branch_revision_keeps_a_message_appended_after_its_read档案 在读取后附加一条消息并断言转录本, 存档的有效负载和索引(seq, turn_id)行;replace_messages_preserves_owning_turn_ids涵盖了剩余的重写 来电者。 UI 分页仍然是手动的。
E2E-119:并行子代理在不进入父级上下文的情况下报告
- 先决条件:项目绑定的 Agent 会话,其用户主目录包含
~/.agents/subagents/scout.md(只读,无tools键)、~/.agents/subagents/fixer.md(tools: Read, Edit)、~/.agents/subagents/pinned.md(model:命名第二个 配置的提供程序)和~/.agents/subagents/broken.md(缺少name);提供商 可以驱动其流在一条辅助消息中发出两个Task调用; 权限模式ask,因此代表的Edit被门控;读取访问权限<data_dir>/sessions/<id>.jsonl和messages索引。 - 步骤:
- 提示一个回合,其中助手发出两个
Task呼叫 —scout和pinned— 在一条消息中。观察代表团卡,同时运行和 各自安顿下来后;折叠它,然后展开每个节点。 - 提示一个回合,其中助手消息只发出一个
Task调用 —scout— 并将其呈现方式与步骤 1 的进行比较。 - 提示轮流,其中两个
fixer代表各自编辑不同的文件,并且 仅回答第一张许可卡。 - 回答第二张牌,然后提示第三回合,其中有两个
fixer代表们编辑相同文件。 - 当一张卡出现在屏幕上而另一张卡出现时,启动扇出并按停止。 排队。
- 提示
Task调用命名为broken,然后调用一个不命名的代理 存在,则其定义固定未配置的提供商。 - 将会话切换到 Plan,然后切换到 Goal,并检查工具目录。
- 重新加载会话并重新展开委托卡和每个
Task节点。
- 预期:
- 步骤 1 中的两个代表同时运行,并且
pinned单独进行流式传输 provider/model,而父级保留会话的。 - 步骤 1 中的两个
Task调用形成一张全角委托卡。同时 活动时,它打开一次,其标题会更新子代理和已结算的计数; 结算后保留用户的扩展选择并汇总报告 成功、警告或问题状态加上经过的时间。 - 步骤 2 中单独的
Task画出同一张卡,只是只有一个委派节点 — 相同的根、连接线、结果、运行时长和步骤数 — 而绝不是 紧凑的单行工具行(D265)。它的聚合行按一个 Subagent 措辞, 所以任何语言都不会读成“1 个 Subagents”或“Subagents 正在工作”。 - 扩展卡显示一个连接到
scout的主代理根,并且pinned按父行顺序排列,代表之间没有发明的边缘。每个 节点显示其代理、简短描述、明确结果、持续时间和步骤 计数。展开节点会显示摘要、报告一次,并且status/turns/toolCalls。委托行仅出现在该节点内部, 绝不在回合流或小地图中。 - 家长的下一个请求包含报告和无委托消息或 工具排;这些行仍然存在于转录文件中并且 索引为
meta.parentToolCallId和meta.agentName。 - 仅渲染头部许可卡;它命名了请求代表并 后面等待的号码。回答它会显示下一张牌,并且都不会 答案解决了另一个请求。
scout根本无法调用Edit或Write;fixer可以。相同文件编辑 步骤 4 按定义的顺序应用,并且不会丢失对方的写入。- Stop 拒绝显示的请求和排队的请求,并且两个委托都结束
aborted位于其自己的Task节点内的文本和图标中 — 父轮 结束一次,聚合卡结算并发出警告。 broken不在带有启动诊断和会话的目录中 保留其他三名代表;未知的代理和无法解析的模型 每个引脚都会失败,因为Task工具错误指出了原因,没有回退到 会话提供商并且没有转向失败。Task不在 Plan 和 Goal 的目录中。- 充值后卡片默认折叠;重新扩展保留节点 顺序、归因、结果和嵌套内容与出现的完全相同 住。
- 步骤 1 中的两个代表同时运行,并且
- 链接规格:
03-runtime/02-agent-runtime.md§5f/§7.2b/§8,03-runtime/03-tools-and-permissions.md§10.2,03-runtime/04-data-storage.md§4.7a、04-ux/03-permission-ux.md§6a、04-ux/08-component-spec.md§9.9,ADR 0062,决策日志 D201、D265 - 接受:C(对话)、E(工具和权限)、F(持久性)、 安全、品质
- 里程碑:M6
- 状态:由单元测试覆盖(2026-08-06):
packages/sharedsubagent-definition.test.ts和packages/agent-runtimesubagent-definitions.test.ts(frontmatter、工具过滤、格式错误 文档、全局用户覆盖内置、旧版maxTurns被忽略);subagent.test.ts(报告边界、中止、事件归因、提示框架)和path-lock.test.ts(同路径排序、并发上限);桌面permission-inline.test.mjs(队列顺序、ID 匹配删除、工具调用删除、中止拒绝队列、 卡副本),subagent-wiring.test.mjs(主进程发现和模型引脚) 和subagent-transcript.test.mjs+assistant-turns.test.mjs(嵌套、 单一报告打印、记忆、接纳单个委派的卡片判定,以及两种语言 下按数量措辞的聚合文案),加上subagent-topology.test.mjs(委托检测、结构化结果和汇总计数)。满 多提供商扇出和渲染拓扑交互仍然是手动的。
E2E-SUBAGENT-legacy-turn-limit-frontmatter-is-ignored
- 先决条件:Agent 模式。一个用户文档
~/.agents/subagents/legacy-worker.md,其 frontmatter 在有效的description和tools旁声明maxTurns: 2;第二个文档把同一个键拼作max-turns: 2; 第三个文档从不提及它。 - 步骤:1) 打开设置 → 子智能体,确认内置分组和全局分组中的每一行都渲染出它的 工具授权,并且页面中以及编辑器的高级折叠区里都不存在回合上限字段。2) 委派给
legacy-worker,让它进行超过两轮的工具调用。3) 通过设置 API 读回该文档,并以 原始文件的形式再读一次。4) 在该行上打开编辑器,不做任何修改直接保存,然后重新 读取该文件。 - 预期:两种拼写都能作为有效定义加载。声明的键会像其他任何无法识别的 frontmatter 键一样被忽略:没有解析错误,没有点名它的警告或诊断,定义仍然能够 解析,文件也不会被改写。该委派永远不会在两轮处被停止,也永远不会报告
truncated;它只在完成或被TaskStop时结束。应用中的任何界面,以及任何 语言环境下chat.subagentStatus的文案,都不会报告回合上限或“已达到回合上限” 状态。 - 链接规格:
03-runtime/02-agent-runtime.md§5f、04-ux/06-settings-ia.md§7、ADR 0253、决策日志 D423 - 验收:C(对话)、品质
- 里程碑:M6
- 状态:草稿——单元测试覆盖在
packages/shared、packages/agent-runtime以及 host-core 的user_subagents回归测试中;完整的 UI 旅程需要具备条件的环境。
E2E-SUBAGENT-resume-a-settled-delegation
- 先决条件:一个使用确定性本地传输的 Agent 会话,可以按需让委派完成、失败、被停止,或保持 运行。两份用户定义:
~/.agents/subagents/scout.md(只读)与~/.agents/subagents/fixer.md;工作区文件src/report.ts,其结论容易按文件与行号定位;以及 一个可以从当前配置中移除的模型绑定。父级目录提供Task、TaskWait、TaskList和TaskStop。 - 步骤:
- 把一份「结束时必须给出一个结论及其文件与行号」的任务委派给
scout,让它以completed结束。记下 Task 结果返回的delegationId。 - 发送一轮,把该 id 作为
Task.resume传入,要求复述同一个结论,并找出第一次运行没有触及的 第二次出现。展开委派卡片,再检查父级自己收到的内容。 - 发送一轮,只说「复用一下 scout 已经找到的东西」,不传
resume。 - 启动一个长时间运行的
scout委派,在它仍在运行时对它调用Task.resume;然后,在另一条链 恢复后的运行仍未结束时,对该链再次调用Task.resume。 - 用
TaskStop停止一个委派,终止第二个,并在第三个仍在工作时关闭应用。重启后,按TaskWait报告的 id 对这三个逐个调用Task.resume。 - 让一个委派在成功读取一次后失败,然后对它调用
Task.resume。 - 发送一次同时携带
resume与model的Task调用。 - 把该链记录的模型从当前配置中移除,然后
Task.resume该链,并查看它的委派生命周期 details 与卡片。 - 让
scout的两条链先后结束,resume较早的那条,再让同一定义的第三条链结束,读取下一次 提示给出的可复用清单;随后让一条链保持运行,同时同一定义另有两条链结束。 - 让一条链累计超过 5 万行只读工具输出,然后用它的 id 调用
Task.resume。 - 分别以
Explorer、explorer.md、另一个定义的名称作为Task.agent,恢复一条scout链。 - 让一条链读过超过八个文件,并在同一会话内再发两次提示,比较每次提示编排出的可复用清单。
- 重启应用,在不改动其它任何设置的情况下
Task.resume重启前那条completed的链。 - 在一个能解析某条链、但已不再持有该链任何委派行的固定装置会话中,用该 id 调用
Task.resume。
- 把一份「结束时必须给出一个结论及其文件与行号」的任务委派给
- 预期:
- 步骤 2 从该链继续:新的运行给出先前结论的确切文件与行号,且它的行里没有对那个文件的重新 整读。
Task返回一个新的delegationId,而父级自身上下文里仍然只有该委派的一份最终报告, 也没有委派工具行。 - 步骤 3 是冷启动:一个全新的委派,行里没有先前的结论,持有新的
delegationId,且与先前的链 没有关联。不传 id 就绝不继承上下文,无论提示怎么写。 - 步骤 4 两次都以工具错误失败。仍在运行的委派被报告为仍在运行,并提示先用
TaskWait收敛; 不会启动任何东西,也不会排队,运行中的委派继续工作。 - 步骤 5 把
stopped、aborted,以及应用在它仍在工作时被关掉的运行(重启后读作interrupted)一律拒绝为不可恢复,各自说明该原因并指向新建委派;没有任何运行被启动。 - 步骤 6 像
completed一样恢复失败的链:它已经做出的读取成为新运行的种子,而它失败的助手行 不会被重放。 - 步骤 7 以工具错误拒绝:恢复后的运行沿用链的模型,错误信息指向「换模型要新建委派」。
- 步骤 8 仍然可以恢复,走定义现在解析到的绑定,并且该委派的生命周期 details 带上先前模型的 id 作为
modelChangedFrom。父级通过它轮询的生命周期快照看到它,卡片也随该运行显示它,因此这次 切换不是静默的。 - 步骤 9 每个定义最多保留两条可复用链:最久未活动的已结束链被整条淘汰,因此它的每个 id 都按 未知委派作答并附上当前可复用清单;而最新运行仍在工作的链永不被淘汰——该组可以暂时超出上限, 也不会让一个仍在运行的委派搁浅。
- 步骤 10 让超出读取预算的链静默离开可复用清单,且不裁剪它的历史:之后任何提示都不再提供它, 对它的
resume被拒绝为「读取太多、恢复不划算」,而对同一件工作的一次普通委派仍然能从冷启动 成功。 - 步骤 11 定义名不区分大小写并接受文档后缀——
Explorer与explorer.md都能恢复该链——而 另一个定义的名称会被判为名称不匹配,并列出确实拥有可复用链的智能体。 - 步骤 12 每条链列出最新的
delegationId、它的目标,以及它读过的文件中的最多八个,超出部分带(+N more)后缀。刚刚结束的链在同一会话里就已经在清单里,不需要重启应用,也不需要新会话; 运行中、不可恢复、超出预算以及已被淘汰的链从不出现。 - 步骤 13 从转录重建链关系,因此那条
completed的链在重启后又被提供,并且像之前一样可以恢复。 - 步骤 14 以「没有可重放的历史记录」失败,且同一个 id 不会出现在错误的可用列表里,也不会出现 在任何后续提示里:该链从可复用清单中移除。
- 全程中,转录把该链显示为最新 Task 卡片下的一段连续多轮对话,没有任何「已恢复」标记;恢复后的 运行计数器从 0 开始,因此它的轮次、工具与用量数字描述的是新的运行,而更早的轮次仍可读地留在 上方。
- 步骤 2 从该链继续:新的运行给出先前结论的确切文件与行号,且它的行里没有对那个文件的重新 整读。
- 链接规格:
03-runtime/02-agent-runtime.md§5f、ADR 0279 - 验收:C(对话)、品质
- 里程碑:M6+
- 不覆盖(二期):唤醒被
stopped/aborted的委派、链内压缩、任务排队,以及跨会话恢复。 - 状态:草稿——链解析、恢复校验、可复用清单与转录链分组已有单元/回归覆盖 (
packages/agent-runtime/src/delegation-chain.test.ts、delegation-history.test.ts、runtime.test.ts、apps/desktop/test/assistant-turns.test.mjs);桌面旅程需要具备条件的环境。必需套件:test:e2e、test:e2e:subagents、test:e2e:transcript。
E2E-SUBAGENT-context-overflow-compacts-before-failing
- 先决条件:一个使用确定性本地传输的 Agent 会话,其模型元数据声明了一个很小的 上下文窗口(例如 16,000 个 token),回复也是脚本化的。这个小窗口由注入的伪提供商 给出,绝不来自真实提供商:在本仓库里,真实提供商与付费 API 不是默认测试环境。 一份用户定义
~/.agents/subagents/reader.md声明Read、Glob与Grep, 工作区里的文件大到只需两三次读取就会越过委托的硬边界。 - 步骤:
- 委派一个必须按顺序读完这些文件的任务简报,记录传输收到的每一次请求及其 估算大小。
- 读取委托越过硬边界之后的那一次请求。
- 让越界发生在仍有待处理工具结果的时刻,再让它发生在一个已完成的回合上, 各重复一次。
- 把摘要请求脚本化为失败,再重复一次。
- 用同一套夹具让会话 Agent 跑同样的任务简报,把它的请求与转录行同本次改动 之前记录的一次运行作对比。
- 在委托结算后检查委派卡片、转录、上下文检查器,以及父级自己的模型上下文。
- 预期:委托继续工作,而不是失败。越界之后的那一次请求低于硬边界,携带摘要 加上适用的保留尾部;没有任何请求超出窗口被发出。仍有待处理工具结果时按活动 回合保留(只留最新的用户消息),已完成的回合不保留。摘要失败会降级为原始任务 简报加最近的若干条消息,该次运行依然完成,报告与生命周期 details 会说明它已被 降级,而不是把一个不完整的答案当作完整答案呈现。委托压缩不添加转录行、不写 host-core 检查点、不弹警告 toast、也不添加上下文检查器条目;委托自己的行保持 完整,父级的模型上下文里依旧只有那份报告。会话 Agent 的行为与改动之前完全一致。
- 链接规格:
03-runtime/02-agent-runtime.md§5.1、§5f、03-runtime/08-error-codes.md§3.2、ADR 0299、ADR 0064、ADR 0136 - 验收:C — 对话和直播;品质
- 里程碑:M6+
- 状态:草稿。必需套件:
test:e2e、test:e2e:subagents。
E2E-SUBAGENT-context-overflow-reports-actionable-failure
- 先决条件:同一个注入的小窗口伪提供商,窗口小到连降级后的上下文也放不下。 一份定义声明两个有序
fallbackModels:一个窗口不比主模型更大,另一个更大。 第二份定义不声明任何备选。 - 步骤:
- 委派一个会一路越过压缩与降级的任务简报,读取父级收到的工具结果以及生命周期 details。
- 对声明了那两个备选定义重复一次,记录传输实际被请求了哪些备选。
- 让每个备选都处在同样的小窗口上,再重复一次。
- 分别在英文与中文下读取委派卡片与报告。
- 继续父级回合,然后发送一条新的提示。
- 预期:该次运行以
SUBAGENT_CONTEXT_OVERFLOW(不可重试)失败,父级读到的内容 点名它可以改变什么 —— 缩小任务范围、改用上下文窗口更大的模型、一次读取更少内容。 提供商原始的溢出语句不是父级收到的东西。自身预算装不下已携带上下文的备选永远不会 被请求,并以该理由出现在modelFailures里;窗口更大的那个备选会被尝试,并且可以 成功。当没有任何备选装得下时,结果仍是SUBAGENT_CONTEXT_OVERFLOW,而不是最后那个 提供商错误。委托的行保持持久且可见,会话回到空闲,下一条提示不会是AGENT_BUSY。 - 链接规格:
03-runtime/02-agent-runtime.md§5f、03-runtime/08-error-codes.md§3.2、ADR 0299、 ADR subagent-model-fallback - 验收:C — 对话和直播;品质
- 里程碑:M6+
- 状态:草稿。必需套件:
test:e2e、test:e2e:subagents、test:e2e:subagent-models。
E2E-SUBAGENT-resume-seeds-within-context-budget
- 先决条件:同一个注入的小窗口伪提供商。一条已结算的
reader链读取的内容 足以超出委托的硬边界,但仍在MAX_RESUMABLE_READ_LINES以内;第二条已结算的 链远远落在预算之内。 - 步骤:
- 对超出预算的那条链执行
Task.resume,完整捕获它的第一次提供商请求。 - 对落在预算之内的那条链执行
Task.resume,捕获同样的请求。 - 向恢复后的运行询问该链在最近一轮得出的结论,再询问它在第一轮得出的结论。
- 重启应用,从转录重建链索引,再次恢复那条超出预算的链。
- 在一条链里累积超过
MAX_RESUMABLE_READ_LINES的只读输出,读取下一条提示给出 的可复用清单。
- 对超出预算的那条链执行
- 预期:恢复后运行的第一次请求低于硬边界。它以原始任务简报开头,并保有最近的 若干轮;最旧的工具结果优先被丢弃,而丢弃一条助手消息会连同它的工具调用一起丢弃, 因此没有孤立的工具调用会到达提供商。落在预算之内的链仍按原样整条播种。最近一轮的 结论能从播种的上下文里答出;第一轮的结论可能已经不在,此时该次运行会照实说明,而 不是凭空编造。一次恢复绝不会在它的第一次请求上以
CONTEXT_TOO_LARGE或SUBAGENT_CONTEXT_OVERFLOW失败。MAX_RESUMABLE_READ_LINES仍然会把读取过多的链 移出可复用清单;裁剪不会让它重新变得可恢复。 - 链接规格:
03-runtime/02-agent-runtime.md§5f、ADR 0299、ADR 0279 - 验收:C — 对话和直播;品质
- 里程碑:M6+
- 状态:草稿。必需套件:
test:e2e、test:e2e:subagents。
E2E-145:工具结果读取为结构化块,从不 JSON
- 先决条件:项目绑定的 Agent 会话,具有允许的权限 转牌圈;安装了结果为任意记录的插件工具;的 工作空间包含一个足够大的文件,足以导致主机截断。
- 步骤:
- 运行一轮读取源文件、glob 目录、greps 令牌, 再次使用
outputMode: filesWithMatches和count进行 grep,编辑 工作区文件,编辑临时根文件,运行失败的 shell 命令, 并调用插件工具。 - 检查每个折叠的活动行,然后展开每一行 黑暗。
- 单击 Glob 路径和 Grep 命中标题。 4、触发高危工具,出现内嵌权限卡。
- 读取截断的文件并复制每个块。
- 运行一轮读取源文件、glob 目录、greps 令牌, 再次使用
- 预期:
- 没有扩展行显示转义的 JSON,并且没有有效负载出现两次。
- Read/Write 显示突出显示的内容; Bash 显示命令、输出和 错误色调的 stderr 作为单独的块,省略了空通道;全球演出 路径列表; Grep 显示按文件分组的匹配结果以及
content中的行号 模式、filesWithMatches中的路径列表以及count中的每个文件总计;的 失败的命令携带exit 1芯片。 - 工作区编辑不显示内联差异(其 ReviewChangeCard 拥有它);的 暂存编辑显示了紧凑的差异和
scratch芯片。 - 插件结果呈现 label/value 字段和标记块,而不是斑点。
- 单击路径在工作面板中将其打开;工作空间根目录之外的路径 不可点击。
- 权限卡的参数预览使用相同的块。
- 主机截断标记保持可见,出现
truncated芯片,主机notice在其限定的块下呈现为中性音符,上限 列表报告隐藏的剩余部分,复制产生完整的有效负载。
- 链接规格:
04-ux/08-component-spec.md§9、§10.2、08-meta/decisions-log.md(D192) - 验收:C(聊天和直播)、E(工具和权限)、质量
- 里程碑:M5
- 状态:单位覆盖(
tool-presentation.test.mjs,transcript-style.test.mjs);完整的 UI 旅程草稿
E2E-146:不产生可见文本的回合重新运行一次
- 先决条件:项目绑定的 Agent 会话使用确定性提供程序 在没有工具调用和没有文本的情况下结束一圈的夹具 - 一次带有推理 内容呈现,曾经什么也没有;第二场比赛结束 第一个回合,然后以同样的方式重新运行。
- 步骤:
- 使用仅推理夹具启动 Agent 回合并观察 运行时恢复时的转录。
- 随后检查脚本、会话状态和终端诊断。
- 对无任何装置重复上述步骤。
- 对两次静音的夹具重复此操作并检查终端错误消息, 其详细信息披露及其操作按钮。
- 单击错误的重试操作。
- 重新加载会话并验证哪些内容保持持久。
- 预期:
- 恢复的回合保留相同的可见助手消息 ID,发出一个 终端生命周期,并且没有显示错误。用户只能看到答案。
- 在重新运行之前,空助手会从模型上下文中删除,因此 提供商从来不会连续收到两条辅助消息,而且也永远不会 附加到持久的成绩单中。
- 终端诊断标识空响应恢复和重新运行的结果。
- 第二次静音发出一个终端可重试
EMPTY_MODEL_RESPONSE辅助错误和生命周期事件;该消息命名了两次尝试,并且 重试操作会重新发送最后一个提示。 - 由于请求工具而文本为空的回合未受影响,因此 是一个中止或已经失败的回合。
- 每个提示仅发生一次重新运行,包括在上下文溢出之后 在同一提示内恢复。
- 链接规格:
03-runtime/02-agent-runtime.md§5e、§7、03-runtime/08-error-codes.md§3.2、08-meta/decisions-log.md(D193) - 接受:C(聊天和直播)、F(持久性)、H(诊断)、质量
- 里程碑:M5
- 状态:单位覆盖(
runtime.test.ts);完整的 provider/UI 旅程草稿
E2E-147:范围搜索保持在预算范围内,特工讲述
- 先决条件:项目绑定的 Agent 会话;工作区包含一个 多兆字节源文件,一个带有
.map同级的缩小包(一行, 兆字节长)、二进制文件和.gitignore排除的依赖树。 - 步骤:
- 检查
tools.list中的Read、Glob和Grep。 2.读取多兆文件,然后从上报的next处再次读取 抵消。 - Grep 命中缩小包及其
.map的令牌。 - 使用
path查找相同的标记,指向被忽略的依赖树, 然后用include缩小到一个扩展,然后用outputMode: filesWithMatches和count。 - Glob 一个广泛的模式,并使用
path和limit进行 Glob。 - 读取二进制文件。
- 运行一个 shell 命令,在标准输出上打印远远超过 shell 预算的内容,然后 在 stderr 上打印进度噪音并打开溢出后失败的一个 每个标记中命名的文件。 8.提出一个需要多个工具批次的问题,并观看成绩单 批次之间。
- 检查
- 预期:
- 每个描述都带有其参数和实际限制数。
- 没有任何一个工具结果超出其预算:Read/Glob/Grep 为 128 KB,96 KB 对于 Bash。读取报告
offset、lineCount、fileBytes和下一个偏移量notice;第二次读取继续,没有重叠;totalLines从第一次读取就始终 报告。填满的默认或请求窗口即使文件更长也报告truncated: false。 - 任何文件大小都不会被拒绝。来自捆绑包和
.map的行到达 剪辑为 16,384 个字符,并且剪辑计数出现在notice中,因此一行 无法消耗结果。 - 显式
path到达被忽略的树;没有它同样的搜索 从那里没有返回任何内容。include、outputMode和headLimit各一个 缩小有效负载,结果按最新修改的顺序排列。PATH 上有rg时 Grep 使用它且契约不变;缺失或 exit 2 时回退进程内实现(D315)。
- 二进制读取失败并显示
TOOL_BINARY_CONTENT,并且没有二进制文件到达 模型; Grep 默默地跳过它。- Bash stdout 保留其头部,stderr 保留其尾部,两个标记均命名为哪个 结束幸存和溢出路径,并且每个溢出文件以更完整的方式打开 输出。
- 代理以用户编写的语言回答,位于每个工具之前 批处理用一个关于它正在做什么的句子,永远不会留下多个 批处理没有新的可见文本,并以独立的结果结束。
- 链接规格:
03-runtime/16-tool-result-limits.md,03-runtime/02-agent-runtime.md§7、08-meta/decisions-log.md(D194、D306、D315) - 验收:C(聊天和直播)、E(工具和权限)、质量
- 里程碑:M5
- 状态:单位覆盖(host-core
tools测试,runtime.test.ts提示 断言);完整的 provider/UI 旅程草稿
E2E-100:粘贴的 MCP 服务器仅在其作用域内运行
- 先决条件:磁盘上有两个项目,
~/work/api和~/personal/site。 PATH 上可用的本地 stdio MCP 服务器。每个项目一个 Agent 会话。 - 步骤:
- 扩展 → MCP → 从 JSON 导入。粘贴
mcpServers文档持有 三台服务器:一个有效的 stdio 条目,一个指向可信局域网地址(例如http://192.168.1.20:8080/mcp)且没有type的远程 HTTP 条目,以及一个 没有command的 stdio 条目。 2.确认导入,然后打开导入的stdio服务器并按Test 连接。 - 将服务器留在Everywhere,并要求每个项目中的代理 列出其可用的工具。
- 将服务器设置为这些项目,仅选择
~/work/api。 - 在每个项目中再次询问。
- 在已经打开的
~/personal/site会话中 — 在服务器时组装 是全局的——要求代理按名称调用服务器工具之一。 7、编辑服务器的env并保存;再次在~/work/api中询问。 - 重命名服务器并重新调整范围;再问一次。
- 将服务器的命令指向不存在的二进制文件,保存并打开 一个新的会话。
- 恢复有效命令并测试连接。通过
ToolSearch激活一个工具,然后在两次 调用之间终止 stub 服务器进程。在同一会话中再次调用该工具,不再搜索。 - 用重启后不再提供该工具的 stub 重复;以及在恢复前禁用或改出作用域。 也试一次断开后的并发调用,以及恢复握手失败的服务器。
- 扩展 → MCP → 从 JSON 导入。粘贴
- 预期:
- 传输重启后,已激活的工具无需再次搜索即可使用;并发调用共享一次握手。 新的服务器列表仍须公布该工具。已移除的工具和未激活的服务器在不执行 调用的情况下被拒绝;未激活的服务器不会被重新连接。恢复失败返回
UNAVAILABLE,在编辑或测试连接之前不会反复握手。失败的工具执行 从不重放。 - 导入两台服务器;第三个被列为跳过“stdio 服务器 需要指挥”。局域网 HTTP 条目以
http身份登陆,其 url 完好无损,编辑器 显示未加密连接警告。 - 与其找到的工具名称以及行的字形相关的测试报告 从连接状态变为就绪状态。
- 虽然是全局的,但两个会话都会看到
mcp_<serverId>_<tool>名称。 - 缩小范围后,只有
~/work/api会话才能看到它们;总结芯片 读取“1 个项目”并为其命名。 - 第 6 步中的过时调用失败,并显示
TOOL_NOT_FOUND和“not active for 本次会议”——范围在调度时有效,而不仅仅是在目录中。 env编辑会断开连接:下一次组装或调用会重新握手,并且 该工具的行为反映了新的值。第 8 步中的重命名不会 重新连接任何东西。- 损坏的命令记录
failed并带有一条消息,不提供任何工具, 并且不会在下一次会议上重拨;压制测试 重试。
- 传输重启后,已激活的工具无需再次搜索即可使用;并发调用共享一次握手。 新的服务器列表仍须公布该工具。已移除的工具和未激活的服务器在不执行 调用的情况下被拒绝;未激活的服务器不会被重新连接。恢复失败返回
- 链接规格:
07-plugins/01-plugin-system.md§12,03-runtime/01-ipc-protocol.md§12a、08-meta/decisions-log.md(D192、D193) - 验收:E(工具和权限)、质量
- 里程碑:M5
- 状态:单位覆盖(
apps/desktop/test/user-mcp.test.mjs,packages/shared/src/mcp-import.test.ts、host-coremcp_servers测试);满 UI 之旅草案
E2E-100B:远程 HTTP MCP 服务器 OAuth 2.1 授权与令牌生命周期
- 先决条件:配置了需要 OAuth 2.1 身份验证(RFC 9728 发现与 PKCE S256)的 HTTP MCP 服务器端点。
- 步骤:
- 打开设置 > Agent > MCP。添加 HTTP MCP 服务地址。
- 服务器状态显示“需要授权”。
- 点击“授权”。主进程在
127.0.0.1启动回环监听,打开外部浏览器跳转至附带 RFC 8707resource的授权端点。 - 在浏览器完成登录,回调跳转至
http://127.0.0.1:<port>/callback。 - 回环服务校验 state 与 code,通过 PKCE verifier 完成令牌交换,将令牌写入加密密钥库
secret:mcp:<id>:oauth,渲染转义后的成功页面并触发done事件。 - 设置界面状态更新为已连接及工具数量,弹出成功提示,并显示 OAuth 徽标。
- 访问令牌过期时,
UserMcpRuntime透明使用 refresh token 换取新令牌,无需用户重新交互。 - 通过
mcp.transfer迁移服务器时,自动将 OAuth 令牌迁移至新 ID 下。
- 链接规格:
03-runtime/01-ipc-protocol.md、ADR 0283、ADR 0142 - 验收:E(工具和权限)、安全性
- 里程碑:M5
- 状态:单元覆盖(
apps/desktop/test/mcp-oauth.test.mjs、apps/desktop/test/user-mcp.test.mjs);完整 UI 之旅草案
E2E-101:用户技能编写一次并限定每个项目的范围
- 先决条件:磁盘上有两个项目。每个中都有一个 Agent 会话。
- 步骤: 1.扩展→技能→新增。使用空描述保存。 2. 填写描述,编写正文,然后保存。 3. 要求每个项目中的代理按名称使用该技能。 4. 将技能设置为这些项目,仅选择第一个项目, 然后将其关闭并返回到这些项目。 5. 在每个项目中再次询问。 6. 在第一个项目的会话中,将技能范围缩小到第二个项目 并立即要求代理调用它。 7. 粘贴超过 128 KB 的正文。 8. 将应用程序语言切换为中文并重新访问上面的每个表面。
- 预期:
- 没有描述的保存将被拒绝,并显示一条消息命名该字段: 描述是进入提示的唯一部分。
- 基本提示包含技能的 ID、名称和精简的描述以及 不是它的身体;主体仅通过
Skill工具到达。- 切换“关闭”和“返回”可恢复所选项目,而无需重新选择它。
- 缩小范围后,只有范围内的项目的会话可以调用它;另一个 得到“未启用此项目”。
- 步骤 6 在已经打开的会话中也失败了——当 正文已加载,但不受列出它的目录的信任。
- 字节计数器在 128 KB 上限之前发出警告,超过该上限则拒绝保存。
- 每个标签、空状态、错误和计数均以中文呈现,并带有计数 自然地阅读0、1和许多。
- 链接规格:
07-plugins/01-plugin-system.md§12.3,03-runtime/01-ipc-protocol.md§12b、08-meta/decisions-log.md(D174、D192、 D194) - 验收:E(工具和权限)、质量
- 里程碑:M5
- 状态:单位覆盖(host-core
user_skills测试,apps/desktop/test/extensions-page.test.mjs);完整的 UI 旅程草稿
E2E-102:Composer 文件和图像粘贴成为会话草稿参考
- 先决条件:应用程序正在项目中使用 Agent 会话运行,并且 可用的家庭输入框。操作系统剪贴板包含不超过大段粘贴阈值的文本片段、一个或 更多本地文件(包括带空格的文件名)和图像,分别进行粘贴尝试。记录应用程序数据目录和会话 ID。
- 步骤:
- 粘贴阈值内的纯文本,确认可编辑。复制同时带正文和生成图片副本的 Word 选区,确认正文优先;覆盖多行/CRLF、空行与末尾换行、字面量
<>&及引号、 跨换行/附件芯片替换选区、光标与原生撤销/重做。超过阈值时应生成 TXT 芯片而非图片。 粘贴带文件名文字的真实图片文件,确认仍为图片附件。 - 粘贴一个本地文件,然后粘贴包含空格名称的多个文件。
- 粘贴来自操作系统 screenshot/clipboard 提供程序的图像。 4.发送前检查草稿:确认每个物化项目都是 可移动的叶名芯片和无划痕的绝对路径占用 文本区域。 Hover/focus 芯片检查其完整路径,删除一个,然后 发送提示并检查会话消息。 发送前确认缩略图位于输入框外上方并靠左排列。全选替换与撤销正文应保留图片; 仅图片草稿切换会话后仍保留图片与发送元数据。挂载完整 Composer,验证 仅图片时点击发送会交付附件并清空草稿,文字加图片的发送立即失败时全部恢复。 窄面板添加 20 张图片后应能滚动到最后一张并删除,其他图片保留。 用点击、Enter 和空格打开居中图片浮层,工作面板与草稿保持 不变。小图不放大,宽图等比适应窗口;验证缩放、重置、下载原图、多图按钮与方向键 切换。切图重置缩放与位置。在适应窗口和放大后拖动图片,验证移出图片后 松手、取消拖动与滚动平移;适应窗口恢复居中,拖动不关闭浮层,关闭或切换会话 后不残留指针捕获。Esc、关闭按钮和空白处均可关闭,焦点与光标回到原处, 浮层打开时焦点不离开浮层,原生插件视图隐藏。在无项目会话重复。 删除不触发预览或发送。延迟读取期间切换会话/项目或移除当前附件,应关闭预览; 晚到结果不覆盖较新的图片。丢失或解码失败的图片显示重试并保留草稿。 文本芯片仍能展开为可编辑正文。
- 检查
<data_dir>/scratch/<sessionId>/pasted/并比较保存的 源 files/image 的字节。检查项目git status。 6.删除会话,然后确认其临时目录和粘贴的文件 被删除。
- 粘贴阈值内的纯文本,确认可编辑。复制同时带正文和生成图片副本的 Word 选区,确认正文优先;覆盖多行/CRLF、空行与末尾换行、字面量
- 预期:
- 不超过配置阈值的纯文本粘贴保持原生状态,不会通过文件桥进行路由。超过阈值的文本行为由 E2E-102g 覆盖。
- 每个 file/image 都使用经过清理、UUID 支持的唯一名称保存在 会话暂存根,而其芯片仅显示已清理的原始根 叶名。重复的叶名称仍然是单独的引用。
- 分派的提示和持久的用户消息包含剩余的 带空格引用的完整路径,而不是标签或二进制字节,并且 代理可以使用其普通文件工具来读取物化文件。
- 家庭粘贴在写入之前创建或重用持久会话。的 工作区保持干净,并且不会创建任何工作区工件行。
- 图片预览保留草稿及附件元数据,通过
fs/readImageDataUrl限制读取大小与路径; 无项目时也可读取允许访问的暂存图片。预览保持草稿附件格式及工作面板选项卡不变。关闭或卸载时 释放浮层与原生视图阻挡状态。 - 删除会话会删除粘贴的文件以及其余的内容。
- 链接规格:
04-ux/08-component-spec.md§11.7–11.8,03-runtime/01-ipc-protocol.md§13c,03-runtime/03-tools-and-permissions.md§4b,03-runtime/04-data-storage.md、08-meta/decisions-log.md(D197、D209)、 ADR 0059、ADR 0070 - 接受:C(对话和流),E(工具和权限), F(坚持),品质
- 里程碑:M5
- 状态:单元覆盖(
composer-paste-files.test.mjs、composer-clipboard.test.mjs);pnpm test:e2e:composer-paste挂载真实 ComposerInput、草稿/粘贴 hook、文件查看器、 生产 CSS 和沙盒 preload,以 Chromium ClipboardEvent 注入混合数据与原生 File, 调用真实暂存文件写入器和受限文件读取器,核对字节并解码预览图片。 需先构建桌面、安装 Electron,并有图形会话 (Linux 可用 Xvfb)。测试不修改系统剪贴板,也不操作 Word;Word/各平台旅程 及完整提供商发送仍需人工验证。应在更新后的任务候选版本运行并记录修订;随后 按权威AGENTS.md工作流验证 PR 集成候选,无需先合入本地 main。
E2E-102a:Composer 文件引用结果使用紧凑的叶名称
- 先决条件:应用程序正在工作区中使用 Agent 会话运行 包含嵌套文件、不同目录中的重复叶名称,以及 名称包含空格的目录。
- 步骤: 1) 键入
@并过滤出嵌套和重复的条目。 2) 检查可见行,然后将鼠标悬停在完整路径工具提示上并检查其 可访问的名称。 3) 用 Enter 接受文件,确认叶名芯片留在草稿光标处, 同时@标记和完整路径保持隐藏。再用 Tab 或点击接受第二个文件。 接受目录结果并继续子文件。 4) 发送完整的引用并检查持久化的用户 消息。 - 预期:
- 每个结果持久地仅呈现其叶名称;目录保留一个 尾随
/,并且没有父路径消耗水平行空间。 - 工具提示和可访问名称保留完整的相对路径,以便 重复的叶子名称仍然可以区分。
- 对文件按 Enter/Tab/点击会把
@标记替换为留在草稿中的行内芯片; 该按键不会发送。文件接受保留芯片后面原始完整的entry.path; 目录接受保留文字路径延续。发货时 发送并保留的提示包含现有的每个完整路径 空格引用,代理可以正常读取两个选定的文件。
- 每个结果持久地仅呈现其叶名称;目录保留一个 尾随
- 链接规格:
04-ux/08-component-spec.md§11.8,04-ux/09-interaction-patterns.md§8a、03-runtime/01-ipc-protocol.md§13c、08-meta/decisions-log.md(D124、D209、D362)、ADR 0024、ADR 0070 - 接受:C(对话和直播),质量
- 里程碑:M5
- 状态:单位覆盖 (
apps/desktop/test/composer-file-reference-display.test.mjs);完整的用户界面 旅程草稿(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-102b:未答复的停止恢复紧凑的文件参考草稿
- 先决条件:Agent 会话可以延迟其第一个辅助事件。的 草稿包含普通文本、一个工作区参考、两个粘贴参考 具有重复的叶子名称和包含空格的规范路径。
- 步骤:1)发送混合草稿并在助理发短信之前停下来,思考, 或任何工具行开始。 2) 检查修复后的输入框和抄本。 3) 再次发送恢复的草稿并检查保留的用户消息。 4) 重复,允许部分辅助输出开始,然后停止。
- 预期:
- Unanswered Stop 删除刚刚发送的用户行并恢复原始 普通文本加上稳定顺序的叶子名称芯片。
- 相对路径和临时绝对路径永远不会出现在恢复的文本区域中; 重复的标签仍然是不同的参考。
- 重新发送将每个精确的规范路径与现有的路径序列化一次 空白引用。
- 回复开始后停止保留部分中止的记录并执行 不恢复或复制文本或碎片。
- 临时字节保留在现有会话生命周期内。
- 链接规格:
04-ux/08-component-spec.md§11.5/§11.8,04-ux/09-interaction-patterns.md§3.2/§8a.2,03-runtime/10-session-state-machine.md、08-meta/decisions-log.md(D209)、 ADR 0070 - 接受:C(对话和直播)、F(持久)、质量
- 里程碑:M5
- 状态:单位覆盖 (
composer-file-reference-display.test.mjs,transcript-style.test.mjs); 完整的 UI 旅程草案(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-102g:大段文本粘贴成为内联会话临时文件引用
- 先决条件:应用运行在项目 Agent 会话中,并提供主页输入框。先使用默认阈值,再将大段粘贴阈值改为确定的较小测试值。记录应用数据目录、会话 ID 以及包含多行 Unicode 文本的固定输入。
- 步骤:
- 打开设置 → AI → 默认项,确认大段文本粘贴阈值为 600;将其改为测试值并保存,然后返回输入框。
- 粘贴刚好达到阈值的文本,确认仍使用原生文本区域;在草稿开头、中间和末尾粘贴多一个字符的文本,包括多行和 Unicode 内容。
- 检查每次超阈值粘贴:确认前后文本保持不变,原始选择处出现生成的
pasted-text-*.txt芯片,编辑器没有临时文件绝对路径,传输期间显示正确的忙碌/错误状态。 - 点击生成的 TXT 芯片,再用键盘聚焦并按 Enter、Space 重复测试。确认准确的 UTF-8 内容在原位置替换芯片,文本可以继续编辑,插入符号位于内容末尾,随后发送使用修改后的文本。读取等待期间切换草稿或删除芯片,确认过期响应不会修改当前草稿。
- 检查会话
scratch/<sessionId>/pasted/文件字节,发送仍含芯片的混合草稿并检查渲染器请求、持久化用户消息和代理可读取的路径。发送前切换项目和会话以检查缓存草稿,再删除芯片并确认它不再发送。 - 删除所属会话,确认临时粘贴文件被删除。
- 预期:
- 阈值持久化在 AI 默认项中;旧设置默认使用 600,且只接受 1 至 1,000,000 的整数。
- 不超过阈值的文本仍走原生路径;超过阈值的文本以 UTF-8
text/plain原样保存到所属会话的临时pasted/目录,不修改项目或创建工件。 - 由哨兵支持的 TXT 芯片插入在准确的粘贴位置,包括多行草稿中间。点击或按 Enter/Space 会将其展开为准确的可编辑文本并移除引用;之后的修改就是发送内容。读取失败、二进制、图像或过大时保留芯片和映射。
- 仍存在的芯片会在原位置只解析一次为规范路径,不追加 basename,也不作为重复附件发送。删除芯片后会移除映射。
- 会话切换、项目切换、未答复 Stop 恢复以及会话删除遵循现有会话归属和清理规则。
- 链接规格:
04-ux/06-settings-ia.md、04-ux/07-ui-design-system.md§8.1、04-ux/08-component-spec.md§11.7–11.8、04-ux/09-interaction-patterns.md§8a、03-runtime/01-ipc-protocol.md§8、03-runtime/04-data-storage.md§7、08-meta/decisions-log.md(D262)、ADR 0059、ADR 0070、ADR 0131 - 接受:C(对话和流)、E(工具和权限)、F(持久)、质量
- 里程碑:M5
- 状态:单位覆盖(
composer-trigger.test.ts、apps/desktop/test/composer-paste-files.test.mjs);完整 UI 旅程草稿(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-103:设置 > 智能体管理文件能力
- 先决条件:应用注册了项目 A、B,并且两者都有 Agent 会话。固定装置只使用
~/.agents/skills、~/.agents/servers、~/.agents/subagents和两个项目的.agents目录;不存在.pi能力目录。 - 步骤:
- 打开设置 > 智能体,确认技能、MCP、子代理是三个独立导航页面。打开扩展页, 确认只存在“已安装”和“市场”两个选项卡。
- 打开技能页,确认一条工具栏位于单块面板之上,面板中有一个以
~/.agents/skills为根的全局分组标题和一个以项目 A 的.agents/skills为根的项目分组标题,两者 在同一列内按自然页面高度排布,并且项目选择器能切换项目。确认首次绘制出现骨架 行;后续刷新时确认已在屏幕上的行保留,列表改为调暗。 - 试用级别筛选和搜索框。确认“全部 / 全局 / 项目”的计数与渲染出的行一致,选中某个 级别会隐藏另一个分组但不会隐藏工具栏及其操作,搜索会同时收窄两个分组及其计数, 清空搜索能恢复全部行,且无匹配的搜索会明确提示并建议放宽筛选。
- 在选中项目 A 时关闭一个全局技能。确认开关立即翻转且列表没有退回骨架屏、行降低 透明度、toast 指出技能名称、请求进行中其他行仍可操作,且全局文档没有改变。切换 到项目 B,确认全局技能在那里仍启用。强制让 host 拒绝一次,确认开关回到原位。
- 从页面新建一个技能。筛选在“全局”时确认主操作写出全局目标位置且新文档落在
~/.agents/skills;筛选在“项目”时确认它写出项目名称且落在项目 A。选中“项目”但 没有选定项目时,确认操作会给出提示而不是静默失败。编辑刚建的技能并保存,确认正文 可以往返。 - 在一个项目级技能的溢出菜单中选择“在文件管理器中显示”,确认打开的是项目文件而不是 同 id 的全局文件。选择“删除”,确认第一次按下只解除保险并改变文案,第二次按下才 真正删除,关闭菜单会重新上保险。
- 在项目 A 放入同名项目技能并将其关闭。确认有效运行时目录不会回退到全局技能; 先遮蔽项目记录,再过滤关闭状态。
- 使用“导入”选择一个 Markdown 文件。确认文件物理复制到筛选器当前指向的级别, 立即出现在列表中,并且同一次选择不能选择第二个文件。
- 打开 MCP,新增项目服务器和全局服务器,编辑项目服务器,并从行的溢出菜单测试已有 连接。确认编辑时 ID 锁定,同级 ID 或 label 重复被拦截,连接成功/失败同时反映在行 徽标和 toast 上。用同样的两次按下菜单项删除一个服务器,确认它的文件已消失。
- 在应用外删除技能或 MCP 文件,重新加载页面,确认行消失且本地状态没有孤儿项; 删除全局文件还会删除它的项目覆盖。
- 打开子代理,确认它是以
~/.agents/subagents为根的单块全局面板,没有级别筛选、 没有项目选择器、也没有项目级控制。确认即使用户目录为空,内置分组仍列出五个默认 子智能体(explorer、code-reviewer、test-runner、fixer、ui-designer), 带「内置」徽标、工具授权和启用开关,但没有在文件夹中显示或删除。确认全局分组标题 带有全局级别标签和数量,新建 / 编辑 / 删除 / 在文件管理器中显示都能对用户自建行 在页面内完成,且空的用户目录仍通过settings.subagentsEmpty在全局分组下解析为本地化空态文案,不显示原始翻译键。 打开“新建子智能体”,确认模型字段是与 Composer 相同的已配置、 可运行模型下拉列表(按提供商分组,带沿用会话选项),而不是手打provider/model的输入框。固定一个已配置模型并保存,确认文档的model:frontmatter 为vendorKey-or-name/modelId。当两个已配置提供商共享通用或相同的厂商 标识时,确认它们仍是独立分组,并使用唯一的显示名(显示名也重复时使用提供商 ID)。 编辑一个固定值已不在配置中的定义, 确认该固定值仍被选中,而不是跳回沿用会话。 - 把窗口收窄到工具栏的堆叠断点。确认分段控件占满宽度、搜索移到它下方、操作按钮 靠左换行、分组标题隐去解析后的路径,且页面不产生横向溢出。在无法悬停的指针下, 确认行的编辑和溢出控件无需悬停即可见。
- 预期:
- 三个设置页面不使用 tabs 切换能力,空态和有数据状态都按自然页面高度排布,支持明暗 主题,并在说明文字中体现项目优先于全局。每个页面都是一条工具栏加一块面板:工具栏 承载带实时计数的级别筛选、一个带清除操作的搜索框、项目选择器和主操作按钮;面板用 分组标题分隔级别,标题写明级别、解析后的
.agents路径和本地化数量。行使用安静的 能力图标、一个级别徽标加本地化的来源/传输徽标、描述文字和常驻启用开关;编辑和溢出 菜单在行未被悬停、聚焦或菜单未打开时保持安静,在无法悬停的环境中始终可见。骨架行 只在首次绘制时出现,后续刷新把已在屏幕上的行调暗并播报刷新,忙碌状态只作用于发出 请求的那一行,数量对辅助技术可见。空态在面板内居中、不加装饰边框,并提供页面的主 操作,且不引入能力专属的配色体系。 - 三种能力都能在设置内完成新建、编辑和删除。新能力落在筛选器当前指向的级别上,破坏性 操作需要按下同一个改过文案的菜单项两次,显示项目级技能时打开的是该项目的文件,而不是 同 id 的全局文件。子智能体编辑器的模型字段是已配置可运行模型的分组下拉(含沿用会话), 不是手打 id;保存写入
vendorKey-or-name/modelId(别名冲突时使用唯一的提供商名称或 ID), 已不在配置中的固定值仍保持选中。 - 能力文件只包含配置/frontmatter;启用状态存于应用本地
agent-capabilities文件。 - 项目项即使关闭也会按 ID 或名称遮蔽全局项,下一次运行时激活与界面一致。
- 物理导入只复制一个文件且遵循级别,磁盘删除由扫描移除,不显示待清理行。
- 三个设置页面不使用 tabs 切换能力,空态和有数据状态都按自然页面高度排布,支持明暗 主题,并在说明文字中体现项目优先于全局。每个页面都是一条工具栏加一块面板:工具栏 承载带实时计数的级别筛选、一个带清除操作的搜索框、项目选择器和主操作按钮;面板用 分组标题分隔级别,标题写明级别、解析后的
- 关联规范:
03-runtime/01-ipc-protocol.md§12a–§12d、03-runtime/02-agent-runtime.md§5f、03-runtime/13-model-catalog-and-selection.md§2(子智能体编辑器)、04-ux/01-ui-ia.md§3.5–§3.6、04-ux/06-settings-ia.md§2(智能体能力页面)、§4.21–§4.25、07-plugins/01-plugin-system.md§12.2–§12.3、08-meta/decisions-log.md(D193、D194、D202、D257)、ADR 0112、ADR 0126 - 验收:D(工作区)、E(工具和权限)、F(持久性)、质量
- 里程碑:M6+
- 状态:由
apps/desktop/test/agent-capability-settings.test.mjs、apps/desktop/test/extensions-page.test.mjs、apps/desktop/test/subagent-models.test.mjs和 host-core 能力测试覆盖源码/单元; 原生选择器、渲染模态框、项目切换和运行时完整旅程仍为 Draft(除非明确要求,不在本地运行 E2E)
E2E-120:全局插件启动、下一回合编辑和停止吞吐量
- 前提:安装并启用中文显示的面板插件 名称是
无限画布。配置一个流传输速度足够慢以停止的提供程序 部分答案。在 macOS 上运行一次,在 Windows 上运行一次。 - 步骤:
- 在 PI-Desktop 未聚焦的情况下,在 macOS 上按 Option+Space 或在 Windows,而另一个应用程序拥有前台窗口。确认一个 居中的启动器出现在指针的显示屏上,没有本机关闭, 最小化、最大化、调整大小或任务栏控件,并且 Windows 不显示 活动应用程序的系统菜单。
- 分别搜索
无限、wuxianhuabu和wxhb。使用 Up/Down 和 输入一次运行,然后单击进行另一次运行;确认现有插件面板 打开。确认中文输入法候选输入不打开结果。 - 开始 Agent 回答。当它流式传输时,输入下一个草稿并进行更改 思考,权限模式,以及Agent/Plan/Goal。确认每个选择都是 可编辑;有内容的草稿显示发送并排队下一条提示,清空草稿后切换为停止。
- 部分输出后停止。确认部分答案仍然存在,排队的 配置只有在终止后才变得持久,并且下一回合 使用最终选择而不是任何中间选择。
- 检查停止的答案的对话统计信息,重新加载会话, 并再次检查。
- 预期:
- 启动器搜索仅返回启用的、准备好的面板插件和每个 调用以空的、集中的查询开始。逃避和失焦隐藏 它无需关闭主应用程序。
- 没有运行回合观察到分阶段的 mode/model/thinking/permission 变化, 并且不能同时发送第二个提示。
- 流式传输期间更改思考档位时,所选模型已启用的等级保持可见; 子菜单不会塌缩成只剩关闭思考,包括继承应用默认模型的未固定会话。
- 重新加载之前和之后都存在吞吐量停止的情况。它使用精确的输出 提供商提供时的使用情况;否则 UI 会标记持久化 四码点估计为近似值。
- 链接规格:
03-runtime/01-ipc-protocol.md,03-runtime/02-agent-runtime.md§5b/§9,04-ux/07-ui-design-system.md§8.2–8.3,04-ux/08-component-spec.md§11,04-ux/09-interaction-patterns.md§1/§3、D211、D212、ADR 0072、ADR 0073 - 接受:C(对话和流)、F(持久)、G(插件)、 品质
- 里程碑:M6
- 状态:Unit/source-contract 已覆盖;完整的跨平台 UI 之旅草案 (适用变更合入前需在具备条件的环境中运行 E2E)
E2E-121:Goal 批准恢复自主验收标准执行
- 先决条件:项目绑定的会话具有已配置的提供程序并且是 在 Agent 模式下空闲;工作区允许创建主机工件并且没有 先前的测试目标工件。
- 步骤: 1) 将会话切换到 Goal 并让 Agent 调用
EnterGoalMode,然后是SubmitGoal(title, markdown, question)。 2) 检查 新.pi/goal/*.md工件中的确切 Markdown 字节以及匹配的plan_approvals行。 3) 确认共享审批卡仅公开 Approve/Reject 和 Goal 否认 Write/Edit/plugin 工具,而 Bash 紧随其后 所选的权限模式。 4) 通过询问进行批准并遵守相同的 Agent 在 Agent 模式下恢复。 5)逐个标准检查最终响应 验证或显式边界,然后重新加载会话。 - 预期:Goal 使用 Plan 审批管道,无需第二个规划者; 该工件是不可变的,行记录
kind = goal、路径、哈希、 大小和执行状态。批准是一个单独的用户决定,转换 会话到Agent,并在批准后才开始自主工作。的 重新加载后,成绩单仍可审阅,并且没有 scheduled/unattended Goal run 可以绕过审批边界。 - 链接规格:
03-runtime/02-agent-runtime.md,03-runtime/03-tools-and-permissions.md、03-runtime/04-data-storage.md、03-runtime/06-host-rpc-protocol.md、03-runtime/10-session-state-machine.md、04-ux/01-ui-ia.md、04-ux/08-component-spec.md、D198 - 接受:C(对话和流),E(工具和权限), F(持久性)、H(诊断)、安全性
- 里程碑:M6+
- 状态:Unit/source-contract 已覆盖(
packages/agent-runtime和 host-core Goal 测试);完整的 UI 旅程草案(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-122:插件请求并传递本机通知
- 先决条件:已安装的插件声明并被授予
notify;的 桌面平台支持Electron原生通知;操作系统通知 许可处于初始或先前被拒绝的状态。 - 步骤:1)从插件进程中,调用
getNotificationPermission()。 2) 调用requestNotificationPermission()并 观察本机 permission/probe 结果。 3) 打电话showNativeNotification({ title, body })。 4) 对缺少的插件重复此操作notify,并且在不支持本机通知的平台上。 - 预期:第一个状态为
unknown、denied或unsupported;请求 返回尽力而为的granted、denied或unsupported结果;理所当然的 插件接收{ shown: true, permission: "granted" }以进行本机交付, 而 denied/unsupported 传递返回shown: false且不会导致 插件。缺少notify将失败并显示PERMISSION_DENIED。点击已交付的原生插件 通知会恢复并聚焦主窗口,但原生插件通知不会添加持久任务收件箱行或激活聊天会话。 - 链接规格:
07-plugins/01-plugin-system.md,07-plugins/03-plugin-api.md、07-plugins/13-plugin-permissions-matrix.md、 ADR 0074 - 接受:E(工具和权限)、G(插件)、安全性、质量
- 里程碑:M6+
- 状态:Unit/source-contract 已覆盖;完整的跨平台操作系统权限 旅程草稿(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-148:插件设置与插件域快捷键可编辑
- 先决条件:一个已启用的插件声明了字符串、布尔值、JSON 和
shortcut设置;该快捷键指向插件声明的命令。 - 步骤:1)打开“插件”并进入该插件的设置界面。2)修改普通字段和快捷键并保存。 3)刷新插件列表,在应用窗口聚焦时按下新快捷键。4)尝试使用应用保留快捷键, 然后将插件切换到当前项目之外的激活范围后重复操作。
- 预期:控件由清单生成,值持久化到插件私有设置文件,并传递
plugin:settingsChanged。新快捷键仅在聚焦的应用窗口且插件处于匹配激活范围时调用 插件命令。保留或冲突的快捷键会被拒绝;不会注册操作系统全局快捷键。 - 链接规格:
07-plugins/02-plugin-manifest-schema.md、07-plugins/03-plugin-api.md、07-plugins/11-plugin-storage-isolation.md、 ADR 0159 - 接受:F(持久性)、G(插件)、安全性、品质
- 里程碑:M6+
- 状态:Source-contract 与定向集成覆盖;完整桌面旅程草稿(除非明确要求,否则不要 在本地运行 E2E)
E2E-OAUTH-anthropic-rate-limit-retry:有限 token 重试保留账户状态
- 先决条件:本地 HTTP 测试端点只替换 Anthropic token URL;实际 pi-ai 流程及 Desktop
VendorOAuth使用内存 Host RPC 测试实现。不使用真实账户、 浏览器授权或远程端点。 - 步骤:运行
pnpm test:e2e:oauth-retry。覆盖交换/刷新 429 后成功、连续 429、秒数/日期/无效/超预算Retry-After、授权拒绝(含 429 后拒绝)、5xx、 连接中断、成功响应 JSON 无效、请求/响应体/等待期间取消,以及更早的调用方 截止时间。同时解析同一账户,刷新失败后再尝试。 - 预期:最多三次请求共用截止时间和原 signal,不早于服务器提示重试。 仅明确的 429 可重试;授权拒绝或结果不确定的失败立即结束。授权请求字段及 自定义头保持一致。刷新失败保留旧凭据并释放锁;并发解析仅旋转和写入一次。 登录失败/取消只删除此次新建行。HTTP/token JSON 错误不含 token 响应 canary、 URL 或嵌套堆栈,给出恢复指引,不声称所有 429 都消耗授权码。既有网络错误 诊断保持不变。
- 链接规格:
03-runtime/11-provider-model-system.md§8a;ADR 0095。 - 验收:B(厂商账户)、Security、Quality。
- 状态:安装补丁后的 20 个本地 HTTP 场景全部通过,已有 20 个登录/会话 回归也通过。这是合并前传输及编排集成测试,不验证真实 OAuth、视觉 UI 或 Host 持久化。主线合并后 E2E 尚未运行。
E2E-151:厂商账户登录、跑通一轮对话并退出登录
- 先决条件:构建在启动时调用了
registerBunOAuthFlows();至少准备一个 PKCE 厂商(Anthropic)与一个设备码厂商(xAI 或 GitHub Copilot)的真实订阅账户;两个 厂商都还没有对应的提供商行。 - 步骤:1)打开设置 -> 模型配置,确认厂商账户卡片一开始为空,点击“添加账户”, 选择器列出的正好是
models.getProviders().filter(p => p.auth.oauth)去掉已登录的 厂商。2)选中 Anthropic 发起登录,在浏览器 完成授权,等待本地回调结束。3)打开模型选择器,刷新该账户的模型,选中一个并发送 消息直到流式结束;在同一会话再发一条。4)在第二个厂商上发起设备码登录,在对话框 轮询期间点击“取消”。5)重复第 4 步并完成登录,然后运行该行的连接测试。6)退出 Anthropic 账户登录,再删除该提供商行。7)在 sidecar 与渲染层日志中检索令牌串。 - 预期:登录按
vendorKey只创建一行,authKind: "oauth",hasSecret与hasOauth均为真,带非敏感账户标签,baseUrl/apiStyle/defaultModelId来自该 账户自己的目录。模型列表是已认证目录(Copilot 账户只列出其订阅包含的模型),而不是/models探测。两轮对话都不需要粘贴密钥,并复用同一个热 runtime——启动载荷里apiKey: "",每次请求经provider.resolveAuth解析,由 Electron main 本地作答, 未绑定的组合以PROVIDER_NOT_BOUND拒绝。取消会中止本地回调服务器/设备码轮询, 不留下提供商行,也不留下凭据。连接测试通过解析厂商认证来证明账户,而不是拿一把它 并不持有的密钥去探测。退出登录与删除提供商都会清除secret:provider:<id>:oauth(以及 api_key 引用),重新创建的行不会继承任何东西。任何日志、事件或 IPC 载荷都 不含访问令牌、刷新令牌或授权码。 - 链接规格:
03-runtime/11-provider-model-system.md§8a、03-runtime/12-provider-config-schema.md§3、03-runtime/14-secrets-storage.md§10、03-runtime/01-ipc-protocol.md§8、04-ux/06-settings-ia.md、08-meta/decisions-log.md(D237)、ADR 0095 - 接受:B(模型配置)、C(对话与流式)、F(持久性)、安全性、品质
- 里程碑:M6+
- 状态:
packages/agent-runtime(认证解析与 runtime 复用)与 host-core 密钥引用 测试已覆盖;完整桌面旅程为草稿且需要真实厂商账户(除非明确要求,否则不要在本地 运行 E2E)
E2E-153:vendor 的文件管理器替代内置 Files 工具
- 先决条件:一个打包构建(
resources/plugins位于 asar 外),以及包含嵌套目录、node_modules、.env、二进制文件、图片、CSV 和 Markdown 文件的项目。该项目是一个 项目组,其第二个文件夹已注册为另一个根(ADR 0249)。 - 步骤:
- 打开插件页,确认 文件管理器 作为内置插件列出、已启用、显示工作面板视图能力,且没有卸载操作。
- 展开工作面板并打开标题菜单,确认它出现在“插件视图”分组,而不是宿主工具中;触发一次代理编辑,确认面板不会自行打开,Review 仅在用户选择其行后才出现在“已打开项目”中。
- 打开文件管理器,确认文件树按需加载目录,并忽略
node_modules、.git和.env。把视图左上角的文件夹控件切到项目的第二个文件夹,确认文件树跟着切换,而应用显示的可见工作区不变,然后切回项目的主文件夹。 - 右键一个文件,确认提供“用默认应用打开”和“在文件夹中显示”且可用;右键一个目录,确认不提供这两项,因为宿主会拒绝目录。
- 打开文本文件、编辑并保存,确认磁盘上的文件已改变且编辑器保留保存后的内容。在应用之外改动同一文件,再次编辑并保存,确认报告冲突而不是覆盖外部改动。点击二进制文件,确认显示不支持预览而不是打印替换字符;依次打开图片、CSV 和 Markdown,确认各自使用专属查看器。切换到简体中文,确认文件树、查看器和上下文菜单均已本地化。切换项目后确认文件树立即更新,不必等待轮询。
- 点击对话中的项目文件路径,确认它在本视图中打开该文件——对话点击现在优先使用文件视图,而不是宿主的
file:选项卡。 - 禁用文件管理器插件,确认视图从菜单和面板消失,且此时点击对话中的文件路径会退回宿主的
file:<path>选项卡(位于「已打开项目」下)。 - 重新启用并重启应用,确认启用状态和文件树恢复,注册表没有重复行。
- 预期:这一面板完全通过公开插件贡献通道运行,可由用户禁用但不可卸载;由宿主代为执行的动作遵守声明的
fs.read范围,插件自身的读写仍留在它当前浏览的那一个项目文件夹的牢笼内(ADR 0241、ADR 0263)。 - 链接规格:
07-plugins/03-plugin-api.md§3、07-plugins/13-plugin-permissions-matrix.md§2、04-ux/08-component-spec.md§5、ADR 0104、ADR 0109、ADR 0111、ADR 0169、ADR 0241、ADR 0249、ADR 0263 - 接受:G(插件)、D(工作区)、安全性、品质
- 里程碑:M6+
- 状态:
apps/desktop/test/bundled-plugins.test.mjs(manifest 契约、页面只用公开桥接、vendored 校验和)、apps/desktop/test/plugin-fs-scope.test.mjs(fs.openDefault、fs.reveal)、apps/desktop/test/plugin-work-panel-views.test.mjs(停靠视图事件广播)已覆盖;完整打包旅程为草稿(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-PLUGIN-file-view-collapse-persists
- 先决条件:内置的文件管理器视图已在含有嵌套目录和一个文本文件的项目上打开,且工作面板宽度足以容纳双栏布局。
- 步骤:
- 把文件列表与内容区之间的分隔条拖到非默认宽度,然后只用键盘操作工具栏最左边的切换按钮(Tab 定位后按 Enter 或 Space)。
- 点击对话中的一个文件引用。
- 再次操作同一个切换按钮。
- 收起文件列表,关闭并重新打开该视图,最后重启应用。
- 手动展开文件列表,再点击另一个对话文件引用。
- 预期:切换按钮收起视图自己的左侧文件列表,内容区占满整个宽度,且按钮始终可用键盘到达,无障碍名称在「隐藏文件列表」与「显示文件列表」之间切换。对话点击触发的宿主打开请求会显示请求的文件、展开其祖先目录并收起文件列表——无论视图本来已经打开,还是由这次点击打开。再次展开时恢复之前拖拽的分栏宽度、已展开的目录与被选中的文件,而不是默认分栏宽度或项目根。收起状态会被持久化:关闭并重新打开视图乃至重启应用后仍然保持;手动展开会一直保持到下一次宿主打开请求再次收起它。
- 链接规格:
07-plugins/02-plugin-manifest-schema.md§4/§5、04-ux/08-component-spec.md§5.2.2、ADR 0104、ADR 0241、ADR 0262 - 接受:G(插件)、品质
- 里程碑:M6+
- 状态:随包副本的 manifest、入口页面与上游校验和已由
apps/desktop/test/bundled-plugins.test.mjs覆盖;插件侧旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-PLUGIN-file-view-switches-folder-per-project
- 先决条件:一个拥有两个文件夹的项目组,两个文件夹各有一个对方没有的文本文件,另有一个单文件夹项目。工作面板已打开随应用打包的文件管理器视图,正在浏览第一个项目的主文件夹。
- 步骤:
- 用视图左上角的文件夹控件切到项目的第二个文件夹,确认文件树、按文件名搜索以及打开/保存都跟着它走。
- 在只有第二个文件夹才有的那个文本文件里保存一次编辑。
- 关闭并重新打开该视图,再重启应用,确认仍在浏览第二个文件夹。
- 切到另一个项目再切回来,确认每个项目各自记住自己的文件夹。
- 先点击解析到项目第二个文件夹的对话引用,再点击解析到主文件夹的引用。
- 在选中第二个文件夹的情况下,用文件树搜索主文件夹里那个文件的名字,并试着打开
.env,以及第二个文件夹里指向外部的符号链接或 junction。7)在选中第二个文件夹的情况下,右键只有它才有的那个文本文件,依次用「用默认应用打开」与「在文件夹中显示」;再切到主文件夹,对只有主文件夹才有的文件做同样两步。8)切回第二个文件夹,对两个文件夹里都有同名的那份文件重复这两个动作。
- 预期:
- 文件夹控件按项目组顺序列出项目的文件夹,主文件夹在前,并标出正在浏览的是哪一个;文件树、搜索与编辑都只在这一个文件夹内工作,单文件夹项目只提供它自己那一个文件夹。
- 切换文件夹只改变这个视图浏览的内容:应用显示的可见工作区、智能体的工具根、会话的主路径、项目指令与项目记忆都不变(ADR 0263)。
- 选择按项目记忆:关闭并重新打开视图乃至完整重启应用后仍然保持,另一个项目也保持它自己的文件夹。
- 两个对话引用都在这个视图里打开所引用的文件——包括来自第二个文件夹的那个(在该文件夹里打开)——不会新增宿主
file:选项卡。 - 牢笼是所选文件夹,而不是整个项目组:按文件名搜索到不了只有别的项目文件夹才持有的文件,凭据类路径与符号链接/junction 逃逸依旧被拒绝(ADR 0241)。
- 这两个交给系统的动作落在被点击的那个文件、以及正在浏览的那个文件夹上:只有第二个文件夹才有的文件打开/显示的是它自己,而不是报「没找到」;两个文件夹同名的文件打开的是第二个文件夹里的那份,而不是主文件夹里的那份(ADR 0264)。
- 链接规格:
07-plugins/03-plugin-api.md§3、04-ux/08-component-spec.md§5.2.2、ADR 0241、ADR 0249、ADR 0263、ADR 0264 - 接受:G(插件)、安全性、品质
- 里程碑:M6+
- 状态:宿主侧的补全与地址规则已由
apps/desktop/test/chat-ref-resolve.test.mjs、apps/desktop/test/transcript-file-chips.test.mjs覆盖;插件侧旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-PLUGIN-bundled-plugin-keeps-a-marketplace-update
- 先决条件:一个构建,其内置插件在市场目录中存在更新版本,且有一个用户可安装的数据目录。
- 步骤:
- 打开插件页,确认该插件标记为内置、已启用,且不提供卸载操作。
- 从市场更新它,确认版本变为目录版本,且仍然不提供卸载操作。
- 重启应用,确认更新后的版本仍是已安装版本、插件仍启用,且注册表中只有一行。
- 禁用该插件并再次重启,确认它保持禁用且仍是更新后的版本。
- 预期:内置意味着默认且不可删除,但不是被冻结。用户安装的更新会跨过下一次启动(该启动会重新对账随应用打包的副本),应用自带严格更新的版本时仍以自带版本为准,且目录中不比已安装版本更新的版本永远不会被当作更新提供。
- 链接规格:
07-plugins/07-plugin-marketplace.md、ADR 0104、ADR 0241 - 接受:G(插件)、品质
- 里程碑:M6+
- 状态:host-core 的
a_bundled_plugin_keeps_the_update_the_user_installed、a_newer_shipped_version_replaces_an_older_user_install、a_plugin_a_build_stops_shipping_is_no_longer_bundled、market_entry_offers_an_update_only_when_the_catalog_is_newer已覆盖;完整打包旅程为草稿
E2E-NAV-plugins-button-goes-back:插件按钮复用导航历史
- 先决条件:隔离配置中有具名会话及可见消息;不需要模型凭据或外部插件 市场连接。
- 步骤:1) 选择会话,输入未发送草稿,从页脚进入插件页,再次点击同一 按钮。2) 重开插件页,在已安装列表输入搜索文本,返回后再次打开。3) 在 导航测试中分别从
pulls、scheduled和设置进入;再测试插件前同时存在 定时任务和设置的历史。4) 前进到插件页后再次点击插件按钮。5) 没有可后退 历史时打开插件页并点击按钮。 - 预期:第二次点击执行一次现有后退操作,不追加返回条目,也不跳过设置。 聊天历史条目复用现有会话选择和加载行为,返回后会话及未发送草稿仍可用。 无法后退时打开聊天页。按下状态反映插件页是否激活。插件浏览标签、搜索 文本和分类筛选跨页面卸载保留;对话框与监听正常释放,不在隐藏页继续 运行。设置导航保持不变。
- 链接规格:
04-ux/01-ui-ia.md§2/§5、04-ux/08-component-spec.md§3 - 验收:C(会话导航)、G(插件浏览)、Quality
- 里程碑:M6+
- 状态:实际页脚事件、历史 slice 与浏览状态回归已通过。重新构建后的 历史后退实现通过真实 macOS UI 两轮打开/返回验收:相同的两条会话消息 和未发送草稿保留,返回后按钮取消激活,重开保留已安装列表的搜索筛选。 非聊天历史条目、前进和无历史兜底通过测试覆盖;布局旅程现在会再次点击 页脚按钮并断言回到上一目的地,已在集成主线
d6ffaa3b上通过 37/37 项 检查。不需要外部插件市场或真实模型。
E2E-PROVIDER-custom-form-excludes-account-formats:新建自定义服务不提供账户专用格式
- 先决条件:隔离配置,英文及简体中文;构造使用 Codex、Pi 账户格式的 历史非 OAuth 行,其中包含 OpenAI 预设 URL,使用合成模型,不使用真实凭据。
- 步骤:1) 新建自定义服务,检查接口格式选项。2) 编辑两种历史行并原样 保存,再明确选择 Responses 保存。3) 复制原历史行,检查当前格式及说明, 等待超过发现防抖时间后取消。4) 再次复制,选择 Anthropic Messages 后保存。 5) 检查保存请求及原行未被改变。
- 预期:新建只提供四种通用协议;Codex 与 Pi 通过厂商账户使用,不能作为 新 API key 服务格式。历史行在未主动修改时保留协议、名称、URL 和认证。 复制时显示原账户格式但不可重新选择;必须主动选支持的格式后才能保存和 发现模型,并提供本地化说明。取消不创建行;有效副本不复用原行 ID 或凭据。 具名 OpenCode Go 与 OAuth 账户流程保持不变。
- 自动化:
pnpm test:e2e:provider-api-style在 Electron/Chromium 中渲染 实际 React 表单,以模拟 API 边界检查创建、更新及发现请求的准确内容。 不验证 Host 存储或真实 OAuth。 - 链接规格:
03-runtime/12-provider-config-schema.md、ADR 0095。 - 验收:B(模型配置)、Security。
- 状态:helper/复制回归已通过。分支 Electron/React 场景在英文、简体中文 下通过:六组场景,经模拟 API 发出四次创建、八次更新。未验证 Host 持久化、 真实 OAuth/模型请求和视觉布局。主线合并后 E2E 尚未运行。
E2E-PROVIDER-copy-config-without-credentials:复制配置为独立提供商
- 先决条件:设置页有一个普通提供商,已保存 API 密钥、自定义请求头和两个 模型绑定,绑定含不同别名、限制、思考级别及模态覆盖;另有一个 OAuth 账户。记录来源配置和全局默认提供商、模型。使用可捕获发现请求的确定性 端点,不使用真实密钥。
- 步骤:1) 复制普通提供商。2) 确认自定义服务草稿保留名称、地址、API 格式与模型绑定,密钥及自定义请求头为空,并显示未复制凭据和请求头的说明。 3) 修改 API 格式、某个模型的别名/限制及思考级别,然后取消。4) 确认 提供商数量、来源数据及全局默认值未变。5) 再次复制,在未填新密钥时触发 发现,再填写不同的测试密钥触发发现。6) 使用不同名称和修改后的 API 格式保存。7) 重新打开两份配置并编辑副本。8) 检查 OAuth 账户行没有 复制操作。9) 在草稿构建测试中为来源及模型加入未知字段,确认未被复制。 10) 复制 OpenCode Go,确认保留命名服务及固定格式,再切换到自定义服务 并选择其他普通 API 格式。
- 预期:取消不创建提供商或密钥。草稿模型对象、思考级别数组与来源不 共享引用。模型发现和连接测试不使用来源 provider id 或其已存凭据;需要 认证时只使用新草稿密钥。复制不读取秘密存储。保存经现有创建流程生成 独立提供商、模型绑定和凭据,来源与全局默认值保持不变。自定义请求头 和未知字段即使含类似凭据的值也不复制。OAuth 账户不能通过此操作复制。
- 链接规格:
03-runtime/12-provider-config-schema.md、03-runtime/14-secrets-storage.md - 验收:B(模型配置)、F(独立持久化)、Security
- 里程碑:M2
- 状态:真实 Host/helper 测试已验证独立创建、编辑、删除、来源凭据及 默认值保持,以及重启后的持久化。隔离无密钥配置中的实际 UI 验证确认: 取消后仍只有一个提供商;复制后把 Responses 改为 Anthropic Messages, 并修改名称、别名,保存后有两个提供商且全局默认值不变。重新打开两行 确认来源仍为 Responses 和原别名,副本保存了 Anthropic Messages 与新 别名。未测试携带凭据的网络发现、外部模型请求及 OpenCode Go UI 分支。
8. 可追溯性矩阵
| 验收 | 应用场景 |
|---|---|
| C / F — Hourly task updates | E2E-SCHEDULED-manual-to-hourly |
| C / F / Quality — Saved project isolation | E2E-SCHEDULED-manual-workspace-binding |
| C / F / Quality — 桌面定时任务 | E2E-SCHEDULED-desktop-automation-lifecycle |
| F / Security / Quality — 便携式配置同步 | E2E-CONFIG-SYNC-webdav-portable-configuration |
| A / C — Unicode stdio 成帧 | E2E-RPC-unicode-separators |
| C / G / Quality — Plugins navigation | E2E-NAV-plugins-button-goes-back |
| C / D / Quality — 侧边栏行状态 | E2E-LAYOUT-sidebar-row-states |
| A / C / Quality — 侧栏材质与设置返回 | E2E-LAYOUT-sidebar-settings |
| B / F / Security — 提供商复制 | E2E-PROVIDER-copy-config-without-credentials |
| B / F / Quality — 已选模型顺序 | E2E-MODEL-selected-order-persists |
| A — 应用程序启动 | E2E-001、E2E-002、E2E-003、E2E-004、E2E-067、E2E-076、E2E-079、E2E-092、E2E-097、E2E-143、E2E-150、E2E-168、E2E-204、E2E-217 |
| B——模型配置 | E2E-005、E2E-005G、E2E-006、E2E-007、E2E-038、E2E-050、E2E-052、E2E-055、E2E-066、E2E-080、E2E-082、E2E-151、E2E-005J、E2E-199、E2E-201、E2E-202、E2E-203、E2E-209、E2E-166 |
| C — 对话和直播 | E2E-CHAT-running-status-survives-output-pauses、E2E-008、E2E-008d、E2E-008a、E2E-009、E2E-010、E2E-011、E2E-011a、E2E-011b、E2E-031、E2E-040、E2E-047、E2E-048、E2E-048A、E2E-049、E2E-052、 E2E-053、E2E-054、E2E-055、E2E-059、E2E-059a、E2E-060c、E2E-060d、E2E-061、E2E-061a、E2E-062、E2E-064、E2E-065、E2E-068、E2E-071、 E2E-073、E2E-074、E2E-075、E2E-081、E2E-083、E2E-084、E2E-086、E2E-087、E2E-088、E2E-088b、E2E-089、E2E-090、E2E-094、E2E-095、E2E-096、 E2E-097、E2E-098、E2E-099、E2E-102、E2E-102a、E2E-102b、E2E-106、E2E-109、E2E-111、E2E-114、E2E-116、E2E-117、E2E-118、E2E-119、 E2E-120、E2E-121、E2E-代理-001、E2E-142、E2E-144、E2E-145、E2E-146、E2E-147、E2E-151、E2E-199、E2E-250、E2E-166、E2E-SUBAGENT-resume-a-settled-delegation |
| A / C / F / Quality — Tray session navigation | E2E-TRAY-bounded-session-navigation |
| D——工作区 | E2E-012、E2E-013、E2E-022B、E2E-024I、E2E-047、E2E-049、E2E-057、E2E-058、E2E-060、E2E-068、E2E-075、E2E-078、E2E-153 |
| D——工作区(项目排序) | E2E-253 |
| E——工具和权限 | E2E-008a、E2E-014、E2E-015、E2E-016、E2E-017、E2E-018、E2E-019、E2E-024I、E2E-024K、E2E-040、E2E-049、E2E-074、E2E-093、E2E-097、 E2E-099、E2E-100、E2E-101、E2E-102、E2E-103、E2E-105、E2E-106、E2E-107、E2E-111、E2E-112、E2E-113、E2E-114、E2E-115、E2E-116、 E2E-119、E2E-121、E2E-122、E2E-123、E2E-142、E2E-145、E2E-147、E2E-PLUGIN-imported-pi-package-skills、E2E-166 |
| F——坚持 | E2E-020、E2E-021、E2E-036、E2E-037、E2E-038、E2E-040、E2E-042、E2E-047、E2E-048、E2E-051、E2E-054、E2E-056、E2E-061、E2E-062、 E2E-064、E2E-066、E2E-068、E2E-071、E2E-072、E2E-073、E2E-082、E2E-084、E2E-096、E2E-098、E2E-102、E2E-102b、E2E-103、E2E-代理-001、 E2E-061a、E2E-073a、E2E-104、E2E-106、E2E-107、E2E-108、E2E-109、E2E-110、E2E-112、E2E-118、E2E-119、E2E-120、E2E-121、E2E-123、E2E-142、E2E-146、E2E-148、E2E-151、E2E-171、E2E-005J |
| F——持久化(项目排序) | E2E-253 |
| G——插件 | E2E-022、E2E-022A、E2E-022B、E2E-022C、E2E-023、E2E-024、E2E-024B、E2E-024C、E2E-024D、E2E-024AA、E2E-024E、E2E-024W、E2E-024F、E2E-024G、E2E-024H、 E2E-024I、E2E-024J、E2E-024K、E2E-024L、E2E-024M、E2E-024N、E2E-024O、E2E-024P、E2E-025、E2E-026、E2E-105、E2E-117、E2E-120、E2E-122、E2E-123、E2E-148、E2E-153、E2E-PLUGIN-imported-pi-package-skills、E2E-PLUGIN-imported-pi-package-wrapper、E2E-PLUGIN-import-extension-installs-dependencies、E2E-PLUGIN-import-extension-reports-missing-dependency、E2E-PLUGIN-global-shortcut-owns-only-its-own-command、E2E-PLUGIN-permission-gate-for-real-time-capabilities、E2E-PLUGIN-background-audio-and-realtime-connection |
| H——诊断 | E2E-027、E2E-031、E2E-034、E2E-042、E2E-096、E2E-098、E2E-104、E2E-107、E2E-108、E2E-109、E2E-110、E2E-113、E2E-115、E2E-116、 E2E-118、E2E-121、E2E-146、E2E-194、E2E-195 |
| 安全性 | E2E-028、E2E-029、E2E-030、E2E-024J、E2E-024K、E2E-024M、E2E-049、E2E-068、E2E-086、E2E-105、E2E-106、E2E-107、E2E-108、E2E-109、 E2E-110、E2E-112、E2E-113、E2E-115、E2E-116、E2E-117、E2E-119、E2E-121、E2E-122、E2E-123、E2E-142、E2E-148、E2E-151、E2E-153 |
| 品质 | E2E-CHAT-running-status-survives-output-pauses、E2E-032、E2E-033、E2E-039、E2E-043、E2E-044、E2E-045、E2E-046、E2E-047、E2E-048、E2E-048A、E2E-049、E2E-050、E2E-053、E2E-055、 E2E-056、E2E-057、E2E-058、E2E-059、E2E-060、E2E-061、E2E-062、E2E-063、E2E-064、E2E-065、E2E-066、E2E-067、E2E-068、E2E-069、 E2E-070、E2E-071、E2E-072、E2E-073、E2E-074、E2E-075、E2E-076、E2E-077、E2E-078、E2E-079、E2E-080、E2E-081、E2E-082、E2E-083、 E2E-084、E2E-085、E2E-086、E2E-092、E2E-093、E2E-094、E2E-095、E2E-096、E2E-097、E2E-098、E2E-099、E2E-100、E2E-101、E2E-102、 E2E-102a、E2E-102b、E2E-103、E2E-AGENTS-001、E2E-024N、E2E-024O、E2E-059a、E2E-060b、E2E-060c、E2E-060d、E2E-061a、E2E-073a、E2E-111、 E2E-114、E2E-117、E2E-118、E2E-119、E2E-120、E2E-122、E2E-123、E2E-142、E2E-143、E2E-144、E2E-145、E2E-146、E2E-147、E2E-148、E2E-150、E2E-151、E2E-153、E2E-194、E2E-195、E2E-199、E2E-200、E2E-201、E2E-202、E2E-203、E2E-204、E2E-209、E2E-210、E2E-250、E2E-PLUGIN-imported-pi-package-skills、E2E-SUBAGENT-resume-a-settled-delegation |
| 品质(项目排序) | E2E-253 |
| C — 对话和直播(输入法斜杠别名) | E2E-255 |
| E——工具和权限(Skill 常驻) | E2E-254 |
| 品质(Skill 常驻与输入法斜杠别名) | E2E-254、E2E-255 |
| C — 对话和直播(导入可见性) | E2E-257 |
| F——持久化(导入可见性) | E2E-257 |
| C — 对话和直播(聊天文件引用) | E2E-CHAT-shorthand-file-ref-opens-the-matching-file、E2E-CHAT-file-ref-opens-the-surface-that-owns-it |
| G——插件(聊天文件引用) | E2E-CHAT-file-ref-opens-the-surface-that-owns-it、E2E-PLUGIN-file-view-collapse-persists |
| 品质(聊天文件引用) | E2E-CHAT-shorthand-file-ref-opens-the-matching-file、E2E-CHAT-file-ref-opens-the-surface-that-owns-it、E2E-PLUGIN-file-view-collapse-persists |
| G——插件(项目文件夹根) | E2E-PLUGIN-file-view-switches-folder-per-project |
| 安全性(项目文件夹根) | E2E-PLUGIN-file-view-switches-folder-per-project |
| 品质(项目文件夹根) | E2E-PLUGIN-file-view-switches-folder-per-project |
| G——插件(导入可见性) | E2E-257 |
| 品质(导入可见性) | E2E-257 |
| G——插件(Session Orchestrator) | E2E-PLUGIN-session-orchestrator-real-workers |
| 安全性(Session Orchestrator) | E2E-PLUGIN-session-orchestrator-real-workers |
| 品质(Session Orchestrator) | E2E-PLUGIN-session-orchestrator-real-workers |
| C — 对话与流式(会话列表响应性) | E2E-SESSION-list-refresh-keeps-desktop-responsive |
| 品质(会话列表响应性) | E2E-SESSION-list-refresh-keeps-desktop-responsive |
| 安全性(导入扩展依赖) | E2E-PLUGIN-import-extension-installs-dependencies、E2E-PLUGIN-import-extension-reports-missing-dependency |
| 品质(导入扩展依赖) | E2E-PLUGIN-import-extension-installs-dependencies、E2E-PLUGIN-import-extension-reports-missing-dependency |
| F / G / 安全性 / 品质 — 导入扩展的 npm 恢复 | E2E-PLUGIN-import-extension-recovers-missing-npm |
| G — 插件(桌面工具调度预算) | E2E-PLUGIN-slow-tool-is-not-cut-off-by-host-dispatch |
| C — 对话与流式(独立会话通信) | E2E-SESSION-independent-top-level-communication |
| D — 插件安全(独立会话通信) | E2E-SESSION-independent-top-level-communication |
| G — 插件(独立会话通信) | E2E-SESSION-independent-top-level-communication |
| 品质(独立会话通信) | E2E-SESSION-independent-top-level-communication、E2E-SESSION-hover-card-model-and-links |
| C — 对话与流式(hover 卡片模型和链接) | E2E-SESSION-hover-card-model-and-links |
| D — 工作区(删除项目) | E2E-PROJECT-delete-removes-project-and-owned-sessions |
| F — 持久化(删除项目) | E2E-PROJECT-delete-removes-project-and-owned-sessions |
| 品质(删除项目) | E2E-PROJECT-delete-removes-project-and-owned-sessions |
| 品质(两步删除) | E2E-SESSION-two-click-delete-arms-first |
| Security (plugin real-time capabilities) | E2E-PLUGIN-global-shortcut-owns-only-its-own-command、E2E-PLUGIN-permission-gate-for-real-time-capabilities、E2E-PLUGIN-background-audio-and-realtime-connection |
| C — 对话与流式(展开详情保持阅读位置) | E2E-CHAT-disclosure-toggle-keeps-reading-position |
| E — 工具与权限(展开详情保持阅读位置) | E2E-CHAT-disclosure-toggle-keeps-reading-position |
| E — 工具与权限(能力跨级别迁移) | E2E-CAPABILITY-move-across-levels |
| F — 持久化(能力跨级别迁移) | E2E-CAPABILITY-move-across-levels |
| 品质(能力跨级别迁移) | E2E-CAPABILITY-move-across-levels |
| 里程碑 | 应用场景 |
|---|---|
| M1 | E2E-001、E2E-002、E2E-003、E2E-028、E2E-029 |
| M2 | E2E-004、E2E-005、E2E-006、E2E-007、E2E-008、E2E-008d、E2E-009、E2E-010、E2E-011、E2E-011a、E2E-011b、E2E-020、E2E-021、E2E-027、E2E-031、 E2E-036、E2E-037、E2E-042、E2E-087、E2E-088、E2E-088b、E2E-089、E2E-090、E2E-144、E2E-005J、E2E-201 |
| M3 | E2E-012、E2E-013、E2E-014、E2E-015、E2E-016、E2E-017、E2E-018、E2E-019、E2E-040 |
| M4 | E2E-022、E2E-023、E2E-024、E2E-025、E2E-026、E2E-030、E2E-038 |
| M5 | E2E-CHAT-running-status-survives-output-pauses、E2E-008a、E2E-032、E2E-033、E2E-034、E2E-039、E2E-043、E2E-044、E2E-045、E2E-046、E2E-047、E2E-048、E2E-048A、E2E-049、E2E-050、 E2E-051、E2E-052、E2E-053、E2E-054、E2E-055、E2E-056、E2E-057、E2E-058、E2E-059、E2E-060、E2E-061、E2E-062、E2E-063、E2E-064、 E2E-065、E2E-066、E2E-067、E2E-068、E2E-069、E2E-070、E2E-071、E2E-072、E2E-073、E2E-074、E2E-075、E2E-076、E2E-077、E2E-078、 E2E-079、E2E-080、E2E-081、E2E-082、E2E-083、E2E-084、E2E-085、E2E-086、E2E-092、E2E-093、E2E-096、E2E-097、E2E-098、E2E-099、 E2E-100、E2E-101、E2E-102、E2E-102a、E2E-102b、E2E-AGENTS-001、E2E-059a、E2E-060b、E2E-060c、E2E-061a、E2E-073a、E2E-094、E2E-095、E2E-143、E2E-145、E2E-146、E2E-147、E2E-194、E2E-195、E2E-204、E2E-250 |
| M5(项目排序) | E2E-253 |
| M2(输入法斜杠别名) | E2E-255 |
| M5(Skill 常驻) | E2E-254 |
| M6 | E2E-104、E2E-105、E2E-106、E2E-107、E2E-108、E2E-109、E2E-110、E2E-111、E2E-112、E2E-113、E2E-114、E2E-115、E2E-116、E2E-117、 E2E-118、E2E-119、E2E-120、E2E-103 |
| M6+ | E2E-121、E2E-122、E2E-123、E2E-142、E2E-148、E2E-150、E2E-151、E2E-168、E2E-199、E2E-200、E2E-202、E2E-203、E2E-209、E2E-211、E2E-212、E2E-213、E2E-214、E2E-215、E2E-216、E2E-217、E2E-257、E2E-166、E2E-SUBAGENT-resume-a-settled-delegation |
| M6+(Session Orchestrator) | E2E-PLUGIN-session-orchestrator-real-workers |
| M6+(已选模型顺序) | E2E-MODEL-selected-order-persists |
| M6+(会话列表响应性) | E2E-SESSION-list-refresh-keeps-desktop-responsive |
| M6+(独立会话通信) | E2E-SESSION-independent-top-level-communication、E2E-SESSION-hover-card-model-and-links |
| M5(聊天文件引用) | E2E-CHAT-shorthand-file-ref-opens-the-matching-file、E2E-CHAT-file-ref-opens-the-surface-that-owns-it |
| M6+(聊天文件引用) | E2E-PLUGIN-file-view-collapse-persists |
| M6+(项目文件夹根) | E2E-PLUGIN-file-view-switches-folder-per-project |
| 后MVP | E2E-022A、E2E-022B、E2E-022C、E2E-024I、E2E-024J、E2E-024K、E2E-024L、E2E-024M(插件路线图 R2/R3/R6) |
| 基线后本地自动化 | E2E-220 |
| MVP 后远程控制 | E2E-221、E2E-222、E2E-223、E2E-224、E2E-225、E2E-226、E2E-227、E2E-228、E2E-229、E2E-230、E2E-231、E2E-232 |
| 受信任扩展(R7 v1) | E2E-DIALOG-long-text-boundaries、E2E-241、E2E-242、E2E-HOOKS-cancel-and-dispose、E2E-243、E2E-244、E2E-245、E2E-PLUGIN-imported-pi-package-skills、E2E-PLUGIN-import-extension-installs-dependencies、E2E-PLUGIN-import-extension-reports-missing-dependency、E2E-PLUGIN-declared-provider-appears-in-the-native-provider-list |
| 受信任扩展(R7 v1 npm 恢复) | E2E-PLUGIN-import-extension-recovers-missing-npm |
| Post-MVP 回归覆盖(插件工具调度) | E2E-PLUGIN-slow-tool-is-not-cut-off-by-host-dispatch |
| M6+(删除项目) | E2E-PROJECT-delete-removes-project-and-owned-sessions |
| M6+(两步删除) | E2E-SESSION-two-click-delete-arms-first |
| C — 对话和直播(模型回退) | E2E-SUBAGENT-ordered-model-fallback-preserves-work |
| 品质(模型回退隔离) | E2E-SUBAGENT-ordered-model-fallback-preserves-work |
| C — 对话和直播(旧版子代理回合上限) | E2E-SUBAGENT-legacy-turn-limit-frontmatter-is-ignored |
| 品质(旧版子代理回合上限) | E2E-SUBAGENT-legacy-turn-limit-frontmatter-is-ignored |
| M6+(展开详情保持阅读位置) | E2E-CHAT-disclosure-toggle-keeps-reading-position |
| M6+(能力跨级别迁移) | E2E-CAPABILITY-move-across-levels |
| E — 工具与权限(内置子智能体默认项) | E2E-SUBAGENT-settings-lists-builtin-defaults |
| 品质(内置子智能体默认项) | E2E-SUBAGENT-settings-lists-builtin-defaults |
| C — 对话与流式(不透明浮动表面) | E2E-CHAT-opaque-floating-decision-and-retry-surfaces |
| 品质(不透明浮动表面) | E2E-CHAT-opaque-floating-decision-and-retry-surfaces |
| M6(不透明浮动表面) | E2E-CHAT-opaque-floating-decision-and-retry-surfaces |
| B — 模型配置(目录窗口来源) | E2E-MODEL-catalog-window-correction-reaches-saved-bindings |
| F — 持久化(目录窗口来源) | E2E-MODEL-catalog-window-correction-reaches-saved-bindings |
| 品质(目录窗口来源) | E2E-MODEL-catalog-window-correction-reaches-saved-bindings |
| M6+(目录窗口来源) | E2E-MODEL-catalog-window-correction-reaches-saved-bindings |
| C — 对话和直播(委托上下文预算) | E2E-SUBAGENT-context-overflow-compacts-before-failing、E2E-SUBAGENT-context-overflow-reports-actionable-failure、E2E-SUBAGENT-resume-seeds-within-context-budget |
| 品质(委托上下文预算) | E2E-SUBAGENT-context-overflow-compacts-before-failing、E2E-SUBAGENT-context-overflow-reports-actionable-failure、E2E-SUBAGENT-resume-seeds-within-context-budget |
| M6+(委托上下文预算) | E2E-SUBAGENT-context-overflow-compacts-before-failing、E2E-SUBAGENT-context-overflow-reports-actionable-failure、E2E-SUBAGENT-resume-seeds-within-context-budget |
| C / F / 品质 —— 上下文估算保持安全(校准) | E2E-CONTEXT-estimate-calibration-stays-safe |
| C — 对话与流式(工具调用 id 唯一) | E2E-RUNTIME-unique-tool-call-ids-per-request |
| 品质(工具调用 id 唯一) | E2E-RUNTIME-unique-tool-call-ids-per-request |
| C — 对话与流式(循环上下文归属) | E2E-RUNTIME-loop-context-ownership |
| 品质(循环上下文归属) | E2E-RUNTIME-loop-context-ownership |
| G — 插件宿主生命周期(崩溃上报) | E2E-PLUGIN-crash-report-names-the-exit-code |
| 品质(崩溃上报) | E2E-PLUGIN-crash-report-names-the-exit-code |
| F — 持久化(存储的模型绑定数组) | E2E-PROVIDER-stored-binding-array-reads-entry-by-entry |
| 品质(存储的模型绑定数组) | E2E-PROVIDER-stored-binding-array-reads-entry-by-entry |
US-UI-* 视觉场景(§UI shell 视觉场景)追踪到 决策日志 §D 中的法典平价决策 而不是 A-H 标准;他们的黄金来源是捕获套件。
9. AI 必须如何更新本文档
添加或更改影响用户可见或协议可见行为的功能时:
- 使用第 6 节中的模板添加新场景。分配下一个可用 ID (
E2E-<N>)。 - 将其链接至相关验收标准 (A–H) 和里程碑 (M1–M6 或 M6+ 用于当前产品增量)。
- 将状态设置为
Draft,除非已存在自动测试。 - 更新第 8 节中的可追溯性矩阵。
- 提交更新作为更改的一部分(根据 ai-development-workflow R3)。
10. 未来自动化地图
当实施 E2E 自动化时(M5 之后):
- 每个
Draft场景→剧作家测试文件。 - 场景 ID 变为测试用例名称:
e2e-001-app-launches。 tests/e2e/fixtures/目录中定义的夹具和测试数据路径。- CI 门:所有 E2E 场景必须通过才能发布。
当工具决策最终确定时,自动化部分将在未来的 ADR 中扩展。
11. 验收标准
该测试计划规范在以下情况下被接受:
- [ ] 所有 MVP 验收标准 (A–H) 至少有一种 E2E 场景。
- [ ] 所有安全验收项至少有一个 E2E 场景。
- [ ] 每个场景至少链接到一个规范文档。
- [ ] 可追溯性矩阵已完成(场景 ↔ 验收 ↔ 里程碑)。
- [ ] 场景模板已定义,所有条目均遵循该模板。
- [ ] AI 更新规则已记录并与工作流程规范交叉链接。
- [ ] 环境要求符合基线(本机 macOS arm64/Intel x64,干净的配置文件)。
UI shell 视觉场景
US-UI-01 符合 Codex 的外壳镀铬
- 在 macOS 深色主题上打开桌面应用程序。
- 预计木炭主表面(
#181818),左侧边栏包含当前项目 和临时会话组,以及浮底输入框 配有 mode/model 控件,无工作区导轨。 - 预计不会有蓝石营销镀铬;主要发送控件是一个圆形倒置按钮。
US-UI-02 空线程英雄
- 打开或创建零消息的线程。
- 期待居中的英雄文案“我能帮你建造什么?”,一个简短的静音 支持线,没有开发者入门卡。可选的项目名称 当工作区打开时,仍然是点下划线操作。
US-UI-03 侧边栏目的地
- 预计扩展的主页侧边栏将显示会话和项目,而无需 独立插件、拉取请求或计划行。
- 单击侧栏页脚右侧的插件图标 设置,并期望它用专用页面取代主窗格。
- 打开设置→项目存档并使用它来打开、切换和关闭本地 文件夹工作区。
US-UI-04 没有工作区上下文的 Composer
- 打开 git 工作区时,composer 不显示项目、本地或分支 提示表面上方的标签。
- 操作模式选择器在 Agent、Plan 和 Goal 之间切换;双方签订合同 模式保留了权限模式芯片并解释了它们的 Bash 权衡。
US-UI-05 区域设置 chrome
- 在 zh-CN 系统语言环境中,侧边栏标签以中文呈现(项目/临时会话), 没有拉取请求或已安排条目。页脚插件图标显示 本地化的可访问名称插件。
- 空线英雄和辅助线均为汉化汉化副本;项目 当工作区打开时,名称仍然是点下划线操作。
- Composer 省略本地工作区标签并显示 Agent/Plan/Goal 以及 活动模型 ID;这两个区域设置都会公开 Plan 和 Goal 批准副本。
US-UI-06 会话自动标题
- 创建一个新任务并发送第一个提示,例如“同步代码”。
- 期望其项目或临时会话行标题成为截断形式 该提示而不是保留“新任务”。
US-UI-08 仅快捷方式目的地历史记录
- 导航设置 → 项目存档 → 项目会话 → 插件。
- 扩展侧边栏或主标题栏中不会有 back/forward 按钮。
- 按
Cmd/Ctrl+[和Cmd/Ctrl+];期望他们能够穿越那段历史。
US-UI-09 分组会话标题回填
- 打开先前显示“新任务”/“新聊天”但有第一条用户消息的旧会话。
- 期望其范围侧边栏行显示截断的第一用户消息标题 会话列表加载后。
US-UI-11 空草稿重用
- 单击“新任务”两次。
- 预计当前项目或临时项目中只有一份空的“新任务”草稿 团体和主场英雄仍然可见。另一个范围内的空草稿是 不被重复使用。
US-UI-12 Composer 不带工作区导轨
- 在空主目录、项目主目录和线程中,预计没有项目/本地/ 输入框上方的分支上下文栏。
- 提示外壳保持一个不间断的圆角表面,无保留 导轨高度、附加顶唇、导轨阴影、底部接缝或分隔符。
US-UI-13 Light 主题 shell 平价
- 在浅色 macOS 外观上将主题设置为 system/light。
- 预计侧边栏
#f3f3f3、主#ffffff、文本#1a1c1f、白色浮动输入框和带有项目下划线的主页英雄。 - 侧边栏 project/session 标签、页脚 Settings/Plugins/notification 图标、当前项目标识、线程标题和输入框控件必须保留 可读明暗对比 (≥4.5:1)。永远不要 white/translucent 文字亮起 侧边栏。
- macOS 交通灯行使折叠侧边栏在 就在浅铬上,不渲染 Logo/Home 品牌。
US-UI-14 语义 chrome 标记
- 切换主题系统→浅色→深色,无需重新启动。
- Shell chrome(侧边栏项目、Composer 运行时控件、图标按钮) 两个主题中均遵循语义
--ds-text-*/--ds-bg-*标记;不 浅色表面上的硬编码白色 (gray-0) 文本。
US-UI-15 法典密度 + 海拔
- 侧边栏行使用紧凑的约 28–32 像素间距,层次结构为 12–14 像素 US-UI-69 和 8px 水平填充(Codex
radius-token-row10px)。 - 浮动输入框使用 Codex 高度突出:0.5 像素笔画 + 柔和的 3px/20px 阴影(不是沉重的 10–30 像素下降)。
- 空英雄标题的行高为 28px / 34px,粗细为 400。
- 如果 Stage Manager 折叠窗口,窗口将恢复 ≥1000×700(目标 1200×800)。
US-UI-16 侧边栏页脚实用布局
- 在 light/dark home shell 上,侧边栏页脚是一个透明实用程序 带没有分隔符。设置、插件和通知操作 分组在左侧,而 build/version 芯片则右对齐。
- 通知铃声在左侧操作组中仍然可见,其 未读徽章并打开页脚上方的收件箱;主标题栏没有 复制贝尔。
- 单击 build/version 芯片会检查当前更新或打开 设置 → 可用更新时的信息。
- Codex
{x:16,y:16}上的交通灯带有 46 像素的工具栏;扩展的 macOS 侧边栏将折叠侧边栏放在右侧相同的位置 行,没有 Logo/Home 品牌或 back/forward 按钮。
US-UI-17 PI-Desktop 家庭英雄标志
- 在空聊天主页上,100px
HomeMascotLogoGIF 在标题上方呈现 八帧挥手吉祥物,并在首帧稍作停留。浅色和深色主题各使用一套 GIF 和静止 PNG。 - 指针悬停不改变节奏或几何形状;减少运动时显示对应静止 首帧。吉祥物保持装饰性。
- 标题为 28px / 粗细为 400;活动项目名称使用点下划线(1 像素,偏移 4 像素)。
- Composer 不会在有效负载之前渲染附件或 appshot 控件 首尾相连达到 pi。
US-UI-18 Composer 没有惰性操作
- 在聊天主页和停靠线程上,检查每个输入框控件。
- 当这些有效负载存在时,预计没有文件、照片或应用程序快照控件 pi 运行时不支持。具有精确推理能力的模型揭示了 当前思维级别紧邻 Agent / Plan / Goal 右侧;不支持的 模型显示没有触发器。未知的兼容模型可以显式启用 从模型菜单思考,更改更新持久会话。
- 预计 Composer 中没有项目、本地或分支上下文标签。
- 每个可见的 Composer 控件都会更改活动会话、打开其菜单,或者 submits/aborts 当前回合。
US-UI-19 永久舞台管理器边界恢复(仅 macOS)
- 在使用 Stage Manager 的 macOS 上,缩小或取消聚焦 PI 窗口,直到宽度 < 1040 或高度 < 700。
- 预计外壳会重新声明类似 Codex 的足迹(~1200×800,最小 1040×700)并在仍然折叠的情况下继续恢复(不仅在发射后的前 20 秒内)。
- 该恢复看门狗仅限 macOS(D447)。在 Windows/Linux 上它必须完全不运行:应用绝不能在无人操作时重新调整或抬升自己的窗口。聚焦其他窗口,确认 PI-Desktop 留在其后方而不是跳回窗口栈顶端,并且栈序检查(
xprop -root _NET_CLIENT_LIST_STACKING)不会显示它周期性回到顶端。
US-UI-20 深色浮动编辑框
- 在聊天主页切换到深色主题。
- 预计主
#181818、侧边栏#000000以及位于高架主要位置的浮动输入框板(#212121f5/ 灰色-800 96%),具有高程突出笔画 + 软升力,以便盒子在主表面上读取。
US-UI-21 Composer 模型菜单配置 pi
- 与提供商 A/model A 创建会话,然后打开 Composer 右侧模型 × 推理菜单。
- 期望顶部栏只显示任务标题和窗口操作。Composer 右侧组合芯片显示 provider A/model A 和当前推理级别。菜单根部仅有“模型”和“推理级别”; “模型”打开可搜索的 provider 分组列表,“推理级别”打开能力过滤的单选列表。
- 选择提供商 B/model B,发送提示,并期望 main-to-sidecar
agent.prompt负载和 pi 运行时使用 B 进行该会话。 - 切换离开和返回;期望 B 继续被选中。当回合运行时,预计 模型控制被禁用。
US-UI-22 配置文件页脚菜单
- 在侧边栏页脚上,单击
Custom/Local profile触发器。 - 预计页脚上方 8 像素处有一个 280 像素的不透明升高菜单。重复本地的 非交互式标题中的标识,然后显示分隔符和设置, 按顺序记录日志和主题操作。
- 方向键贯穿三个动作; Home/End 跳转到边界。 Escape 关闭菜单并恢复触发焦点。外部指针按下 关闭它而不窃取目标焦点。
- 设置导航到设置页面,日志打开本地日志,主题 关闭菜单后循环当前主题。
US-UI-23 项目档案索引
- 打开设置 → 项目存档。
- 预期设置标题“项目存档”加上一个工作台构成 (D168/D267): 一条仅承载页面说明的安静引导行,没有英雄区块、渐变横幅或页面级 计数器串;一个工具栏, 带最近/姓名排序分段控件、搜索字段、其清除可供性、实时匹配计数 以及主要的“添加项目”;以及一个面板,其部分按“固定”、“所有项目”、 “已存档”的顺序呈现为面板内标题条,当前存在的每条都显示其标签和 行数。完全没有项目的情况下,该面板显示一个具有其自己的主要操作的 安静空状态。
- 期望每一行都带有一个彩色字形,即项目名称及其活动/ 打开/固定/存档标签,一条带有缩短的等宽字体的元行 路径、分支和会话计数、相对最后活动时间以及 悬停显示新任务和行菜单操作。已归档的行保持列出状态并且 软化而不是隐藏。
- 展开一个非活动项目并打开其中一个会话;期望该应用程序 在选择会话之前激活该项目,因此工作区工具和 会话范围使用相同的项目。
- 将排序切换为名称并期望行在每个部分内重新排序 没有隐藏行;清除搜索并期望返回完整的索引。
US-UI-24 设置全页 shell
- 打开设置(页脚配置文件 → 设置)。
- 预计全页 Codex 设置(无应用程序 sidebar/nav)。左导轨有后部 到应用程序、搜索和确切的基础知识/全局AI/快捷方式/模型配置/ 按顺序导入/项目存档/信息;内容窗格显示部分标题和 目的地的设置或存档内容。
- 返回应用程序外壳并期望插件保持独立 侧边栏页脚目的地。
- 将空的 46 像素顶带拖到导轨或内容窗格上;本地人 窗口移动,而“后退”、“搜索”和“导航”仍然可单击。
US-UI-27 深色目标页面
- 强制使用深色主题并打开插件和设置 → 项目存档。
- 预计黑色侧边栏、主
#181818和目标 cards/rows 在升高的暗板(不是平面同灰色)上可读。
US-UI-28 主页空输入框协会
- 在空的聊天主页(浅色+深色)中,期待英雄,可选的入门 清单和家庭输入框在一个可滚动的垂直流中(D111/D204/D206), 没有大的空白间隙或起始卡层。
- 组合器仍然是一个独立的板,没有附加的工作区导轨。
- 开始转录会恢复底部停靠的输入框并带有淡入淡出的面纱。
US-UI-29 Light Composer 板易读性
- 在浅色主题的空屋中,白色输入框外壳使用统一的实体 填充没有内部渐变或背景图像。
- 外壳仍然通过细细的笔触读作一个升高的盒子,
#ffffff主表面上的受限软阴影。 - 工具栏控件和占位符保持清晰(不是纯白底白字)。
US-UI-30 Composer 占位提示文案
- 空首页和会话输入框分别从本地化欢迎语开始:
chat.placeholderHome/chat.placeholder。 - 当前提示在页面/会话上下文变化前保持不变;切换上下文时才推进到本地化
//@命令/文件提示和Shift+Enter 换行 · 点击发送提交快捷键提示,并使用透明度渐变。 - 等待、聚焦、编辑、清空或组合输入都不会改变文案。
- 占位符墨水在浅色和深色浮动板上清晰可见。
US-UI-31 Home 空垂直堆栈 (D111/D204/D206)
- 给定空聊天主页,当窗口为~1200×690时,英雄和可选 入门清单呈现在上方居中的可滚动内容堆栈中 底部保留的家庭输入框(不是双增长绝对门户区域)。
- 不存在起始卡或绝对覆盖;新手引导清单 仍然可操作,并且可以直接与输入框联系。
US-UI-32 深色浮动框标高
- 给定黑暗主题空置家庭,当绘制输入框外壳时,它在
#181818上使用升高的主#212121,并具有与光相同的高度突出笔划+提升(没有较重的自定义深色阴影)。
US-UI-33 范围侧边栏会话组
- 主侧边栏没有最近的聚合。
- 它为每个保留的项目路径显示一个独立的可折叠标题 嵌套会话和一个
Temporary sessions/临时会话标头 无路径会话。 - 项目和临时标头公开紧凑的特定于范围的
+控件; project/session 溢出菜单公开 pin/archive 操作;导航行间距 保持约 32 像素,会话行间距约 28-31 像素。
US-UI-34 Home 没有开发者入门卡 (D206)
- 在空的聊天主页(浅色+深色)上,没有开发人员入门网格、卡片或 上下文快速操作行在英雄和输入框之间呈现。
- 任务条目直接从底部编辑器开始,而可选的 新手引导清单在存在时仍然可操作。
US-UI-35 空作曲器板密度
- 空屋输入框结构紧凑,内容驱动,带有空行或一行 草稿;它不保留以前固定的~148px空板。
US-UI-36 Hero Y + 夜间箱仰角
- 在 ~1200×690 灯光下,英雄形成一个居中的方块;家 滚动条不会剪裁其顶部或与底部合成器重叠,并且没有启动器 网格被渲染。
- 深色家庭输入框板读取为升高的主要
#212121f5,并相对于#181818具有高度突出(不是平坦的同一表面)。 - 灯光合成器渲染为一个不间断的实体表面,没有上下文 轨道或独立顶立面。
- 模型芯片显示当前模型ID;它的菜单仅包含可运行的 provider/model 选择和 Agent。
- 占位符和批准芯片在浅色和深色板上仍然清晰可见。
US-UI-39 主标记 + 英雄标题光学
- 空置 PI-Desktop 标记可见(并非几乎不可见);笔划密度保持可读,没有装饰性重影效果。
- 项目的空主标题使用可读的项目标签范围(短基本名称可能显示为
PI-Desktop以实现光学奇偶校验)。
US-UI-40 主页内容宽度与 rem root
- 在 1200×690 光空的家里,输入框板外部宽度为〜744–760px(不是〜640px)。
- 主页建议网格与输入框板跨越相同的内容列。
US-UI-41 黑暗英雄 + 夜间框可读性
- 深色空家庭英雄标题墨水是浅色深色(
--ds-text-primary/接近白色),而不是硬编码的#1a1c1f。 - 夜间输入框板位于主
#181818上,主#212121f5升高,高度突出;浅色主题不强行给夜盘填充。
US-UI-42 Light范围会话创建chrome
- 在轻型侧边栏上,会话和项目范围的创建控件仍然存在 仅图标,带有语义悬停清洗;不会呈现独立的新任务行。
US-UI-43 空本垒板 Y + 夜间高架-初级
- 以 ~1200×690 浅色主题打开空荡荡的家。
- Composer 板底部对齐且内容驱动:空行或一行 草稿使用紧凑的外壳,而不是固定的最小 ~140px;表面 保持均匀的固体,没有装饰性的洗涤。
- 切换深色主题:夜盘为高架-主色(
#212121f5/ 灰色-800 96%)具有相同的约束标高并且没有内部梯度。
US-UI-44 设置紧凑目录+合并部分
- 打开设置灯光主题~1200×690。
- 全页外壳:
#f3f3f3上的轨道 ~260px,主#fff;搜索药丸位于导轨顶部;返回应用程序 固定在导轨底部,并与主外壳侧边栏底部图标行垂直居中于同一条线;带有图标的常规 活动药丸。 - 铁路订单完全是常规、AI、快捷键、指令、模型、技能、MCP、子智能体、导入、 项目和信息;其下显示偏好、智能体、工作区和系统四个柔和标题,组间没有 分割线、重复目的地或占位符行。选中页面仍保留描述性标题,例如“模型配置” 和“项目归档”。
- 常规内容:大标题和一张带有工作信息的外观卡 system/light/dark 控件。全局AI持有权限、默认项和命令 Shell 行; 上下文管理没有设置卡。快捷键包含键盘快捷键卡。 文件打开目标、语言 覆盖、菜单栏行为和底部面板行为不存在,直到 存在主机支持的实现。
- 模型配置包含提供商工作室英雄、默认模型选择器,以及带有添加提供商 对话框的基于卡的提供商管理。
- 插件 load/enable/disable/uninstall 仍然可以从应用程序 shell 中使用 独立的扩展目的地;其“市场”选项卡同时拥有官方/镜像/自定义目录来源 选择器,因此设置中不再重复扩展目的地。
- 深色:铁路
#000、主线#181818、卡高架#212121。
US-UI-38 Composer 工作区上下文被省略
- 在空置的家、项目家以及开始转录后,输入框 从不渲染项目/本地/分支胶囊。
- 工作区身份通过主页英雄或侧边栏仍然可见 而不是在提示符上方重复。
US-UI-37 空草稿行 + 调整大小
- 空的输入框提示行不显示领先品牌图标并保留可见 占位符墨水(不是空白的 white/night 孔)。
- 自动调整大小永远不会折叠低于 ~28px 的空文本区域。
- 禁用的发送控制是一个亮着的灰色实心芯片(
#8e8e90),完全不透明,带有白色箭头。 - 暗夜板保持升高的主
#212121f5,并在#181818上突出可读的升高。
US-UI-31b(已取代)
- 被 US-UI-31 家用空垂直堆栈 (D111) 取代。
US-UI-46 Home-with-project Composer chrome
- 在空荡荡的家里打开一个项目(没有成绩单)。
- 预计板上没有连接工作区控件;没有遗留草案 标记,占位符使用 PI-Desktop 副本。
- 模型芯片显示当前模型ID;页脚使用循环本地用户 字形、两行自定义/本地配置文件标识、披露 V 形符号和 单独的“帮助”→“设置信息”控件。
US-UI-47 项目指数平价
- 打开设置 → 项目存档。
- 期望设置部分标题、搜索药丸、添加项目按钮和 完整的持久项目列表,包括存档行。
- 行扩展以显示最近的任务;激活项目或其会话之一 使用
setProject而不通过对话框重新选择并保留 session/workspace 上下文同步。侧边栏 pin、archive 和 close 元数据保留在渲染器本地; 只有显式的行菜单“删除项目”操作才会删除持久的项目存档行,并且会一并删除 该项目的会话(ADR 0251)。
US-UI-48 Home starter 字形和标签缺失 (D206)
- 在空置的房屋中,没有开发人员入门图标板、title/description 或 入门卡字形以浅色或深色主题呈现。
- 英雄、可选的入门清单和底层输入框仍然是唯一的 空置的任务输入表面。
US-UI-49 范围侧边栏行镀铬
- 将鼠标悬停或选择项目或临时会话行。
- 预期标题行具有 active/hover 背景且紧凑 pin/archive 的溢出操作(不是最近聚合)。
- 多个保留的项目组可能会同时可见;剩余会话数 在精确路径组下,而封闭项目会议仍然可用 设置 → 项目存档。
US-UI-50 目的地标题比例
- 打开设置 → 项目存档和插件。
- 预计大节标题 (~28px) 与 Codex destination/index 页面一致。
- 黑暗家庭范围内的会话创建控件保持安静的图标操作,无需 独立的新任务行。
US-UI-52 设置金铬指标 (D070)
- 打开“设置”灯“基本”,分辨率约为 1200×690。
- 预计约 275 像素
#f4f4f4导轨,单个活性基础药丸,后退 + 搜索。 - 期望工作主题选择器没有惰性切换或开放目标行。
- 期待权限+基础+外观提升卡; Agent, 导入和信息仍然是唯一的其他目的地。
- 在 1040 像素、1200 像素和 1600 像素宽度之间调整大小;内容卡填充 每种尺寸都可用右窗格,无需更换导轨或引入 水平滚动。
US-UI-53 设置暗壳 (D070)
- 深色主题设置基础:黑色导轨、提升卡、蓝色开关、返回聊天。
- 行描述使用主题感知辅助文本并保持清晰可读
#212121卡面;它们不能退回到低对比度的柔和墨水。
US-UI-54 Toast 变体 + 生命周期 (D085)
- 触发成功(保存提供程序)、错误(使用无效密钥运行)和 来自测试插件的信息 toast。
- 预计顶部中心堆叠在高架板上,带有有色变体图标(绿色✓/红色!/信息)和每张卡的X消除;最新的牌进入顶部中心的锚点并将旧牌推下。
- Success/info 自动关闭约 4 秒,错误持续约 8 秒;将鼠标悬停在卡片上会暂停倒计时; X 立即将其删除。
- 当堆栈与无框标题栏带重叠时,悬停仍然会暂停 倒计时和每个 X 仍然可点击,而不是拖动窗口。
- 重复相同的操作会重新启动现有的Toast,而不是堆叠重复的Toast;堆栈永远不会超过 4。
- Toast 消息文本可选择:在卡片上拖动只高亮其消息文本,
Cmd/Ctrl+C可复制; 卡片图标与 X 关闭控件不暴露选择。 - 捕获装备场景
pi-toasts-light/pi-toasts-dark显示两个主题中的堆栈。
US-UI-55 Composer 文本区域增长 (D089)
- 在家庭输入框和线程对接输入框中,空草稿或单行草稿 显示一根可见的文本行。
- 输入或粘贴两到七条视线;文本区域随着 无需手动调整大小即可包装内容。
- 添加第八条视线;文本区域保持在七行可见的位置 内部滚动而不是进一步发展输入框。
- 删除回一行或提交草稿;文本区域收缩到它的 默认一行。
- 打开大型工作区并让
@文件菜单处于筛选状态,持续输入;插入符号 始终跟得上输入、没有可见停顿,菜单的行顺序保持不变。增长到七行、 超过第七行后内部滚动,以及 delete/submit 后的收缩都与上述一致(D264)。
US-UI-56 Codex 转录工具活动
- 在浅色和深色主题中,工具调用使用透明紧凑的活动行, 不是高架卡片或彩色成功轨道。
- 连续调用出现在一个默认折叠的处理组内。其 活动标头显示
Processing · {elapsed}且其完整标头显示Processed for {elapsed},加上本地步数。 - 该行显示语义 15–16px 图标、progressive/past-tense 操作、 椭圆形等宽参数提示、安静披露 V 形和本地化 running/error/denied 状态。
- Fork 系列工具显示 GitFork 分支图标而不是通用工具 字形。
- 展开已完成的调用会在输入之前显示输出。两个部分都是 可独立复制并带有内部滚动功能。
- 重新加载会话会保留操作标签和参数提示,而不是 将该行降级为通用
Tool。 - 运行一个回合,发出辅助文本,调用多个工具,然后继续 更多辅助文本。在流式传输期间和会话重新加载后,预计会出现一个 整个用户回合的辅助文章,其中包含片段和活动 原始订单,但只有一个尾随 model/usage 行和一个 Copy/Fork/Retry 工具栏。副本按顺序包含所有辅助文本片段。
US-UI-57 多项目侧边栏组
- 打开项目 A 和 B,但不关闭其中任何一个。
- 预计
Sessions标题高于Projects,包含无路径 对话加上新会话和排序操作。拥有五个以上 独立对话,期望有一个五行高的列表,可以滚动到每个 剩余行不再进一步增长。 - 右键单击
Sessions标题或空白独立列表 chrome 并期望 一个单项创建菜单,用于 creates/reuses 无路径临时会话。 - 期望以下
Projects标题保留其新项目文件夹 操作、每个保留项目一个路径键控组以及一个活动状态标记 恰好在一个组上。它的列表消耗剩余的高度并滚动 独立。相邻的项目组被视为紧凑的连续树 无分离卡间距。 - 右键单击
Projects标题或空项目列表镶边并期待 一项创建菜单,打开与文件夹加相同的项目选择器 行动。 - 期望项目和会话列表在侧边栏主体内滚动,而无需 在页脚后面剪裁;侧边栏 Collapse 保留在侧边栏中 标头。当工作面板打开时,预期其唯一的折叠控制位于 会话窗格右上角而不是工作面板内容标题,与 主窗格右边缘的分隔线。
- 通过单击目录标签折叠 A,将其从 V 形区域展开, 然后激活B并返回A。只有A的子行折叠;项目
+并且溢出操作不会切换它;的 活动项目、顶栏路径和脚本一起切换;输入框 仍然不受工作区身份镀铬的影响。 - 关闭 B 并从“设置”→“项目存档”重新打开它。关闭仅删除侧边栏选项卡; 持久的 project/session 行仍然可用。
US-UI-58 侧边栏组织操作
- 打开项目和对话溢出菜单。
- 需要本地化的 Pin/Unpin、Archive/Restore 和(对于对话)删除 具有键盘可访问的菜单语义的操作。
- 从独立的
Sessions标题打开排序菜单,第一个引脚 project/session,并选择每个面向用户的排序模式(最近更新, 创建日期,最早的在前,名称)。固定的行保留在第一位。 - 存档一行,验证默认情况下不存在,启用显示存档,然后 恢复它。成绩单和项目绑定保持不变。
- 传统的
manual首选项加载时不会出现拖动重新排序 可供性。
E2E-PROJECT-delete-removes-project-and-owned-sessions
- 前提条件:三个持久项目 A、B 和 C,每个都至少有一个带转录本的会话;A 已归档并作为 侧边栏选项卡保留;B 是活动工作区;C 是一个已存储双文件夹项目组的根目录。
- 步骤:打开设置 → 项目存档,打开 A 的行菜单,选择删除项目,并在对话框中确认。然后 从侧边栏项目菜单对正处于活动工作区的 B 重复同一操作:第一次点击只是武装该项,只有第二次 点击才会移除 B。接着对 C 尝试同一操作,然后对一个宿主已不再知晓的路径尝试,最后在 D 的 某个任务仍在运行时尝试删除 D。
- 预期:对话框会指明项目名称,说明该项目及其会话与转录本会被永久移除,并说明磁盘上的 文件夹不会被删除;确认之前不会移除任何内容,被武装后放置不管的项会自行解除武装、不会移除 任何东西。确认后,持久项目行、该项目的会话、其转录本、scratch 和 review 文件以及该项目的 持久记忆均已消失,而磁盘上的文件夹保持原样。被删除的项目会立即从设置 → 项目存档和侧边栏 中消失,重新加载后依然如此:没有保留的选项卡、没有最近项目条目、没有由会话推导的行,也没有 残留的 pin、archive 或 order 偏好。其他所有项目的会话与转录本不受影响。当被删除的项目曾是 活动工作区时,工作区回退到另一个已打开的项目 或 Temporary,且下次启动不会重新打开已删除的路径。磁盘上文件夹已被移动或删除的项目仍可 移除。删除 C 会被拒绝并给出提示消息,该组保持不变;宿主已无持久行的路径仍会从项目存档与 侧边栏中移除,不会报出缺少项目的错误。当 D 的任务仍在运行时删除 D 会打开确认对话框,而不是给出 一条随 toast 消失的警告;对话框会指明正在运行的会话,其确认按钮表述为停止这些任务,取消不会移除 任何内容,确认则先停止这些轮次,然后再删除 D(见 E2E-PROJECT-delete-running-sessions-are-named-and-stopped)。
- 链接规格:
03-runtime/06-host-rpc-protocol.md§项目、03-runtime/04-data-storage.md、04-ux/08-component-spec.md§3.9、ADR 0251 - 验收:D(工作区)、F(持久化)、品质
- 里程碑:M6+
- 状态:部分自动化 ——
pnpm test:e2e覆盖宿主契约(项目行、所属会话、磁盘上的转录本 与 scratch、项目记忆、对其他项目的隔离、运行中任务的拒绝、项目组根的拒绝,以及未知路径的 幂等),pnpm test:e2e:boot通过沙箱 preload 往返pi-desktop/project/remove;设置 → 项目存档 → 对话框 → 侧边栏这条完整旅程仍为草稿
E2E-PROJECT-delete-running-sessions-are-named-and-stopped
- 前提条件:一个持久项目 D,其中一个会话的轮次仍在流式输出,并且该项目同时可从侧边栏项目 菜单与设置 → 项目存档中访问。
- 步骤:从这两个界面分别打开 D 的行菜单,在不停止该轮次的情况下选择删除项目。预期出现带 运行中会话行与“停止任务并删除”确认按钮的确认对话框;按取消,预期不发生任何变化。再次打开 对话框并确认。
- 预期:菜单不会用一条纯警告替代对话框,因此在任务运行时该操作始终可达。对话框仍会指明项目 名称、会话数量与不会被删除的文件夹;当有轮次在运行时,它还会指明还有多少个会话正在运行,其确认 按钮表述为停止这些任务,且该行仅在渲染时才加入对话框的
aria-describedby。取消不会删除任何 内容,轮次继续流式输出。确认会且仅会停止列出的这些会话,之后才移除项目、其会话、其转录本及其 持久记忆,磁盘上的文件夹保持原样。若在对话框打开与确认之间启动了新的轮次,宿主仍会拒绝,对话框 会用project.deleteRunningBlocked报告该拒绝且不移除任何内容。 - 链接规格:
03-runtime/06-host-rpc-protocol.md§项目、04-ux/08-component-spec.md§3.9、ADR 0251、D421、D431 - 验收:D(工作区)、品质
- 里程碑:M6+
- 状态:部分自动化 ——
apps/desktop/test/project-delete.test.mjs固定了两个菜单在传入项目 当前运行会话 id 的情况下都能到达对话框、对话框的运行中会话行与“停止任务并删除”标签、abort 循环 先于deleteProject执行、CONFLICT兜底路径,以及所有已发布语言包中的新文案;端到端旅程仍为 草稿
E2E-SESSION-two-click-delete-arms-first
- 前提条件:一个包含一个空闲会话和一个运行中会话的项目,二者都可从侧边栏会话菜单、侧边栏 项目菜单以及项目索引到达。
- 步骤:打开空闲会话的会话菜单,按一次删除,并让该项保持武装直到武装超时,然后再按一次以 确认移除。对来自侧边栏菜单与项目索引的项目行重复该操作。
- 预期:第一次按下不会移除任何东西,并把该项标签改为
nav.deleteTaskConfirm/project.deleteMenuConfirm("Delete?" / "确认删除?")且带data-armed="true";菜单保持 打开,点击外部、按 Escape 或武装超时都会解除武装且不移除任何内容。只有第二次按下才会移除 该会话及其转录本和该行,也只有对项目行的第二次按下才会移除空闲项目。会话与项目永远不会共用 一次武装。删除仍有运行中轮次的项目时,仍会打开指明这些会话并停止它们的对话框(见 E2E-PROJECT-delete-running-sessions-are-named-and-stopped)。 - 链接规格:
04-ux/09-interaction-patterns.md§1.6、D421、D431、D441 - 验收:品质
- 里程碑:M6+
- 状态:部分自动化 ——
apps/desktop/test/two-step-delete.test.mjs固定了共享的武装与它的 超时、所有已发布语言包中的两个标签,以及第一次按下只做武装;端到端旅程仍为草稿
US-UI-59 基于会话的后台工具
- 在项目 A 中启动可见轮次,在项目 B 运行时切换到项目 B,并且 检查两个侧边栏状态指示器。
- 期望轮到 A 在后台继续,B 的 composer/context 显示 仅 B,并且来自 A 的工具 output/artifacts 保持扎根于 A,无需 打开或激活 B 上的工作面板选项卡。
- 打开临时会话并调用工作区所需的工具;期望 正常的
WORKSPACE_REQUIRED结果而不是从 B 继承。
US-UI-60 WorkBuddy 成绩单板 (D101)
- 打开浅色和深色主题的混合成绩单。
- 期望右对齐紧凑的用户板,透明的全角助手 散文,更密集的行间距,以及每轮下仅悬停的复制芯片。
- 当助理回答时,请注意左口音规则,而不是 一个盒装框架。
US-UI-60b 助理 Markdown 散文重新设计
- 打开带有标题、表格、代码栏、块引用和任务列表的助理答案。
- 预计两个主题中都会有精致的
.prose-chat层次结构和嵌入代码镶边。 - 扩展思维标记并确认它在视觉上保持从属于答案。
US-UI-60c 紧凑的助理错误卡
- 在浅色和深色主题中触发可重试的提供商/模型失败。
- 期望助理错误使用带细错误轨的克制内联表面;本地化摘要、稳定错误码和详情披露共用紧凑标头,卡片不渲染第二个底部操作行。
- 对终态
PROVIDER_RATE_LIMITED(包括 HTTP 429),期望 TurnOutcomeCard 只显示一个本地化的“继续”操作,不显示“重新生成”。 - 点击“继续”,期望应用把本地化继续指令(
Continue the user's unfinished task./继续用户未完成的任务)追加到同一会话并开始新一轮,不截断失败轮次。
US-UI-61 助理上下文摘要 + 重试(D103、D184、D244、D347)
- 完成报告使用情况的助理回合。
- 答案下方会有模型徽章;紧凑的上下文检查器出现在输入框工具栏、模型选择器 左侧。触发器显示剩余容量圆环和百分比;点击或键盘激活后显示剩余令牌 + 百分比、used/window 计数、无卡片的本轮/速度数值、一条精确模型用量摘要, 以及一条显示工具种类、调用次数和估算令牌的聚合工具摘要。默认不显示逐 工具行、进度条、徽章、解释性文案或内部分隔线。
- 将鼠标悬停在触发器上不会打开面板;再次激活、点击外部或 Escape 会关闭。
- 面板打开时滚动或调整大小;期望 body 级覆盖层翻转、夹紧并保持完全可见, 而不是被输入框或记录滚动容器剪切。
- 将鼠标悬停在操作行并单击“重试”;最近的前面的用户提示会重新发送。
US-UI-62 就地再生 (D105)
- 在多轮成绩单上,重新生成较早的助理答案。
- 期望后面的轮次消失并且所选用户提示重新运行 放置,而不堆叠提示的第二个副本。
US-UI-63 重新生成历史寻呼机 (D109)
- 重新生成助手答案两次。
- 每次重试后,将鼠标悬停或聚焦于根用户气泡并期待其操作 工具栏以公开
1/N寻呼机以恢复早期变体。
US-UI-64 空荡荡的家,没有输入框重叠 (D111/D204/D206)
- 在〜1200×690和较低的高度(〜900×640)打开空的家。
- 在可滚动内容中期待英雄和可选的入门清单 区域,主输入框明显保留在底部,没有启动器 卡片。
- 短窗口滚动内容区域而不是堆叠输入框 检查表;当清单不存在时,不存在空的垫片。
US-UI-65 持久通知收件箱 (D117/D130)
- 验证当前焦点完成是否使收件箱保持不变,然后 通过 background/unfocused 已完成和失败的任务行填充它, 包括一个很长的会议标题。检查展开的侧边栏页脚并 light/dark 主题中的弹出窗口默认宽度和窄支持宽度。
- 预计没有标题栏响铃,在前帮助位置有一个稳定的 32px 页脚响铃, 不重叠的
1–99/99+徽章, 密集的 360 像素或更窄的列表、本地化的 kind/session/time/error 内容,以及 独特的 text/icon/unread-dot 语义,无需嵌套卡片或剪切文本。 - 切换All/Unread;使用 Tab 键、箭头键、Home/End、Enter/Space、Escape 和 外部点击。焦点顺序保持可预测,行激活打开 正确的会话,然后 Escape 将焦点恢复到铃声上。
- 标记所有已读并清除公开图标 tooltips/accessible 名称、禁用和 空状态仍然是可以理解的,并且减少运动模式改变了 立即弹出,无需抑制焦点或未读状态。
US-UI-66 应用程序更新通知布局
- 在与停靠的输入框的对话中可见,练习手册
available、应用内downloading和downloaded在灯光下更新灯具 以及默认和最小支持窗口大小的深色主题。种植 输入框草稿到其最大可见高度。 - 预计主窗格标题栏下方会出现一个紧凑的更新通知 右上角的安全区域。它永远不会与输入框相交,包括当 草案增加,并且它没有覆盖开放的工作面板。
- 期待 icon/title/message 层次结构的稳定更新,确定进度
downloading,适用的查看版本或重新启动以更新操作,以及 24px 使用可访问的名称解除控件。取消一个状态阶段 不会抑制同一版本的后续阶段。 - 当灯具包含
releaseNotes时,预计会在下面出现“新增功能”部分 横幅和设置 → 信息更新行上的状态消息,使用 产品 UI 区域设置(EN 或 zh-CN)。没有注释的夹具省略了 部分。切换语言会重新解析相同版本的注释,而无需 新支票。
US-UI-67 独特的侧边栏任务状态指示器 (D135)
- 在浅色和深色主题中,在会话 A 进行时保持会话 B 处于选中状态 通过正在进行、已完成、新的正在进行的转向、失败和中止 州。启用减少运动后重复并检查键盘焦点。
- 预计 A 在进行过程中会显示橙色呼吸点,绿色勾号 完成,失败时出现红色圆圈警报。开始新回合清除 A 较早的终止标记;中止不会留下已完成或失败的标记。
- 预计选定的空闲 B 将显示静态重音蓝色轮廓环和活动环 行背景。如果选择的 B 开始工作,其橙色的进行中点将显示 优先,直到回合结束;其最新的最终结果仍然隐藏 选择时位于所选环后面。
- 每个指标都公开本地化的“进行中”/“已选择”/“已完成”/“失败” 通过其可访问的名称和工具提示来显示文本。减少运动使橙色 点静态而不改变其颜色或含义。行高、标题截断、 图钉图标、悬停操作和对焦环在两个主题中保持稳定。
- 打开带有已完成或失败标记的对话并期望该终端 当其持久任务通知被读取时,标记为立即清除。 刷新通知并重新启动应用程序;认可的商标不得 返回。标记为从收件箱中读取的终端通知同样会产生 无侧边栏端子标记。
US-UI-68 会话范围的内联权限和工件 (D138/D142)
- 同时运行两个会话,并在 B 到达工具时保持 A 可见 批准请求。检查默认宽度和窄宽度的 light/dark 主题。
- 没有背景、模态、page/session 开关、工作面板隐藏、转录 A 中的替换或输入框焦点更改。B 保留其挂起状态。
- 明确打开 B 并期望在 B 最新的之后有一张内联权限卡 活动,具有可读的风险、参数、工作区、倒计时和包装操作 控制。切换回来可以保留绝对期限。
- 将A和B一起挂起,各自独立解决,并且都不确认 操作删除或更改另一张卡。
- 解析A的Write/Edit权限,完成前切换到B。预计不会 B 中的瞬态检查面板且无 panel/window 闪烁;返回A恢复 之前的面板选择,其转录中有内联审阅卡——这次编辑没有打开任何标签页—— 而 B 的选项卡和浏览器资源保持不变。
US-UI-69 侧边栏类型平衡 (D144/D161)
- 在默认和最小值下打开浅色和深色主题的扩展侧边栏 支持至少一个会话、一个项目组和本地的宽度 个人资料页脚可见。
- 期望插件、页脚配置文件名称和配置文件菜单操作呈现在 机身镀铬尺寸 (
--text-base/ 14px)。 - 预计 session/thread 标题、project/group 标题和空状态副本位于
--text-md/ 13px,带有大写部分标签 (SESSIONS/PROJECTS) 位于--text-sm/ 12px — 主列表内容永远不会低于--text-md。 - 确认行间距保持紧凑(约 28–32 像素),标题仍然截断干净, 折叠的图标栏控件无需回流外壳即可保持清晰。
US-UI-70 禁用可编辑字段的文本更正 (D145)
- 打开空的主页,停靠的成绩单,设置搜索,插件市场搜索, 项目存档搜索、全局搜索(现在包括命令)、提供商模型组合、 消息编辑文本区域,以及工作面板浏览器 URL 栏(浅色和深色)。
- 期望每个文本 Write/Edit/panel/window 都暴露
spellcheck="false"(ReactspellCheck={false}) 加上autocorrect="off"和autocapitalize="off"。 - 输入类似代码的标记、路径、模型时不要出现红色拼写下划线 ID 或 URL;复选框和非文本控件保持不变。
US-UI-71 Composer 运行时芯片下降器 (D150)
- 打开空的 home 和一个带有包含下降器的模型 ID 的停靠线程 (例如
gpt、gemini或任何具有g/y/p/q/j的 ID)。 - 检查 Agent/Plan/Goal、思考(如果存在)、权限模式和 明暗模型芯片。
- 期望每个芯片标签都显示完整的字形墨水 —
gpt/gemini/g的底部则不然 被 28px 胶囊剪切——而长模型 ID 仍然水平椭圆。 - 链接规格:
04-ux/07-ui-design-system.md§8.2、04-ux/08-component-spec.md§11.5、决策日志 D150 - 里程碑:M5
- 状态:部分自动化(渲染器源测试:芯片行高+无前导-无)
US-UI-72 受 Apple 启发的全局角落层次结构 (D210)
- 打开空的家,填充的成绩单,设置,插件,项目 默认支持的桌面大小的存档、菜单和对话框 和黑暗主题。
- 预计固定角点将遵循全局 4/6/8/10/12/14/16/18/20/24px 梯子,视觉上更大或更高的表面接收更大的 半径。
- 标准紧凑型和中型按钮和字段保持圆角矩形, 不是胶囊。药丸、分段选择、状态标签、进度轨迹、 开关、等宽圆形图标控件和点保留其明确的 胶囊或圆形。
- 当一个圆形的孩子坐在一个圆形的父角上时,期望半径 与中间的插图同心阅读。全宽侧边栏, 标题栏和工作面板边缘保持方形而不是浮动 牌。
- 调整到支持的最小窗口大小并检查每个窗口附近的 menus/dialogs 边缘。圆形表面不得剪切文本、对焦环、操作或可滚动内容 内容。
- 链接规格:
04-ux/07-ui-design-system.md§6.2、ADR 0071 - 里程碑:M5
- 状态:部分自动化(radius 令牌和共享控制源测试)
US-UI-73 Composer 模式选择器具有稳定的宽度
- 以英语和中文打开空的主页和停靠的线程。
- 通过Agent/Plan/Goal多次切换Composer模式芯片。
- 期望模式芯片为最长的内置保持一个固定的宽度尺寸 标签(英文“Agent”/zh-CN“智能体”);相邻的思维和 权限控件、发送按钮和 Composer shell 不会移动或调整大小。
- 链接规格:
04-ux/08-component-spec.md§11.3 - 里程碑:M5
- 状态:部分自动化(渲染器 style/source 合约)
E2E-123:asktool 收集多个答案并返回跳过的占位符
- 先决条件:Agent、Plan 或 Goal 模式;已配置的提供商;一个 与活动转录本的会话。
- 步骤:1) 要求客服人员致电
asktool,并提出单选问题, 一个多选问题,每个问题都有一个选项列表。 2)确认每张卡 显示固定的自定义输入选择。 3)回答第一个问题,点击 接下来,在多选问题上选择两个答案。 4)跳过决赛 问题无需输入文字。 5) 检查完成的工具行和 下一个模型响应。 - 预期:一次可见一个问题;小指标显示 输入框批准区域中已回答、当前和跳过的状态 Plan 和 Goal 批准使用的停靠位置相同。请求没有 倒计时。工具输出排序为
question:answer,使用、多个答案之间和问题之间的\n---\n之间,并保持question:用于跳过的问题。拒绝所有产生空 每个问题的占位符,仍然完成工具调用。 - 链接规格:
03-runtime/17-asktool-questions.md,04-ux/11-asktool-question-card.md、ADR 0077 - 验收:E(交互式工具输出)、C(内联卡)
- 里程碑:M5
- 状态:草案(单位覆盖范围有效;桌面旅程待定)
E2E-124:最小化隐藏跨平台托盘中的窗口
- 先决条件:在 macOS、Windows 和 Linux 上构建桌面应用程序;英语 和 zh-CN 语言环境可用;正常的主窗口打开。
- 步骤:1)从平台的窗口控制最小化(macOS流量 光或 Windows/Linux 渲染器控制)。 2) 确认主窗口是 当应用程序进程保持驻留时隐藏。 3) 单击或双击 托盘图标并确认同一窗口已恢复并聚焦。 4) 打开 托盘菜单并选择“显示”,然后重复“退出”。 5) 在 zh-CN 中重复并 在窗口隐藏时调用 macOS 应用程序激活。
- 预期:所有最小化路径都隐藏到一个托盘图标,而不是退出或 留下任务栏最小化的窗口。 Show/click/double-click/app 激活 恢复现有窗口;本地化菜单包含 Show PI-Desktop 并退出 PI-Desktop。 Quit 运行正常的关闭顺序,并且不留下任何内容 孤立主机、sidecar 或托盘进程。关闭窗口仍然是明确的 放弃行动。
- 链接规格:
03-runtime/07-process-model.md,04-ux/09-interaction-patterns.md、08-meta/decisions-log.md(D216)、 ADR 0078 - 验收:A(应用程序生命周期),质量
- 里程碑:M6+
- 状态:Unit/source-contract 已覆盖;原生跨平台托盘之旅 草稿(除非明确要求,否则不要运行本地 E2E)
E2E-150:再次启动会呈现正在运行的应用,而不是新开一个
- 先决条件:在 macOS、Windows 和 Linux 上构建并使用默认数据目录安装桌面 应用;已有一个实例在运行,侧边栏中至少有一个会话。
- 步骤:1)从平台常规入口再次启动应用(开始菜单/桌面快捷方式、
.AppImage、macOS 上的open -n),同时观察窗口与进程列表。2)把窗口最小化 到托盘后再次启动。3)在关闭行为为tray的 Windows/Linux 上关闭窗口后再次 启动。4)在应用运行期间,用指向空目录的PI_DESKTOP_DATA_DIR启动一个构建。 5)在正式版仍在运行时启动一个开发构建(dev脚本,不设PI_DESKTOP_DATA_DIR)。6)退出应用,确认没有残留进程,然后再启动一次。 - 预期:步骤 1–3 不会创建第二个窗口、托盘图标、host-core、agent sidecar 或 日志文件:已有窗口被恢复并聚焦,重复的进程退出,运行中实例的会话列表、进行中 的轮次和
pi.sqlite都不受影响。步骤 4 作为面向自有数据目录的独立实例正常 启动。步骤 5 同样正常启动:开发构建使用自己的userData与~/.pi-desktop-dev,既不等待也不打扰正在运行的正式版。步骤 6 干净地启动单个 实例,证明锁在退出时释放,不会留下过期阻塞。 - 链接规格:
03-runtime/07-process-model.md、08-meta/decisions-log.md(D236、D599、D002)、ADR 0094 - 验收:A(应用程序生命周期),质量
- 里程碑:M6+
- 状态:Unit/source-contract 已覆盖;原生跨平台再次启动之旅 草稿(除非明确要求,否则不要运行本地 E2E)
E2E-125:完整的双语 VitePress 文档仍然可用
- 先决条件:已安装文档依赖项并提供 VitePress 预览版 服务器正在从存储库运行。
- 步骤:1)以1440×900打开
/并验证英文登陆页面, 系统地图、按意图阅读的旅程、参考书架、全局搜索、 Guide/Specs/ADRs 导航和语言选择器。 2) 切换到/zh-CN/并验证翻译后的英雄、镜像主题图、中文规范 链接。 3)打开/spec/03-runtime/01-ipc-protocol,切换语言, 验证/zh-CN/spec/03-runtime/01-ipc-protocol是否包含翻译的散文, 保留的代码标识符,以及返回英文源的链接。 4) 搜索 在每个区域设置中并打开匹配结果。 5)重复主页和长 明暗模式下的桌面规格为 390×844。 - 预期:两个语言环境入口点和每个 English/Chinese 规范 对渲染没有损坏的链接或页面级水平溢出。登陆 阅读栏在其可用布局中视觉上居中;的 移动英雄在系统视觉之前呈现文本。搜索返回本地 结果。移动导航打开和关闭不会移动或遮挡 页面。代码块和表格通过包含的滚动保持可读, 主题对比清晰,每个中文规范都标识英文 页面作为其规范来源。直接刷新
/zh-CN/spec/README,/zh-CN/spec/03-runtime/01-ipc-protocol、/zh-CN/adr/、/spec/README和 Vercel 上的/adr/README;每条路线都通过记录的解析cleanUrls配置而不是返回 404。 - 链接规格:
02-architecture/04-documentation-site.md、ADR 0079 - 验收:质量、文档可发现性、响应式布局
- 里程碑:M6+
- 状态:浏览器渲染的 desktop/mobile 验证已获得授权 本文档重新设计;远程部署刷新检查仍处于草稿状态。
E2E-126:外观卡片选择全局界面字体
前置条件:应用在 macOS 上运行,系统装有与内置令牌栈不同的字体 (例如 PingFang SC);使用干净的
~/.pi-desktop配置。步骤:
- 打开设置 → 基础,确认外观卡片在主题与语言下方显示一个字体行, 触发器标签为「跟随系统」。
- 打开字体选择器,确认只列出跟随系统与系统已安装字体—— 没有内置开源字体,任何一行都没有许可证角标; 确认搜索框可过滤字体系列,当前选择显示选中徽章。
- 选择某个系统已安装字体(例如 PingFang SC),确认触发器标签与整个界面 无需重载即以该字体渲染,中文文本通过 CJK 回退层正常显示。
- 重新打开选择器确认该字体仍为选中状态,然后选择跟随系统, 确认界面立即恢复为内置令牌栈。
- 重启应用并重新打开设置,确认默认字体仍然生效 (覆盖已被清除,持久化为空的
AppSettings.fontFamily)。 - 在保存过内置字体栈的配置上(例如
"Geist", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif), 确认字体行把该字体显示在「已保存」分组,应用没有加载任何自带字体文件, 且界面穿过系统 CJK 回退层渲染。
预期:字体行为可搜索的选择器,触发器以该字体的字样预览当前字体; 选项只有跟随系统与通过
pi-desktop/app/systemFonts(缓存 60 秒、排除隐藏.前缀字体)枚举的系统已安装字体;应用不再自带字体文件, 因此没有内置分组、也没有许可证角标,不再匹配任何选项的已保存字体栈 仍排在「已保存」分组首位;选择结果以 CSS 字体栈持久化到AppSettings.fontFamily并实时覆盖--font-sans; 中文文本通过纯系统 CJK 回退层保持可读;跟随系统清除覆盖。关联规格:
04-ux/06-settings-ia.md、04-ux/07-ui-design-system.md、03-runtime/01-ipc-protocol.md、ADR 0083、ADR 0298验收:A(核心 shell)、H(本地化)
里程碑:M5+
状态:已记录
E2E-193:外观卡片设置全局文字缩放
- 前置条件:干净的
~/.pi-desktop配置,并有一条能看到会话内容、输入框和侧栏的对话。 - 步骤:
- 打开设置 → 常规,确认外观卡片在字体行下方显示字体大小,「大杯」选中,滑杆为 100%。
- 选择「超大杯」。确认会话、输入框、设置标签、侧栏会话标题和 Lucide 图标无需重载即按相对阶一起变大,控件显示 115%。
- 把滑杆拖到 125%。确认「超超大杯」被选中,全部
--text-*表面和图标继续变大。确认界面没有 px 输入。 - 使用放大,再重置缩放。确认窗口缩放仍会放大 chrome,重置后文字比例仍为 125%。
- 重启应用,确认 125% 仍然生效(
AppSettings.fontScale= 1.25)。 - 选择「大杯」。确认整个界面立即回到 100%;重启后默认仍保留。
- 预期:字体大小为星巴克式杯型档位中杯 / 大杯 / 超大杯 / 超超大杯,加上 80%–150%、步进 2.5% 的百分比滑杆。选择持久化为
AppSettings.fontScale(缺失表示 1),并设置--font-scale乘以全部--text-*令牌和共享 Lucide 图标。窗口放大/缩小/重置保持独立。无效值被拒绝或钳制。无协议/存储版本升级。 - 关联规格:
04-ux/06-settings-ia.md、04-ux/07-ui-design-system.md、ADR 0180、D343 - 验收:A(核心 shell)、B(设置)、H(本地化)
- 里程碑:M5+
- 状态:单元覆盖(
packages/shared/src/font-size.test.ts、apps/desktop/test/settings-font-size.test.mjs);完整 UI 旅程为草稿(除非用户明确要求,否则不要本地跑 E2E)
E2E-130:Read 生成的 tag 可被 Edit 直接使用而无需重读
- 先决条件:项目绑定的 Agent 会话具有可写工作区和一个至少 300 行的源文件。 提供商装置可以发出精确的
Edit负载。 - 步骤:
- 不带
offset地Read该文件,记录[path#TAG]头、tag字段以及返回行上的N:前缀。 - 用该
tag发出Edit,其中包含一个PUT N.=M:,正文替换读取窗口内的两行。 - 确认成功结果报告了新的
tag,随后用返回的 tag 发出第二次Edit,执行PUT >$:追加,中间不做任何Read。 - 在某个
offset处读取 200 行窗口,然后使用该窗口读取所得的 tag 编辑窗口内的一行。 - 在磁盘上重新打开文件,与预期内容逐字节比较。
- 不带
- 预期:头部的 tag 是整文件 tag,而不是窗口的 tag,因此窗口读取也能正确锚定; 行号是绝对的,不受
offset影响。两次编辑都应用成功,第二次无需任何重读,并且 每次成功都返回写入后的 tag。磁盘上的文件与预期内容完全一致,原有的行尾与 BOM 状态被保留。 - 链接规格:
03-runtime/18-line-anchored-edit-contract.md§3、§4.2、§5、§6、§9、03-runtime/16-tool-result-limits.md§5、ADR 0087 - 验收:E(工具和权限)
- 里程碑:M5+
- 状态:已记录
E2E-131:针对从未显示过的行的编辑被拒绝,而重试成功
- 先决条件:一个仅读取了某 400 行文件第 1–50 行的会话。第二个装置文件有一行 长度超过 16,384 个字符。
- 步骤:
- 用正确的
tag发出带PUT 300.=301:操作的Edit。 - 检查错误代码,确认消息内联了第 300 与 301 行的当前内容。
- 原样重试完全相同的
Edit负载,包括同一个tag。 - 用正确的 tag 发出跨越 56 个未显示行的
PUT 5.=60:操作,并检查 reveal。 - 原样重试该负载。
Read第二个装置,确认长行被剪辑并计入notice,然后Edit该被剪辑的行。
- 用正确的
- 预期:第 1 步以
EDIT_LINES_UNSEEN失败且文件未变。第 3 步应用成功,因为 完整的 reveal 已把这些行并入会话来源集。第 4 步以在 40 行处截断的 reveal 失败并 提示重新读取该范围,第 5 步再次失败——被截断的 reveal 不并入任何行,因此该闸门 无法用低于上限的分片绕过。第 6 步以EDIT_LINES_UNSEEN失败:被剪辑的行从未显示过。 - 链接规格:
03-runtime/18-line-anchored-edit-contract.md§4.3、§9.1、§11、§12、03-runtime/16-tool-result-limits.md§2、ADR 0087 - 验收:E(工具和权限)、质量
- 里程碑:M5+
- 状态:已记录
E2E-132:间隙插入、删除与多操作负载都针对同一份快照应用
- 先决条件:一个逐行内容已知的已读文件。
- 步骤:
- 在单个
ops负载中组合PUT <1:、PUT >40:、CUT 12.=14与PUT 80.=80:发出一次Edit。 - 将结果与按原始行号独立计算的这四项改动相比较。
- 对同一文件发出
PUT >$:,确认追加落在最后一行之后且只有一个终止换行。 - 发出一个正文行以字面
-开头(写作+- item)的ops负载,以及一个以字面+开头(写作++ item)的负载。 - 发出一个
PUT正文与该范围当前内容完全相同的Edit。
- 在单个
- 预期:每个锚点都索引被 tag 命名的快照,因此没有操作会移动另一个操作,组合结果 等于四项独立改动。
+-与++写出单个前导-与+。第 5 步返回EDIT_NO_CHANGE而不是报告一次“成功写入了什么都没写”,并且不留下审核记录。 - 链接规格:
03-runtime/18-line-anchored-edit-contract.md§7.2、§7.3、§7.4、§8.1、 §9.3、ADR 0087 - 验收:E(工具和权限)
- 里程碑:M5+
- 状态:已记录
E2E-133:块操作解析并回显其跨度,无法解析时拒绝而不猜测
- 先决条件:受支持语法的已读装置(一个带装饰/属性行的函数的 Rust 文件)、一个带 嵌套标题的 Markdown 文件,以及一个语法不在受支持列表中的文件。
- 步骤:
- 在声明上方带有
#[attribute]行的函数的fn行上发出PUT N*:,并检查回显的{anchorLine, start, end, op}。 - 改为锚定在属性行上重复,并比较回显的跨度。
- 在同一开启行上发出
PUT >N*:,确认插入落在该块最后一行之后并处于兄弟缩进。 - 在孤立的
}闭合行上发出CUT N*。 - 对 Markdown 装置的
##标题发出PUT N*:,确认跨度延伸到下一个同级或更高级 标题,而不是下一个更深的标题。 - 在不受支持语言的文件中发出
PUT N*:。 - 在 Rust 装置中引入语法错误,重新读取,然后发出块操作。
- 在声明上方带有
- 预期:第 1 步的跨度从
fn行开始并排除属性;第 2 步的跨度包含两者——这一差异在 模型需要推断之前就已通过回显可见。第 4、6、7 步以EDIT_BLOCK_UNRESOLVED失败, 消息给出纯范围替代方案;两者都不近似跨度。在不受支持的语言中,范围与间隙操作仍然可用。 - 链接规格:
03-runtime/18-line-anchored-edit-contract.md§8.2、§11、§12、ADR 0087 §4 - 验收:E(工具和权限)、质量
- 里程碑:M5+
- 状态:已记录
E2E-134:寄存器在一次调用内以及跨调用之间搬移代码
- 先决条件:同一会话中的两个已读文件。
- 步骤:
- 在一次
Edit中先发出CUT 20.=30再发出无寄存器标签的PUT <5,确认这些行在 文件内完成了搬移。 - 在一次
Edit中发出两个无标签CUT再发出一次无标签粘贴。 - 对第一个文件发出
CUT 40* @fn,然后在另一次Edit调用中对第二个文件发出PUT <10 @fn。 - 对一个从未设置过的寄存器发出
PUT <10 @missing。 - 发出附带正文行的
PUT 10.=12 @fn。 - 在一次未做任何捕获的新
Edit调用中发出无标签的PUT <1。 - 删除源文件,然后在之后的调用中再次粘贴
@fn。
- 在一次
- 预期:第 1 步作为一次搬移应用,没有重复或孤立的行。第 2 步以
EDIT_REGISTER_AMBIGUOUS失败,而不是使用最近一次捕获。第 3 步跨两次调用完成跨文件 搬移,每次调用各有自己的权限闸门、审核记录与工件行。第 4 与第 6 步以EDIT_REGISTER_EMPTY失败——匿名寄存器没有在上一次调用之后存活。第 5 步以EDIT_PARSE_FAILED失败。第 7 步仍然粘贴成功:寄存器持有的是被捕获的内容,而不是 活引用。 - 链接规格:
03-runtime/18-line-anchored-edit-contract.md§7.5、§8.3、§11、§13.2、 ADR 0087 §5 - 验收:E(工具和权限)
- 里程碑:M5+
- 状态:已记录
E2E-135:漂移文件在重映射可证明时恢复,不可证明时失败
- 先决条件:一个已记录 tag 的已读文件。外部进程可以在读取与编辑之间修改该文件。
- 步骤:
- 从会话外部在编辑目标之上插入 10 行无关内容,然后用过时
tag发出原来的Edit。 - 检查成功结果上的警告,确认改动落在移位后的位置,而不是原来的行号。
- 换成修改某个锚点行本身的改动重复。
- 换成在多操作负载的两个锚点之间插入行的改动重复,使这些锚点需要移动不同的偏移量。
- 换成其被捕获的内部行被外部编辑过的
CUT重复。 - 在会话自身已写入该文件两次之后,使用第一次写入的 tag 重复。
- 换成一个重复出现的锚点行重复,其一侧相邻上下文匹配而另一侧不匹配。
- 从会话外部在编辑目标之上插入 10 行无关内容,然后用过时
- 预期:第 1 步以行重映射加外部变更警告应用成功。第 3、4、5、7 步以
EDIT_TAG_MISMATCH与当前内容失败关闭;它们都不写入。第 6 步以会话链警告而不是 外部变更警告应用,因为纠正建议不同。任何恢复路径都不会把被 tag 命名的快照内容写到 实时文件之上。 - 链接规格:
03-runtime/18-line-anchored-edit-contract.md§9、§10、§11、ADR 0087 §6 - 验收:E(工具和权限)、质量
- 里程碑:M5+
- 状态:已记录
E2E-136:过时的 tag 对首尾插入仍然适用
- 先决条件:一个已记录 tag 的已读文件,以及一个外部写入者。
- 步骤:
- 从外部修改文件中部,然后用过时 tag 发出
PUT >$:。 - 用同一个过时 tag 对
PUT <1:重复。 - 换成混合
PUT >$:与带锚点的PUT 50.=50:的负载重复。 - 发出一个
tag格式正确但本会话从未为该路径记录过的Edit。 - 发出一个
tag不是四位十六进制的Edit。
- 从外部修改文件中部,然后用过时 tag 发出
- 预期:第 1 与第 2 步以漂移警告应用,因为这两个锚点都不会被内容漂移移动。第 3 步 不走位置稳定路径:它进入恢复,失败后进入
EDIT_TAG_MISMATCH。第 4 步返回EDIT_TAG_UNKNOWN,第 5 步返回EDIT_TAG_REQUIRED;两者都不被报告为通用的TOOL_FAILED。 - 链接规格:
03-runtime/18-line-anchored-edit-contract.md§9、§11、03-runtime/08-error-codes.md§3.4 - 验收:E(工具和权限)
- 里程碑:M5+
- 状态:已记录
E2E-137:边界修复纠正差一的边缘,并拒绝并列的候选
- 先决条件:一个带嵌套闭合定界符的已读源文件。
- 步骤:
- 发出一个范围包含正文并未重述的一个尾随
}的PUT N.=M:,并检查结果及其警告。 - 发出一个正文重述了紧邻范围之外一行的
PUT N.=M:。 - 构造一个负载,使两个不同的修复文本在最小修复代价上并列。
- 针对一个本来就无法解析的文件发出负载,确认修复不会仅凭解析成功的证据保留某一行。
- 发出一个范围包含正文并未重述的一个尾随
- 预期:第 1 与第 2 步以准确说明修复了什么的警告应用,因此不可能出现静默的结构性 改动。第 3 步返回
EDIT_REPAIR_AMBIGUOUS而不是从中选择;第 4 步不臆造保留。在任何 情况下,文件要么包含警告中描述的修复结果,要么完全未被触碰。 - 链接规格:
03-runtime/18-line-anchored-edit-contract.md§8.4、§11、ADR 0087 - 验收:E(工具和权限)、质量
- 里程碑:M5+
- 状态:已记录
E2E-138:移动、删除与回滚让审核证据保持诚实
- 先决条件:一个 Review 可见的项目绑定会话,以及工作区根内的一个已读文件。
- 步骤:
- 在同一个
ops负载中发出带PUT操作加MV DEST的Edit。 - 检查该工具调用的审核记录与 Review 面板行。
- 回滚该改动,确认源与目标都回到调用前的状态。
- 发出带
REM的Edit,然后回滚。 - 回滚之后,使用会话在回滚前持有的 tag 发出
Edit。 - 对一个不存在、但其基名与 tag 恰好匹配本会话记录过的唯一文件的路径发出
Edit, 并检查警告。 - 在有两个共享该基名与 tag 的候选记录时重复第 6 步。
- 在同一个
- 预期:第 1 步在一次工具调用下记录一次源删除与一次目标创建;第 3 步要么同时恢复 两者,要么都不恢复。第 4 步的回滚恢复被捕获的字节,哈希保护基于完整摘要而不是 16 位 tag。第 5 步失败,而不是针对回滚已替换的内容进行编辑。第 6 步以警告重新绑定到真实 文件,并针对重新绑定后的路径评估写权限闸门;第 7 步拒绝而不是从中挑一个。
- 链接规格:
03-runtime/18-line-anchored-edit-contract.md§9.2、§13.1、03-runtime/03-tools-and-permissions.md§4c、ADR 0043、ADR 0087 - 验收:E(工具和权限)、质量
- 里程碑:M5+
- 状态:已记录
E2E-139:快照来源按会话隔离且有界
- 先决条件:一个具备子代理能力的会话(§5f)、同一工作区上的第二个会话,以及一个 文件数多于快照存储路径上限的装置。
- 步骤:
- 在父会话中读取一个文件,然后让委派代理在不先读取的情况下用父会话的 tag
Edit该文件。 - 在会话 A 中读取一个文件,并从会话 B 发出相同的
Edit负载。 - 读取多于存储所保留数量的不同路径,然后用最初的 tag 编辑最先读取的那个路径。
- 读取同一路径五次,期间内容不断变化,然后使用第一次读取的 tag 进行编辑。
- 读取一个文件,然后在不同
offset上再读取两次未变的同一文件,确认一个 tag 覆盖 全部三个窗口。 - 重启应用,然后用重启之前的 tag 发出
Edit。 - 通过一个会被保存钩子重新格式化的路径写入文件,然后用该写入返回的 tag 进行
Edit。
- 在父会话中读取一个文件,然后让委派代理在不先读取的情况下用父会话的 tag
- 预期:第 1 与第 2 步失败——来源按读取者隔离,任何会话都不会把自己的 tag 交给另一个 会话。第 3、4、6 步以
EDIT_TAG_UNKNOWN与重新读取的指示失败,绝不会以错误写入结束。 第 5 步可以在三个窗口的并集内任意位置应用而无需第四次读取。第 7 步应用成功,因为被 记录的 tag 描述的是实际落盘的字节,而漂移被报告为一行警告而不是整文件差异。 - 链接规格:
03-runtime/18-line-anchored-edit-contract.md§4.2、§4.4、§5.4、§13.5、03-runtime/02-agent-runtime.md§5f、ADR 0087 §3 - 验收:E(工具和权限)、质量
- 里程碑:M5+
- 状态:已记录
E2E-140:可恢复的编辑失败各有一次重试,保护器累计三次后终止
- 先决条件:一个读取过文件的会话,以及能在调用之间让文件在磁盘上漂移的方式。
- 步骤:
- 让文件漂移,然后用现在已过时的 tag 发出锚点无法重映射的
Edit,使其以EDIT_TAG_MISMATCH失败。 - 重新读取,然后发出一次锚定在会话从未显示过的行上的
Edit,使其以EDIT_LINES_UNSEEN与一个被截断的 reveal 失败。 - 在同一路径上发出一次头格式错误的
Edit。 - 再发出一次头格式错误的
Edit。 - 第三次发出头格式错误的
Edit。
- 让文件漂移,然后用现在已过时的 tag 发出锚点无法重映射的
- 预期:第 1 与第 2 步返回各自的代码且不带
terminate提示——每个可恢复代码在该 路径上花掉它唯一的宽限,本轮继续进行,因此代理可以按错误交回的信息行动。第 3 与第 4 步分别计为尝试 1 与 2,仍然不终止。第 5 步终止。在第 5 步之前任意位置插入一次成功的Edit都会重置计数,因此其后的失败又是尝试 1。 - 链接规格:
03-runtime/18-line-anchored-edit-contract.md§9.3、§11、03-runtime/03-tools-and-permissions.md§4d、ADR 0087、ADR 0207 - 验收:E(工具和权限)、质量
- 里程碑:M5+
- 状态:已记录
E2E-141:额度耗尽时本轮以一条可见、可重试的错误行结束
- 先决条件:一个在某条路径上每次
Edit都以不可恢复代码失败的会话。 - 步骤:
- 在一个提示内对同一路径发出三次失败的
Edit调用。 - 在代理循环停止之后观察会话记录。
- 在同一会话中发送一条后续提示。
- 用三次失败的
apply_patchshell 命令代替Edit重复上述过程。
- 在一个提示内对同一路径发出三次失败的
- 预期:第三次调用携带终止提示并停止循环,但本轮不是仅仅完成:会话记录以一条带
MUTATION_RETRY_BUDGET_EXHAUSTED的 assistant 错误行结束,被标记为可重试,指明路径与 下一步动作,并且同一个代码作为错误事件到达。该 turn 被记为失败,而不是完成却没有最终 消息。第 3 步正常进行——守卫的计数器按提示作用域。第 4 步产生同一条行,其details.kind为patch-command。 - 链接规格:
03-runtime/18-line-anchored-edit-contract.md§9.3、03-runtime/03-tools-and-permissions.md§4d、03-runtime/08-error-codes.md§3.3、ADR 0087 - 验收:E(工具和权限)、C(聊天与流)
- 里程碑:M5+
- 状态:已记录
E2E-142:后台委托通过 TaskWait 收敛并遵守权限作用域
- 先决条件:一个绑定项目、权限模式可以在
ask、accept-edits和auto之间切换的 Agent 会话,其提供方的流可以被驱动;五个内置子代理(explorer、code-reviewer、test-runner、fixer、ui-designer)以及一个全局~/.agents/subagents/readonly.md定义。内置定义使用默认的permission: inherit行为。 - 步骤:
- 提示一轮,其中助手在一条消息里发出两次
Task调用 —— 一个explorer查一个方向,另一个explorer查另一个方向 —— 然后不结束本轮,继续自己 的工具调用,最后用TaskWait收敛。 - 确认父级的可见文本在
Task与TaskWait之间持续流出(没有死掉的一 轮)、两条Task行组成一张一次展开的委托卡片,并且TaskWait那行 展示了两份报告。 - 先将会话设为
ask,用一份多文件规格委托给fixer;确认它的Write/Edit、Bash和外部路径调用都会渲染标明fixer的权限卡片。切换 到accept-edits,确认只有工作区内的Write/Edit自动放行。切换到auto, 确认同一委托的Write/Edit、Bash以及外部Glob/Write调用都无需第二张 授权卡即可完成。 - 提示一轮,启动三个委托后以
mode: "any"、minCompleted: 1调用TaskWait;确认它在第一个结算时立即返回,且仍在运行的委托继续运行。 - 提示一轮,启动一个委托后不调用
TaskWait/TaskStop就结束本轮;确认 该委托在运行结束时被停止、其节点读作aborted,并且下一轮的模型上下文 中没有任何委托行。 - 在一轮里提示十次
Task再加一次;确认第十一次以指明 10 个委托上限的 工具错误失败,并且TaskStop会释放一个名额,使第十一个委托得以启动。 - 重新加载会话;确认委托卡片、它的节点和
TaskWait行都持久化并折叠重 绘,并且TaskWait能按 id 重读已结算委托的报告而不重新运行它。 - 把
~/.agents/subagents/readonly.md改成声明permission: auto并重新加载目录; 确认该定义仍然加载但带着一条警告,且它的委托仍在会话的有效模式下裁决 (工作区内的Write依旧弹出权限卡片)。 - 提示一轮,启动一个委托,让
TaskWait超时使节点仍显示 running,再调用TaskStop;确认拓扑节点和TaskStop行都读作stopped而不是running。结束本轮并重新加载会话;确认卡片不再标为工作中,也不再持续计时。
- 提示一轮,其中助手在一条消息里发出两次
- 预期:
Task立即带着delegationId返回,父级继续工作;TaskWait带着每个委托的报告与状态收敛;TaskList/TaskStop驱动生命周期;TaskStop的结果和已结束的一轮都不会留下实时的“子智能体工作中”卡片;内置fixer继承所选的会话权限模式,因此auto也会让明确的外部路径无需重复授权卡, 而ask和accept-edits保留各自的审批边界;项目定义声明的作用域被丢弃; 每会话 10 个运行委托的上限被强制执行;没有委托活过它所属的一轮;重新加载的 会话记录保留其委托拓扑。 - 链接规格:
03-runtime/02-agent-runtime.md§5f/§5f.1/§7.1、03-runtime/03-tools-and-permissions.md§10.2、08-meta/decisions-log.md(D242 修订 D231)、ADR 0089 和 ADR 0100 - 验收:C(对话和直播)、E(工具和权限)、F(坚持)、安全性、质量
- 里程碑:M6+
- 状态:草稿(单元覆盖在
packages/agent-runtime的runtime.test.ts子代理套件与 host-corerpc/mod.rs的委托作用域测试中;桌面端旅程待补)
E2E-160:跨显示器拖动窗口保持放下的位置
- 前置条件:PI-Desktop 在拥有两块并排显示器的机器上打开,最好两块显示器的 工作区域不同(只有一块带菜单栏或任务栏,或分辨率不同)。工作面板关闭和 以确定宽度打开各执行一次。
- 步骤:
- 记下窗口在第一块显示器上的位置,然后用标题栏把它拖到第二块显示器并松开鼠标。
- 确认窗口停在松开的位置:不跳动、不吸附到显示器边缘、也没有继承第一块显示器的 垂直偏移。
- 把窗口拖到横跨两块显示器边界的位置并松开,确认它完整落在某一块显示器的 工作区域内,且尺寸不变。
- 面板打开时重复跨显示器拖动,确认对话宽度保持稳定,保留量按目标显示器的 工作区域重新规划,只有该工作区域窄到无法提供确定宽度时才收缩。
- 把窗口拖回第一块显示器,确认其工作区域允许时会恢复完整保留。
- 让窗口留在第二块显示器上,退出并重新启动。
- 窗口位于第二块显示器时断开该显示器,然后重新连接。
- 预期:每次松开鼠标都让窗口停在用户放下的显示器与位置上。横跨边界的放置会被 规范化进某一块工作区域且不改变尺寸。重新启动时窗口在最后使用的显示器上打开, 而不是最初那一块。移除窗口所在的显示器仍会把它迁移到在线显示器,重新连接后 按 ADR 0122 为宽敞工作区域指定的方式恢复保留。
- 链接规格:
03-runtime/01-ipc-protocol.md、04-ux/09-interaction-patterns.md§8、ADR 0122、ADR 0132 - 验收:F(坚持)、质量
- 里程碑:M6+
- 状态:单元覆盖(
work-panel-window.test.mjs:跨显示器拖动采纳落点、 原显示器重排回归、工作区域规范化);双显示器桌面端旅程与 重启/热插拔分支待补
E2E-166:子代理模型选择
前提条件:Agent 模式;至少一个提供商配置了两个模型绑定;存在可委托的子代理。
步骤:1) 勾选一个模型的自动调度许可,保存并重新打开,重启后确认仍保留。 2) 检查父代理模型摘要与 Task 定义目录。3) 显式选择已启用的模型。 4) 显式选择未启用或不存在的模型。5) 分别对有固定模型和无固定模型的定义 省略
model。6) 使用按需解析启用的新模型。7) 在无覆盖目录时重复当前会话模型。 8) 把未勾选模型固定给一个定义,尝试供另一子代理覆盖使用,再省略参数调用原定义。 9) 撤销已启用模型的许可,在同一空闲会话发起下一回合。预期:自动调度许可持久化。系统模型摘要只列出成功解析且已勾选的模型,不包含 仅供定义固定使用的模型;Task 定义目录展示默认模型并提示省略或重复该键保留默认。 优先级保持 Task.model → 定义固定模型 → 会话模型;未配置或未授权的显式覆盖返回 工具错误,且不向子模型发送请求。省略参数,或重复该定义自己的固定模型键,仍能使用定义自己的未勾选固定模型。 当前会话模型的既有继承例外不变,按需解析仍需 main 检查许可,且不得覆盖固定模型或替换空闲运行时。许可变化会在下一 回合替换空闲运行时,旧缓存不再授予选择权。
链接规格:
03-runtime/02-agent-runtime.md§5f;03-runtime/11-provider-model-system.md§7;08-meta/decisions-log.mdD278; ADR subagent-model-opt-in。验收:C(对话/流)+ B(模型配置)+ E(工具)
里程碑:M6+
状态:部分自动化。
pnpm test:e2e:subagent-models通过真实 NDJSON 驱动构建后 的 sidecar,并使用本地确定性 SSE 模型夹具,已验证跨定义拒绝、自身固定模型回显、固定模型、合法覆盖 优先级、按需授权且不重建运行时、会话继承与跨回合撤销。运行时单测覆盖选择校验,桌面启动测试 验证独立许可、撤销和多账号别名冲突,连接测试检查按需唯一匹配。设置复选框 UI/持久化旅程及外部真实提供商执行仍需手动验证; 此夹具不代表完整原生 UI 旅程已通过。Authorization regression coverage (#841):
pnpm test:e2e:subagent-modelsgrants a model on demand, reuses the runtime, then revokes it without changing the launch catalog. The next prompt must reauthorize and issue no child request. A transcript-restored resume with a colliding model id must use the session binding rather than another definition's private account. Runtime tests also cover own pins/fallbacks, opted-in bindings, visible fallback metadata, live-key reauthorization, and late RPC responses crossing parent turns.
E2E-167:原生边缘调整大小保持流畅并保存稳定边界
- 前置条件:PI-Desktop 在 macOS、Windows 或 Linux 上以普通、未最大化 窗口打开。工作面板关闭时执行一次,以确定宽度打开时再执行一次。
- 步骤:
- 缓慢拖动每个可用窗口边缘和一个角落,中途短暂停顿后松开鼠标。
- 确认指针按下期间窗口持续跟随,没有跳回默认尺寸或显示器边缘。
- 工作面板打开时从外边缘调整大小,确认对话窗格回流,而面板宽度和 分隔线首选项不变。
- 等待调整大小稳定后关闭并重新启动应用。
- 预期:无边框外壳仍提供原生边缘和角落命中区域,最小尺寸保持 1040×700,恢复看门狗不会与慢速调整大小流竞争。最后稳定的基础边界会 在重新启动后恢复;临时工作面板预留宽度不会被保存为用户的聊天窗口尺寸。
- 链接规格:
03-runtime/01-ipc-protocol.md、04-ux/01-ui-ia.md、04-ux/07-ui-design-system.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、ADR 0029 / ADR 0122 - 验收:A(应用外壳)、F(坚持)、质量
- 里程碑:M6+
- 状态:单元/源码契约覆盖;原生桌面边缘/角落旅程仍待补
E2E-168:展开侧边栏宽度跟随锚定的调整手势
- 前置条件:PI-Desktop 在聊天 Shell 中打开,侧边栏已展开,并显示一个保留的项目/会话。
- 步骤:
- 从默认宽度拖动侧边栏右边缘手柄向两个方向调整,包含一次中途短暂停顿的慢速拖动,然后释放。
- 确认主窗格连续回流,指针按下时侧边栏没有跳变。
- 分别拖到低于最小值和高于最大值,释放后确认宽度停在 240px 和 520px。
- 聚焦边缘手柄,按左右方向键、Home 和 End,并检查分隔器当前的 ARIA 值。
- 开始一次调整后按 Escape 或取消指针,再重启应用;单独检查折叠并重新展开侧边栏。
- 预期:手柄在悬停/聚焦时可发现,不会触发原生窗口拖动或文本选中,并保持按下点锚定。 MainChat 跟随实时宽度回流。指针释放保存一次限制后的首选宽度;Escape/取消恢复起始宽度而不保存。 键盘调整立即提交并暴露本地化宽度语义。保存的宽度在重启后保留,折叠后重新展开仍会恢复;折叠不会把首选宽度变成图标栏宽度。
- 链接规格:
04-ux/01-ui-ia.md、04-ux/07-ui-design-system.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、ADR 0141、D280 - 验收:A(应用外壳)、F(持久性)、质量
- 里程碑:M6+
- 状态:单元/源码契约已覆盖(
sidebar-preferences.test.mjs、sidebar-resize.test.mjs); 渲染桌面拖动与重启旅程仍待补
E2E-162:厂商账户与 AI 服务提供同一个模型选择器
- 前提条件:一个已登录的厂商(OAuth)账户与一个使用 API 密钥的 AI 服务,两者都 能发现模型,其中至少一个提供支持推理的模型。
- 步骤:1)打开设置 → 模型配置。2)编辑该 AI 服务,展开某个已选模型的“高级” 折叠区,记下其上下文窗口、输出上限与思考等级按钮;取消。3)编辑厂商账户,对它的 某个已选模型做同样的操作。4)在支持推理的账户模型上切换一个思考等级并保存。 5)重新打开账户编辑器,读取该模型的等级按钮。6)对 OpenAI Codex 账户,检查
gpt-6-astra(或其他同时发布在 models.devopenai提供商下的账户模型), 确认其已发布的上下文/输出上限与推理等级存在。7)在账户编辑器里手工输入一个目录 未发布的自定义模型 ID,为它启用一个思考等级并保存。 - 预期:两个对话框渲染同一个选择器 —— 同样的已发现列表、同样的搜索、同样的 自由填写自定义模型入口、同样的已选面板、同样的“高级”折叠区与等级按钮 —— 账户 编辑器不再缺少高级设置。在账户模型上启用的等级会持久化,并在重新打开编辑器后 依然存在。OpenAI Codex 的
openai-codex适配器键会解析匹配的openaimodels.dev 记录,因此gpt-6-astra不会显示为通用的 128,000 / 8,192 / 无推理默认值。已认证的 ChatGPT 列表本身来自已固定的 pi-ai 目录(0.86.1 包含gpt-6-astra);models.dev 不能补上缺失的 OAuth ID。没有已发布记录 的模型则保留其已存等级不变。账户的默认模型仍是首个绑定。 - 链接规格:
04-ux/06-settings-ia.md、04-ux/08-component-spec.md§19、03-runtime/11-provider-model-system.md§10、08-meta/decisions-log.md(D270 细化 D237/D240) - 验收:B(模型配置)、质量
- 里程碑:M6+
E2E-164:上下文压缩保留活动任务边界
- 先决条件:提供商夹具可以在一个会话中完成多个连续任务,在终止边界触发自动 检查点,在工具循环期间触发活动回合检查点,并且可以重启会话。
- 步骤:
- 在同一会话中完成任务 A 和任务 B,使用不同指令并产生可见的完成回复。
- 在已完成回合后触发检查点,然后发送任务 C,捕获下一次提供商请求的上下文。
- 在任务 D 仍有工具结果或
toolUse待处理时触发压缩,捕获下一次请求。 - 重启并重新打开会话,然后再发送一条提示。
- 预期:已完成回合检查点的保留尾部为空;下一次请求包含其摘要和任务 C,不包含裸的 A/B 提示。活动检查点只保留最新的活动用户提示,不包含更早的用户提示或边界前的 助手/工具消息。重启遵守
retainedTailMode,没有该字段的旧多用户尾部归一化为 最新用户消息。可见转录本保持完整,检查点行仍然存在。 - 链接规格:
03-runtime/02-agent-runtime.md、03-runtime/04-data-storage.md、03-runtime/16-tool-result-limits.md、08-meta/decisions-log.md(D275)、ADR 0136 - 验收:C(聊天/流)、F(持久性)、质量
- 里程碑:M5
- 状态:已覆盖单元测试(
packages/agent-runtime/src/runtime.test.ts、context-compaction.test.mjs);provider/UI 旅程草稿
E2E-172:回合进行中改思考档位不会塌缩未固定会话菜单
- 前提条件:应用默认是具备推理能力的自定义供应商/模型,绑定发布稀疏档位 (例如
low/high/max)。会话走默认创建路径(provider_id/model_id为 NULL)。 - 步骤:1)发送一条提示使会话进入运行中。2)打开 Composer 模型 × 思考菜单并选择 另一个已启用档位。3)在回合结束前再次打开思考子菜单。4)切换到其他会话再切回。 5)等待回合结束。
- 预期:芯片显示所选档位(不是关闭思考)。子菜单仍列出全部已启用绑定档位。 切换会话不会塌缩菜单。
agent_end之后排队配置落库。不必通过重新保存供应商来恢复菜单。 - 链接规格:
03-runtime/01-ipc-protocol.md、03-runtime/13-model-catalog-and-selection.md、04-ux/08-component-spec.md§11 - 验收:B(模型配置)、C(聊天和流)、质量
- 里程碑:M6
- 状态:单元已覆盖(
session-thinking.test.mjs、thinking-ui.test.mjs、composer-send-state.test.mjs);完整 UI 场景草稿 (适用变更合入前需在具备条件的环境中运行 E2E)
E2E-174:绑定的默认思考等级播种草稿和新会话
- 前提条件:一个 AI 服务,其中某个推理模型的已发布档位不含
off(例如low/high/max),并且其“高级”默认思考等级是非最高的 已启用档(例如low)。 - 步骤:1)在没有活动会话时打开首页 Composer,读取模型 × 推理芯片。 2)不打开推理菜单,直接新建任务,然后读取芯片和会话存储的
thinkingLevel。3)在首页草稿中从模型菜单切换到该模型,发送前读取芯片。 4)把绑定默认改成另一个已启用档并保存,然后在新草稿上重复步骤 1–2。 - 预期:首页草稿芯片、草稿上的模型切换、以及新持久化会话都从绑定 存储的默认值开始,而不是最高已发布或已启用档。设置中改默认值会影响 下一个草稿和新会话,但不会改写已有会话。
- 链接规格:
03-runtime/11-provider-model-system.md§6.2、03-runtime/13-model-catalog-and-selection.md§4、04-ux/08-component-spec.md§11.4 / §11.5、08-meta/decisions-log.md(D303) - 验收:B(模型配置)、C(聊天/流)、质量
- 里程碑:M6+
- 状态:单元/源码契约已覆盖(
thinking-levels.test.ts、thinking-ui.test.mjs);渲染桌面旅程仍待补
E2E-175:分页读取不显示为已截断
- 前提条件:绑定项目的 Agent 会话;工作区有一个至少 3000 行、每行短于 16,384 个字符的文本文件,以及首行超过该上限的夹具。
- 步骤:
- 不带
offset/limit地Read该长文件。 - 用报告的下一个偏移和一个适度的
limit再Read同一文件。 Read超长行夹具。- Grep 一个匹配数超过默认
headLimit的词。
- 不带
- 预期:
- 步骤 1 返回默认 2000 行窗口,
truncated: false,无截断芯片,totalLines为整文件行数,notice写出下一个偏移,且不让模型去 Grep。 - 步骤 2 无重叠地继续,并保持
truncated: false。 - 步骤 3 设
truncated: true,在notice中计数被剪行,并显示截断芯片。 - 步骤 4 因还有剩余命中而设
truncated: true,并显示芯片。
- 步骤 1 返回默认 2000 行窗口,
- 链接规格:
03-runtime/16-tool-result-limits.md§2 / §5、04-ux/08-component-spec.md§9.2、08-meta/decisions-log.md(D306) - 验收:C(聊天和直播)、E(工具和权限)
- 里程碑:M5
- 状态:单元已覆盖(host-core
tools测试)
E2E-176:设置 → 信息打开预填的 GitHub bug 表单
- 前提条件:可以打开设置;本机可以启动系统浏览器。
- 步骤:1)打开设置 → 信息。2)确认「应用」行显示当前版本。3)用设置搜索查找「问题反馈」。4)点击「打开 GitHub」。
- 预期:该行可被设置搜索索引,并停留在信息页。动作调用
pi-desktop/app/openFeedback,渲染器不提供 URL。Main 打开https://github.com/vastsa/PI-Desktop/issues/new,带template=bug_report.yml,并预填app-version、os和environment。GitHub bug 表单仍要求描述、复现步骤、预期、实际、版本和操作系统;空白 issue 保持禁用。 - 链接规格:
04-ux/06-settings-ia.md、03-runtime/01-ipc-protocol.md、06-delivery/03-ai-development-workflow.md、08-meta/decisions-log.md(D313)、ADR 0157 - 验收:H(诊断)、质量
- 里程碑:M6+
- 状态:单元/源码契约已覆盖(
github-feedback.test.ts、feedback.test.mjs);渲染桌面旅程仍待补(除非用户明确要求,否则不要在本地跑 E2E)
E2E-177:切换仍在运行的长会话时,最新提示仍留在视野里
- 前提条件:会话一直开着,渲染器里的行数已超过最新 100 条持久化页(数百条用户/助手/工具行);另有第二个会话可供切换。
- 步骤:1)如有需要先中断进行中的回复,再发送一条新提示。2)在该回合仍在运行时,切到另一个会话再切回来。3)确认最新用户行(以及任何流式尾巴)在记录底部,且会话仍显示为运行中。4)可选:停止,再切走切回;同样的最新行仍按时间顺序保留。
- 预期:再验证不会把更早的实时历史追加到有界持久化页之后。尾部挂载窗口仍显示刚发送的提示和实时尾巴。切换期间回合在后台继续。不必靠停止才能让提示重新可见。
- 链接规格:
04-ux/08-component-spec.md§1.6 / §3.5、04-ux/09-interaction-patterns.md(会话隔离)、ADR 0120、ADR 0137、08-meta/decisions-log.md(D261、D317) - 验收:C(对话和直播)、F(持久化)、质量
- 里程碑:M5
- 状态:单元已覆盖(
session-transcript.test.mjs的 D317 用例);完整桌面旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-183:切换空闲会话时,尚未落盘的已完成回复仍保留
- 前提条件:存在两场对话。源会话已完成一条用户提示,助手回复仍在屏幕上。该会话的持久化
session.get页此时仍只有用户行(持久化 outbox 尚未刷入助手行)。 - 步骤:1)等到回合不再运行。2)切到另一场会话再切回来。3)确认助手回复仍可见。4)可选:等到 outbox 排空后再切走切回;回复仍在,并且 JSONL 里也有了。
- 预期:空闲再验证把实时快照缝到持久化页上。实时独有的已完成助手/工具行不会被只有用户句的持久化页替换。只有当该页包含每一个实时 id 时才清掉实时来源。
- 链接规格:
04-ux/08-component-spec.md§1.6 / §3.5、04-ux/09-interaction-patterns.md(会话隔离)、ADR 0137、08-meta/decisions-log.md(D317、D324) - 验收:C(对话和直播)、F(持久化)、质量
- 里程碑:M5
- 状态:单元已覆盖(
session-transcript.test.mjs的 D324 用例、session-switch-performance.test.mjs);完整桌面旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-184:关闭并重新打开应用后,已完成的 AI 回复仍然保留
- 前提条件:会话至少有一条已完成的用户提示和助手回复。进程退出时,回复可能仍在持久化 outbox 里,或只在
sessions/<id>.inflight.json中。 - 步骤:1)发送提示,等到助手回复完成且会话空闲。2)退出应用(关窗口 / 托盘退出)再启动。3)打开同一会话。4)在回复刚出现在屏幕上时硬杀进程,再重复。5)完成多轮后再退出并重新打开。
- 预期:重新启动后每一条用户提示和每一条已完成的助手回复都可见。不会出现只有用户行、答案位置空白的会话。从已
completed回合的残留检查点恢复的回复是complete,不是aborted。已到达tool_end的工具行也在。磁盘上更早的回合不变。 - 链接规格:
03-runtime/04-data-storage.md、03-runtime/07-process-model.md、03-runtime/10-session-state-machine.md、 ADR 0041、ADR 0153、08-meta/decisions-log.md(D327) - 验收:C(对话和直播)、F(持久化)
- 里程碑:M5
- 状态:单元已覆盖(
sessions.rs的 D327 inflight 测试、persistence-outbox.test.mjs、inflight-checkpoint.test.mjs);完整桌面旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-178:缺失的 sessions 行被恢复,outbox 才能排空
- 前提条件:会话仍有活的
sessions/<id>.jsonl,以及session-message-outbox.json里排队的回合,但pi.sqlite的sessions行已不在(WAL/索引丢失)。 - 步骤:1)确认侧边栏不再列出该会话,且
session.appendMessage会因session not found失败。2)重启应用(或完成一次会刷新 outbox 的主机握手)。3)可选:删除该会话,确认其 outbox 条目被丢掉而不是被救回。 - 预期:主机启动从 JSONL 重新插入 sessions 行并重建搜索索引。outbox 不会卡在队头,能够排空。对话回到侧边栏且消息还在。用户删除的会话不会被残留 outbox 条目重建。
- 链接规格:
03-runtime/04-data-storage.md、03-runtime/06-host-rpc-protocol.md、03-runtime/07-process-model.md、 ADR 0041、08-meta/decisions-log.md(D318) - 验收:C(对话和直播)、F(持久化)
- 里程碑:M5
- 状态:单元已覆盖(host-core 孤儿会话恢复测试、
persistence-outbox.test.mjs);完整桌面旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-180:已发送的文件引用保持芯片并可点击打开
- 前提条件:Agent 会话所在工作区含有嵌套源文件、HTML 文件,以及文件名带空格的文件;该项目组还有一个第二文件夹,里面有一个属于它自己的源文件。输入框也可以把操作系统文件粘贴到会话临时目录。内置的文件管理器插件已加载。
- 步骤:1)通过输入框芯片引用工作区源文件、工作区 HTML、带空格的文件名,以及粘贴的临时文件,然后发送。2)查看用户气泡。3)点击 HTML 芯片,再点击工作区源文件芯片,最后点击临时目录芯片。4)附加上项目第二个文件夹里的文件,并点击它的芯片。
- 预期:
- 每条已发送引用画成紧凑的叶子名芯片(图标 + 名称),而不是完整
@path。工具提示和无障碍名称保留规范路径。带引号路径和临时绝对路径也包括在内。 - 芯片加上短提示时,用户气泡按内容收缩,而不是撑到
min(82%, 600px)上限。 - 点击 HTML 芯片在工作面板浏览器打开该文件。
- 点击工作区源文件芯片后,该文件在文件管理器工作面板视图中打开;芯片点击不再打开宿主的
file:选项卡,也不再交给系统默认应用。 - 点击临时目录芯片时,文件在宿主的
file:选项卡中按其绝对路径打开——它位于文件管理器项目根之外。 - 点击项目第二个文件夹里那个文件的芯片时,该文件在文件管理器视图中打开:补全会搜索整个项目组、主文件夹优先,同级文件夹里的文件用绝对路径寻址,因为相对路径永远指主文件夹(ADR 0263)。
- 持久化用户消息仍包含给模型用的规范
@path文本。
- 每条已发送引用画成紧凑的叶子名芯片(图标 + 名称),而不是完整
- 链接规格:
04-ux/08-component-spec.md§8.3 / §11.8、04-ux/09-interaction-patterns.md§8a.2、03-runtime/01-ipc-protocol.md、 ADR 0163、ADR 0241、ADR 0262、ADR 0263、08-meta/decisions-log.md(D320) - 验收:C(对话和直播)、质量
- 里程碑:M5
- 状态:单元已覆盖(
chat-links.test.mjs、transcript-file-chips.test.mjs、fs-panel-guard.test.mjs、transcript-style.test.mjs);完整 UI 旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-CHAT-shorthand-file-ref-opens-the-matching-file
- 前提条件:一个项目组的主文件夹里有
img/openimage.js、src/dir/a.ts,以及更深的第二份dir/a.ts(例如packages/app/dir/a.ts);它的第二个文件夹里有lib/only-here.ts。当前会话的临时目录里有一个同名叶子文件openimage.js,以及一个项目里没有的文件;附件目录里有一个attachments/<sha256>blob。对话能渲染助手 markdown。 - 步骤:1)让一轮回复用行内代码提到
openimage.js,点击它。2)让一轮回复提到dir/a.ts,点击它。3)让一轮回复提到只存在于临时目录的文件并点击,再对attachments/<sha256>引用做同样操作。4)让一轮回复给出某个项目文件的绝对路径(该叶子名在临时目录里也存在),点击它。5)让一轮回复提到missing-helper.js,点击它。6)让一轮回复提到only-here.ts,点击它。 - 预期:
- 点击
openimage.js打开项目里的img/openimage.js,而不是临时目录里的同名文件:项目会被搜到底,之后才会考虑临时目录。 dir/a.ts打开src/dir/a.ts:精确路径优先于简写,更长的匹配尾优先于裸叶子名,尾长相同则更浅的路径胜出,因此更深的packages/app/dir/a.ts永远不会被选中。- 项目里找不到的引用回落到会话临时目录;两边都没有的再回落到附件目录,
attachments/<sha256>引用打开对应的已存 blob。 - 指向已知根内真实文件的绝对引用直接命中,无论哪条简写规则本来会匹配。
only-here.ts打开第二个文件夹里的lib/only-here.ts:先搜主文件夹并搜到底,再按项目组自身顺序搜索其余文件夹,命中结果也指出是哪个文件夹应答的(ADR 0263)。- 由同级文件夹应答的文件交给工作面板时用绝对路径,主文件夹里的文件则保持项目内相对路径(ADR 0263)。
- 什么都匹配不上时弹出错误提示「没有匹配 missing-helper.js 的文件」,并且什么都不打开:不新增工作面板选项卡、不出现空面板、不出现空白旁浏览器页面,工作面板与对话保持原有内容。
- 点击
- 链接规格:
03-runtime/01-ipc-protocol.md§ fs、04-ux/09-interaction-patterns.md§8a.2、ADR 0124、ADR 0163、ADR 0249、ADR 0262、ADR 0263 - 验收:C(对话和直播)、D(工作区)、质量
- 里程碑:M5
- 状态:单元已覆盖(
apps/desktop/test/chat-ref-resolve.test.mjs);完整 UI 旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-CHAT-file-ref-opens-the-surface-that-owns-it
- 前提条件:内置的文件管理器插件已加载并启用,工作面板处于关闭状态。当前会话的临时目录里有一个文件,附件目录里有一个 blob。项目是一个项目组:主文件夹里有一个
.html页面和一个文本文件,第二个文件夹里有一个属于它自己的文本文件。 - 步骤:1)点击助手回复中的项目文件引用。2)在该视图里留下未保存的改动,再次点击同一引用。3)点击解析到会话临时目录的引用,再点击
attachments/<sha256>引用。4)在助手回复和已发送的用户芯片中分别点击一次工作区.html引用。5)禁用文件管理器插件后再次点击项目文件引用,重新启用后再点击一次同一引用。6)点击解析到项目第二个文件夹的引用,再点击解析到主文件夹的引用。 - 预期:
- 项目文件在文件管理器工作面板视图中打开:该文件被选中、祖先目录已展开,不会为它新增宿主
file:选项卡。 - 再次点击同一引用不会重新加载视图:未保存的改动仍在编辑器里,也不会出现第二个选项卡。
- 临时目录或附件里的文件在宿主的
file:选项卡(位于「已打开项目」下)中按绝对路径打开,而不是在文件管理器视图中打开。 - 项目主文件夹里的
.html/.htm页面在旁浏览器中打开,助手回复和用户芯片两条路径一致;同级文件夹里的页面和别的项目文件一样在文件管理器视图中打开,因为旁浏览器以主文件夹为根(ADR 0263)。 - 解析到项目第二个文件夹的引用在文件管理器视图中打开该文件,用绝对路径寻址,不会新增宿主
file:选项卡;解析到主文件夹的引用也在同一视图中打开,按项目内相对路径寻址(ADR 0263)。 - 插件被禁用时,项目文件引用退回到宿主的
file:选项卡(也就是改动前点击所用的界面,现在也能覆盖项目里的其他文件夹),而不是什么都不打开;重新启用后恢复为文件管理器视图。
- 项目文件在文件管理器工作面板视图中打开:该文件被选中、祖先目录已展开,不会为它新增宿主
- 链接规格:
04-ux/08-component-spec.md§8.3、04-ux/09-interaction-patterns.md§8a.2、ADR 0104、ADR 0163、ADR 0241、ADR 0249、ADR 0262、ADR 0263 - 验收:C(对话和直播)、G(插件)、质量
- 里程碑:M5
- 状态:单元已覆盖(
apps/desktop/test/transcript-file-chips.test.mjs);完整 UI 旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-181:导入的技能会出现在下一个会话的目录里
- 前提条件:设置 > 智能体 > 技能已打开。一份约定的
<skill>/SKILL.md文档带有非 ASCII 的 frontmatter 名称和折叠 YAML 描述。同一项目上有一个空的 Agent 会话,技能将导入到该项目。 - 步骤:
- 把
SKILL.md导入到 Global,再导入到当前项目。 - 确认技能页显示显示名、ASCII id(目录名,而不是
skill)以及展平后的描述。 - 在该项目上开一个新的 Agent 会话,让智能体按显示名使用该技能。
- 再用第二个同样没有 ASCII 名称的目录技能,以及一份
|块描述的技能重复一遍。
- 把
- 预期:
- 导入成功。目录列出两个 id 不同的技能。
- 下一个会话的系统提示包含每个技能的 id、名称和展平描述。
Skill工具能按该 id 加载正文。 - 拼错的
Skillid 会在可用技能里列出用户技能 id,而不只是插件 id。 - 文档不会因为标题是非 ASCII、或因为两个文件都叫
SKILL.md而被丢掉。
- 链接规格:
03-runtime/01-ipc-protocol.md§12b、07-plugins/01-plugin-system.md§12.3、08-meta/decisions-log.md(D174、 D194) - 验收:E(工具和权限)、质量
- 里程碑:M5
- 状态:单元已覆盖(host-core
user_skills/agent_capabilities测试、apps/desktop/test/plugin-skills.test.mjs);完整 UI 旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-185:外部链接只打开 http(s) 和 mailto
- 前提条件:对话可渲染 markdown 链接。插件已授予
shell.openExternal。 工作面板预览可加载 http 页和工作区 HTML 文件。 - 步骤:1) 对 https、mailto、
file:、javascript:、ms-msdt:和自定义 scheme 的 markdown 链接做 modified-click(target="_blank")。 2) 从插件调用pi.shell.openExternal,分别传入 https、mailto 和file:。 3) 在嵌入预览中window.open一个 https URL 和一个file:URL;对 http 页 和根内 HTML 文件使用「在系统浏览器打开」。 - 预期:
- https 和 mailto 交给系统处理器。
file:、javascript:、data:、ms-msdt:和自定义 scheme 不会。 - 插件的
file:以INVALID_ARGUMENT失败;mailto 成功。 - 预览对非白名单 scheme 的
window.open在应用内拒绝,且不调用openExternal。 - 「在系统浏览器打开」对 http(s) 走
openExternal;对根内文件预览走openPath,而不是把file:URL 交给openExternal。
- https 和 mailto 交给系统处理器。
- 链接规格:
05-security/01-security.md、07-plugins/04-plugin-security.md§8、07-plugins/03-plugin-api.md、ADR 0109、ADR 0168、08-meta/decisions-log.md(D330) - 验收:安全
- 里程碑:M5
- 状态:单元已覆盖(
safe-open-external.test.mjs、feedback.test.mjs);完整 UI 旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-187:历史附件与本地 Markdown 图片内联显示
- 前提条件:工作区含
docs/pixel.png的 Agent 会话。用户曾粘贴图片,会话 JSONL 存有带 mimeType 的attachments/<sha256>图片引用。 - 步骤:
- 重新打开该会话。确认粘贴图片渲染为缩略图,而不只是文件芯片。
- 点击缩略图。确认宿主文件查看器打开该附件引用并显示图片。
- 发送一回合,助手 Markdown 含
和。确认工作区图片内联显示,工作区外路径不加载文件字节。 - 确认
fs/readImageDataUrl在ref: "/etc/passwd"且mimeType: "image/png"时返回missing,而不是 data URL。
- 预期:受限图片引用在 5MB 上限内内联显示;工作区外路径与对非图片扩展名的 mime 伪造保持关闭;点击已解析缩略图打开宿主
file:选项卡,而不是系统默认应用。 - 链接规格:
03-runtime/01-ipc-protocol.md、04-ux/08-component-spec.md§8.3、ADR 0172、08-meta/decisions-log.md(D334) - 验收:C(对话与流)、D(工作区)、安全、质量
- 里程碑:M5
- 状态:单元已覆盖(
fs-panel-guard.test.mjs、message-image-display.test.mjs);完整 UI 旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-188:插件宿主 API 可列出模型、读取进行中的上下文并发起补全
- 前提条件:至少一个已认证 provider;开发插件已授予
models.list、session.read和agent.complete;Agent 会话已有一条用户回合。 - 步骤:
- 在插件进程调用
pi.models.list()。确认只返回就绪模型,且没有密钥字段。 - 在工具执行之外调用
pi.session.getLlmContext()。确认INVALID_ARGUMENT。 - 让 Agent 调用插件工具。在
execute内调用getLlmContext(),再调用agent.complete({ modelKey, includeSessionContext: true })。确认工具结果含评审文本和 usage,不含密钥。 - 在 60 秒内重复
agent.complete,直到第 8 次成功、第 9 次返回RATE_LIMITED。 - 让该补全在供应商侧失败(例如使用一个用户已撤销凭据的模型)。确认插件读到的是分类后的错误码
PROVIDER_UNAUTHORIZED,而不是一个通用失败。 - 停止 host-core 后再调用
pi.models.list()。确认返回[]且没有 warn 日志。
- 在插件进程调用
- 预期:凭据不离开 Electron main。审计行记录插件 id、模型 key、体积和 usage,不含转录或补全文本。Plan 仍对插件工具返回
PLUGIN_DISABLED_IN_PLAN。宿主传输不可用时模型列表为空且不记警告(D080)。 - 链接规格:
07-plugins/03-plugin-api.md、07-plugins/13-plugin-permissions-matrix.md、ADR 0174、D336 - 验收:G(插件智能体工具)、安全
- 里程碑:M5
- 状态:单元已覆盖(
plugin-complete.test.mjs、plugin-session-context.test.ts、subagent-wiring.test.mjs);完整 UI 旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-189:随应用打包的 Advisor 插件暂时不可用
- 前提条件:PI-Desktop 的打包版或开发构建。
- 步骤:
- 检查随应用打包的插件资源,确认不存在
pi.advisor。 - 打开命令面板和插件设置,确认不存在
/advisor、Advisor 插件及其advisor工具。
- 检查随应用打包的插件资源,确认不存在
- 预期:暂时移除后不暴露 Advisor 命令、插件、面板、技能或 Agent 工具。 通用的宿主代发插件补全 API 仍可供显式安装的插件使用。
- 关联规格:
07-plugins/03-plugin-api.md、ADR 0174、D336 - 验收:G(插件 Agent 工具)、C(对话)
- 里程碑:M5
- 状态:单元已覆盖(
bundled-plugins.test.mjs);完整 UI 旅程仍为草稿 (适用变更合入前需在具备条件的环境中运行 E2E)
E2E-190:设置中的网络代理应用到应用自有 HTTP
- 前提条件:可访问的本地 HTTP 或 SOCKS5 代理,或用于失败路径的无效端口;另有一个可通过该代理访问的已配置供应商/模型,用于模型请求步骤。
- 步骤:
- 打开设置 → 常规。确认网络卡提供系统 / 直连 / 自定义。从未配置过代理的配置文件默认是系统。
- 选择自定义。确认代理 URL、默认绕过列表和测试。输入
not-a-proxy并失焦。确认内联错误且未保存。 - 输入
socks5://127.0.0.1:1080或http://127.0.0.1:7890并失焦。确认settings.get中networkProxy.mode为custom。 - 对正在监听的代理点测试,确认已连接;对关闭的端口点测试,确认失败且不改已保存 URL。对需要认证的 HTTP / SOCKS5 代理填入
user:pass@后再测,确认是已连接而不是net::ERR_NO_SUPPORTED_PROXIES(issue #490)。 - 保存自定义代理后,通过已配置供应商发送一条简短提示。确认供应商请求和响应经过代理;即使 SOCKS5 代理将完整 bind 响应合并在一个 TCP 数据块中,请求仍能完成。再确认扩展市场刷新和 models.dev 刷新走代理;绕过列表中的回环地址不走代理。
- 切到直连再切回系统。确认无需重启应用即可生效。
- 预期:自定义覆盖模型请求、市场、更新、插件
net.fetch和内置浏览器。工作区 Bash 的env看不到该设置写入的HTTP_PROXY/ALL_PROXY。OAuth 仍走系统浏览器。file:/ftp:、SOCKS4 以及百分号编码错误的认证信息均被拒绝。带账号密码的 HTTP / SOCKS5 测试与应用不再报net::ERR_NO_SUPPORTED_PROXIES(issue #490)。无协议/存储版本升级。 - 链接规格:
04-ux/06-settings-ia.md、03-runtime/07-process-model.md、ADR 0177、D340 - 验收:B(设置)、F(供应商)、安全
- 里程碑:M5
- 状态:单元已覆盖(
network-proxy.test.ts、node-proxy.test.ts、authenticated-proxy-relay.test.ts、settings-general.test.mjs、host-corenetwork_proxy);畸形认证信息和不支持的 SOCKS4 协议由共享解析器测试覆盖;完整 UI 旅程仍为草稿
E2E-210:文档截图在 GitHub 与 VitePress 中都能解析
- 前提条件:仓库在
docs/public/screenshots/app/下包含截图集;文档依赖已安装。 - 步骤:
- 从 GitHub 文件页打开
docs/guide/screenshots.md和docs/zh-CN/guide/screenshots.md。确认截图都解析到docs/public/screenshots/下的文件。 - 在 VitePress 预览中打开中英文截图页。确认主页、面板和设置章节的代表性图片正常显示。
- 确认两个语言页面的导航 logo 都加载
docs/public/app-icon.png。 - 确认两个语言页面的 footer 都包含指向
https://aiuo.net的AIUO.NET链接。 - 运行
pnpm docs:build,检查生成页面没有图片加载失败。
- 从 GitHub 文件页打开
- 预期:GitHub 不再请求仓库根目录的
/screenshots/路径,而是显示全部截图;两个 VitePress 语言页面继续使用同一批已提交资源。导航使用应用图标,两个 footer 都显示指向https://aiuo.net的链接,文档构建成功。 - 链接规格:ADR 0079、
docs/README.md、docs/guide/screenshots.md、docs/zh-CN/guide/screenshots.md - 验收:质量
- 里程碑:M5
- 状态:静态/文档检查已覆盖(
pnpm docs:build与路径审计);远程 GitHub 和浏览器旅程待验证
E2E-214:插件会话导入与归属边界
- 前置条件:测试插件声明两个
contributes.sessionSources,并只获得 P0/P1 会话权限;第二个插件不能访问第一个插件的会话。 - 步骤:1)导入带外部项目/provider/model 历史以及 user、assistant、tool 消息的 会话。2)重复导入,确认使用同一个主机生成 id 并跳过。3)调用 list、get 和消息分页, 覆盖倒序与内容截断。4)尝试未声明来源、system/running 角色、无效时间戳、过大或过深 的工具值,以及缺少各权限的调用。5)从第二个插件重复 list/get/messages/rename/delete。
- 预期:Electron 在转发到主机前执行来源和权限校验;id 由 host-core 生成;原始绑定 只作为历史返回;消息标记为 external;主机保留的工具元数据被移除;无效输入返回稳定 错误码;第二个插件看不到该行及其转录本。
- 关联规格:
07-plugins/02-plugin-manifest-schema.md、07-plugins/03-plugin-api.md、07-plugins/13-plugin-permissions-matrix.md、 ADR 0200、D367 - 验收:安全、质量
- 里程碑:M6+
- 状态:单元/RPC/连线已覆盖;完整 UI 路径草稿(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-215:插件批量导入与删除生命周期
- 前置条件:测试插件可调用
session.importBatch、rename 和 delete;host-core 使用 空的 v14 数据库启动。 - 步骤:1)运行包含有效、重复和无效项目的
skip批量导入。2)运行包含一个无效或 冲突项目的fail批量导入,确认整个批次没有落盘。3)重命名归属会话并确认列表顺序 和元数据。4)trash 后确认普通核心/插件读取都不返回,再 purge 并使用相同外部 id 重新 导入。5)覆盖分页、payload、批量大小和导入/删除滚动频率限制。6)从 v13 数据库迁移 后重启,确认既有核心会话仍保持活动。 - 预期:skip 部分成功、fail 原子回滚;list/get/messages 仍按归属限制;trash 可由 归属者 purge;purge 删除转录本并允许重新导入;边界返回
LIMIT_EXCEEDED,滚动限制 返回RATE_LIMITED;迁移得到 schema v14,插件历史不创建项目行。 - 关联规格:
03-runtime/04-data-storage.md、03-runtime/06-host-rpc-protocol.md、 ADR 0200、D367 - 验收:安全、质量、恢复
- 里程碑:M6+
- 状态:主机/RPC/单元已覆盖;完整 UI 路径草稿(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-PLUGIN-usage-listTurns:插件用量事实列举
- 前置条件:测试插件获得
usage.read;主机库中有未删除与软删会话的已完成 turn。 - 步骤:1)无权限调用
pi.usage.listTurns。2)有权限调用、按游标翻页、按会话/项目/时间窗过滤。3)传入倒置边界、超过 365 天的窗口、畸形游标。4)确认行含 token 计数与标题、无消息正文,软删会话不出现。 - 预期:缺权限返回
PERMISSION_DENIED且不打到主机。合法调用返回已完成 turn 的 keyset 页。非法参数返回INVALID_PARAMS。空标题为null。 - 关联规格:
07-plugins/03-plugin-api.md、07-plugins/13-plugin-permissions-matrix.md、03-runtime/06-host-rpc-protocol.md、ADR 0173、D335 - 验收:安全、质量
- 里程碑:M6+
- 状态:单元/RPC/连线已覆盖(
plugin-session-api.test.mjs、host-coreplugin_usage);完整 UI 路径草稿
E2E-216:插件显式绑定项目与宿主拥有的侧栏刷新
- 前置条件:测试插件获得
project.create、session.import权限并声明会话来源; 渲染器正在显示现有侧栏;有一个项目路径可用且无需切换当前工作区。 - 步骤:1)调用
pi.project.create({ path })并记录返回的projectId。2)使用该 id 导入一个会话,再导入一个不传 id 的会话。3)观察插件调用完成时的渲染器。4)使用相同 external id 重复导入,然后重命名/删除一个归属会话。 - 预期:创建项目只返回持久 id,不激活或替换当前工作区。只有显式传入 id 的导入拥有 活动项目绑定;省略 id 的导入保持未绑定,
projectPath仅是历史字段。每个成功写操作 产生一次宿主拥有的pi-desktop/session/event/changed,渲染器通过refreshSessions()刷新,侧栏不依赖插件自发事件。跳过的导入不会触发冗余刷新;刷新 会话列表不会仅因为导入而重新打开已关闭的项目标签。 - 关联规格:
07-plugins/03-plugin-api.md、07-plugins/13-plugin-permissions-matrix.md、03-runtime/01-ipc-protocol.md、03-runtime/06-host-rpc-protocol.md、ADR 0201、D368 - 验收:C(会话)、安全、质量
- 里程碑:M6+
- 状态:单元/源码契约已覆盖;完整 UI 路径草稿(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-217:Windows 在全新 x64 模拟 ARM64 安装上启动主机
- 前提条件:全新的 Windows 11 x64 或 ARM64 机器/配置,没有单独安装 Visual C++ Redistributable、Node.js 或其他本地代理运行时;已有 x64 NSIS 安装程序。
- 步骤:1)安装 PI-Desktop。2)首次启动。3)等待启动 splash 让出给主 shell。4)检查运行时和 timing 日志,然后打开“设置 → 信息”。
- 预期:捆绑的 x64
pi-desktop-host-core.exe启动并完成app.handshake,没有0xC0000135(STATUS_DLL_NOT_FOUND);shell 不会停留在“无法连接本地服务”,主机状态健康, “设置 → 信息”显示主机版本而非host unknown。软件包使用静态链接的 MSVC CRT,无需单独安装运行时。 原生 Windows ARM64 工件仍不在范围内。 - 关联规格:
03-runtime/07-process-model.md、06-delivery/06-release-runbook.md - 验收:A(应用启动)、质量(全新安装打包)
- 里程碑:M6+
- 状态:源码契约已覆盖;全新 Windows x64 与 ARM64 资格验证仍需运行器验证(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-211:Windows 便携版 ZIP 解压后即可启动(D603)
- 前提条件:Windows x64 标签或
dist:win包已从共享 electron-builder 配置 产出PI-Desktop-Setup-<version>.exe和PI-Desktop-Portable-<version>.zip; 有干净用户配置;账户是无需管理员提升的标准用户。 - 步骤:1) 检查发布目录和
latest.yml。2) 将便携版 ZIP 解压到用户可写目录, 不运行 NSIS 安装程序。3) 启动解压后的PI-Desktop.exe。4) 确认没有管理员提示, 且运行中的应用显示 PI-Desktop 图标和任务栏入口。5) 调用检查更新。6) 确认设置 → 信息 提供发布页而不是“重启以更新”。7) 退出并再次启动解压后的可执行文件。 - 预期:两个 Windows 工件都无空格并已上传。
latest.yml只指向 NSIS 安装程序。 ZIP 解压后的应用无需安装向导或管理员提示即可启动,保持正常的 PI-Desktop 任务栏 标识和图标,使用现有应用数据目录,并报告更新模式manual。可用更新不会下载或运行PI-Desktop-Setup-<version>.exe。再次启动从同一配置恢复会话。 - 链接规格:
01-product/01-product-scope.md、06-delivery/06-release-runbook.md、03-runtime/07-process-model.md、 ADR 0197 / D603 - 验收:质量(发布打包)
- 里程碑:M6+
- 状态:单元/源合同已覆盖(
auto-update.test.mjs);本机 Windows 启动仍为 运行器验证(除非明确要求,否则不要在本地跑 E2E)
E2E-201:为已配置模型设置别名并复制模型 id
- 前提条件:已保存一个提供商,至少两个模型绑定,其中至少一个目录发布了显示名。
- 步骤:1) 打开设置 → 模型配置并重新打开该提供商。2) 在实时模型列表中拖选一个模型 id 并复制;确认剪贴板是该 id,且行复选框没有切换。3) 不选中文本时点击同一行复选框,确认仍可切换。4) 展开某行高级选项并输入短别名。5) 保存后打开 Composer 模型选择器;确认该模型显示别名,另一行仍用目录名。6) 分别用别名和真实 id 搜索,都能找到该行。7) 清空别名并保存,确认恢复目录显示名。8) 再填别名、保存、重开提供商并重启应用,别名仍在。9) 在别名输入框输入超过 60 个字符,确认只保留前 60 个;再直接用
providers.update发送 61 字符别名,确认失败码为MODEL_ALIAS_TOO_LONG。 - 预期:别名只是展示标签,请求仍携带
models[].id;配置行在别名芯片旁继续显示真实 id。模型 id 和名称在禁止选择的外壳内仍可选择;带选区的点击不会切换复选框。空白或清空的别名回退到目录显示名。 - 链接规格:
03-runtime/12-provider-config-schema.md、04-ux/08-component-spec.md、ADR 0192 - 验收:B(模型配置)、质量
- 里程碑:M2
- 状态:单元已覆盖(
composer-models.test.mjs、provider-model-config.test.mjs);完整 UI 旅程为草稿
E2E-MODEL-selected-order-persists:保存后保留已选模型顺序
- 前提条件:一个 AI 服务和一个 OAuth 厂商账户各有至少三个已选模型。其中一个模型 具有别名和非默认的高级设置。记录绑定值、应用级默认提供商和模型,以及一个明确绑定 模型的会话的模型选择。保存响应可控,能够让任一编辑器保持忙碌状态。
- 步骤:
- 打开设置 → 模型配置并编辑 AI 服务。通过手柄将最后一个已选模型拖到首行之前, 再将其拖到末行之后。确认插入位置和最终顺序。
- 将已选 ID 配置为以下顺序:
shown-a、hidden-a、shown-b、hidden-b、shown-c。按shown-过滤,再将shown-c拖到shown-a之前。清除过滤并 检查全部五个绑定。 - 再次应用过滤。让
shown-c的排序手柄获得焦点,先按下方向键,再按上方向键。 确认每次都会越过相邻的可见行,且焦点始终保留在已移动模型的手柄上。对首个可见 模型按上方向键,对最后一个可见模型按下方向键;顺序均不改变。 - 开始拖动后取消,再次开始拖动并在已选行之外释放。两种操作都不改变草稿顺序。
- 在发现列表和已选面板中拖选并复制模型 ID。确认没有排序或复选框切换。点击发现 列表复选框、展开或收起高级设置、编辑别名、移除已选模型;每种操作均保持原有 行为,不会开始拖动。
- 保存重新排序后的绑定并重新打开编辑器,再重启应用并重新打开。检查顺序、别名、 模型 ID 和高级设置。检查提供商默认值和现有会话。分别在该服务是应用默认提供商 以及另一个提供商是默认提供商时重复验证。
- 再次更改顺序,取消编辑器并重新打开。上次保存的顺序保持不变。
- 在厂商账户编辑器中重复拖动、键盘、保存后重新打开和取消检查,包括过滤后隐藏 部分已选绑定的列表。
- 暂停保存响应,尝试拖动和键盘排序。恢复保存响应,再过滤到只剩一个可见已选行 并再次尝试。
- 预期:两个编辑器都持久化完整的有序绑定数组。步骤 2 后完整顺序变为
shown-c、shown-a、hidden-a、shown-b、hidden-b;隐藏绑定仍然存在,并保留其相对 顺序。移动绑定不会重置其 ID、别名或高级覆盖设置。保存后提供商的兼容默认值仍为 首个绑定。该服务或账户是应用默认提供商时,保存也会将应用默认模型更新为首个绑定, 保留 E2E-005A 的既有行为。编辑其他提供商不会改变应用默认值。明确绑定模型的会话 保留其已存储的模型选择。取消拖动或在行外释放不会改变草稿;取消编辑器会丢弃未保存 的移动;表单忙碌或只有一个可见行时禁用排序手柄。文本复制和现有行操作保持独立。 - 链接规格:
03-runtime/13-model-catalog-and-selection.md§2、03-runtime/12-provider-config-schema.md;ADR 0114、ADR 0192 - 验收:B(模型配置)、F(持久化)、质量
- 里程碑:M6+
- 状态:已记录;完整桌面旅程尚未运行。
E2E-202:子智能体思考跟随其精确模型绑定
- 前提条件:已配置提供商有标记
availableForSubagents的模型绑定。目录将该模型标为非推理,或未发布绑定显式启用的某一档。用户子智能体定义和内置Task目录可用。 - 步骤:1) 在设置 → 模型配置中为委托模型启用
medium和high,并标记可用于子智能体。2) 将该定义的思考级别设为high,保存并重启。3) 用 frontmatter 模型引脚运行该定义。4) 用内置Task.model选择同一绑定(含按需解析路径)。5) 在父会话为medium时运行没有模型引脚的定义。 - 预期:已固定和显式选择的委托保留绑定启用的思考档,即使 models.dev 说不可推理或集合稀疏,仍发送所选非
off档。未固定的委托继承父级有效级别。没有任何非off档的绑定仍解析为off。 - 链接规格:
03-runtime/02-agent-runtime.md、03-runtime/11-provider-model-system.md、ADR 0144 / D283 - 验收:B + C + 质量
- 里程碑:M6+
- 状态:单元/接线已覆盖;完整 UI 旅程为草稿
E2E-203:省略子智能体思考覆盖,并在深色主题下阅读已选档位
- 前提条件:已配置的子智能体模型支持推理,应用有浅色和深色主题。
- 步骤:1) 打开设置 → 智能体 → 子智能体并检查思考选择器。2) 确认包含沿用会话、不发送、
off和规范档位。3) 选择不发送、保存,确认文档含thinkingLevel: omit。4) 通过有意义适配器默认值的提供商运行该子智能体并检查出站请求。5) 切到深色主题,打开模型配置,展开某模型高级区,选择多个思考档芯片。 - 预期:沿用会话仍用父级档位;不发送会持久化且不发送提供商思考覆盖;显式
off仍是显式关闭。已选芯片在两种主题下都有高对比实心底和可读文字。 - 链接规格:
03-runtime/01-ipc-protocol.md、03-runtime/02-agent-runtime.md、03-runtime/13-model-catalog-and-selection.md、04-ux/06-settings-ia.md、ADR 0194 / D356 - 验收:B + C + 质量
- 里程碑:M6+
- 状态:单元/源合约已覆盖;完整 UI 旅程为草稿
E2E-203a:会话思考 omit 不发送提供商覆盖
- 前提条件:Composer 已选中支持推理的已配置模型。
- 步骤:1) 打开模型 × 推理菜单,确认
omit是第一项,其后是绑定已启用的规范档位。2) 选择omit,确认芯片显示omit且会话存thinkingLevel: omit。3) 发送一回合并检查出站请求。4) 再选显式off并发送。5) 在设置 → 模型配置展开同一模型高级区,确认默认选择器把omit放在最前;把默认存成omit并开新会话。 - 预期:
omit会持久化,且请求不含思考/推理字段。显式off仍序列化为关闭思考。非推理模型菜单仍只有off。绑定默认是omit的新会话从omit开始。 - 链接规格:
03-runtime/01-ipc-protocol.md、03-runtime/02-agent-runtime.md、03-runtime/13-model-catalog-and-selection.md、ADR 0295 / D456 - 验收:C + 质量
- 里程碑:M6+
- 状态:单元/源合约已覆盖;完整 UI 旅程为草稿
E2E-204:显式退出在关机前确认(D363)
- 前提条件:普通交互会话正在运行(不是 boot / supervision / capture 探针)。主窗口可见或已藏到托盘。
- 步骤:1) 从托盘菜单选择退出,或按 Cmd+Q / 应用菜单退出。2) 取消原生警告,确认窗口、托盘、host-core 和 sidecar 仍在。3) 再操作一次并确认退出,确认走有序关机。4) Windows/Linux 在关闭行为未设置时关窗,并在 D230 对话框选择退出;确认没有第二次警告。5) 以
PI_DESKTOP_BOOT_PROBE=1(及 supervision/capture 等价项)启动,确认app.quit()不弹对话框。6) 在打包的 Windows NSIS 或 Linux AppImage 安装上,下载更新并选择“重启以更新”;确认退出警告不再出现,安装器完成升级并重新拉起应用。 - 预期:误触显式退出可以取消。已经在关闭行为对话框选择退出的用户不会再被问一次。自动化探针不会被警告挡住。应用内更新重启同样不会被警告推迟:平台安装器先于
app.quit()启动,应用一旦超出其等待窗口安装器就会中止,因此更新触发的退出必须立即走有序关机 —— 用户选择该操作时已经确认了重启。见 ADR 0022。 - 链接规格:
04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、08-meta/decisions-log.md(D216、D230、D363)、ADR 0022 - 验收:A、质量
- 里程碑:M5
- 状态:单元已覆盖(
close-behavior-tray.test.mjs断言探针与更新重启两项豁免,auto-update.test.mjs断言安装闩锁先于quitAndInstall设置);原生对话框旅程为草稿
E2E-209:从本地智能体存储导入模型配置
- 前提条件:本机存在 Claude Code / Codex / OpenCode / Pi / CC Switch 的提供商配置。
- 步骤:1) 打开设置 → 导入 → 模型配置。2) 扫描并导入与现有行凭据不同的配置。3) 再次导入相同凭据。4) 确认 Composer 能选中新导入的提供商,密钥不进入渲染器。
- 预期:规范化端点、API 风格和凭据都相同才视为等价;不同密钥在同一端点创建独立行。重复导入幂等跳过。见 ADR 0179 / ADR 0188 / D342 / D351。
- 链接规格:
04-ux/06-settings-ia.md、04-ux/08-component-spec.md、ADR 0179、ADR 0188 - 验收:B、质量
- 里程碑:M6+
- 状态:单元已覆盖;完整 UI 旅程为草稿
E2E-213:Composer 模型菜单首帧保持已配置别名
- 前提条件:有一个可运行的提供商,至少有一个已配置模型带非空别名,且该模型有缓存或可发现的模型记录。应用已重启,或提供商模型缓存已失效。
- 步骤:1)打开 Composer 模型 × 推理芯片。2)立即进入“模型”子菜单并观察首个可见帧。3)保持菜单打开,直到缓存/实时模型刷新完成。4)关闭并重新打开菜单。
- 预期:首个可见行已经显示配置别名(无别名时显示配置 ID),加载期间列表不会闪空,也不会把别名替换成目录名称或 wire ID。刷新后和下一次打开仍保持同一个可见名称;选择该行仍发送配置的模型 ID。
- 链接规格:
03-runtime/13-model-catalog-and-selection.md、04-ux/08-component-spec.md - 验收:质量(首帧稳定)、B(模型选择)
- 里程碑:M6+
- 状态:单元/源合同已覆盖(
composer-models.test.mjs);完整 UI 路径仍需运行器验证(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-220:本地 MCP 控制驱动运行中的桌面
- 前提条件:使用
PI_DESKTOP_MCP_CONTROL=1和干净配置启动 PI-Desktop。 有可用的本地项目目录,Electron 用户数据目录可写,桌面已完成后端启动。 - 步骤:1)读取
mcp-control.json,使用其中的 URL 和 bearer token。2)调用initialize、tools/list和pi_control_describe。3)调用pi_project_open打开 fixture 项目。4)调用pi_session_create、pi_session_get和pi_agent_status。 5)调用pi_agent_prompt,观察现有桌面会话变更事件选中目标会话。6)使用pi_desktop_invoke调用一个已审查的只读操作。7)不带确认调用pi_session_delete,再使用confirm: true重试。8)停止应用并检查清单。 - 预期:未认证请求返回 401;
initialize遇到不支持的协议版本时协商为2025-06-18;已认证的 MCP 握手和工具目录成功;项目、会话、Agent 操作使用与渲染器 相同的 IPC 校验和主机权限边界;变更性调用刷新/选中可见项目和会话,而pi_session_get不会;破坏性操作和pi_session_configure在确认前失败;pi_control_describe不包含密钥写入、plugin/loadDev或原生选择器通道;密钥形态 字段会被剥离;端点只绑定回环;禁止的 Origin 和不支持的协议版本头会被拒绝;关闭时 清单变为active: false。 - 链接规格:
02-architecture/01-architecture.md、03-runtime/01-ipc-protocol.md、05-security/01-security.md、ADR 0203、D370、D372 - 验收:A(应用控制)、C(会话)、安全、质量
- 里程碑:M6+
- 状态:由
apps/desktop/test/mcp-control.test.mjs覆盖 MCP 协议/单元;完整 Electron 旅程已记录,仍按策略延后
受信任扩展场景(R7 v1)
以下场景是 D387 / ADR 0214 与 07-plugins/16-trusted-extensions.md 的验收目标;无头 runner 会在运行时的隔离临时目录中生成六个插件形态 fixture。
E2E-241:发现列出受信任扩展,启用是显式的
- 前置条件(D388):一个含
index.ts的 pi 扩展目录hello/;一个声明了contributes.agentExtensions与agent.extension且激活范围限定到夹具项目的插件包; 一个范围之外的第二个项目。 - 步骤:1)插件页 → 导入 pi 扩展,接受确认,选择
hello/。2)在夹具项目发送提示。 3)打开导入插件的行详情。4)禁用该插件并发送提示。5)切换到第二个项目并发送提示。 6)加载一个 manifest 列出agentExtensions却没有权限的插件。 - 预期:导入在
plugins/imported/hello生成持有agent.extension的 manifest,插件 列表显示agentExtension能力和权限标记;下一回合列出其工具,行显示loaded及已 注册名称;禁用插件后运行时重建,下一回合没有扩展工具;限定项目的插件在其项目之外 不贡献任何模块;没有权限的 manifest 以PLUGIN_INVALID被拒绝;~/.pi/agent/settings.json永不被写入。 - 链接规格:
07-plugins/16-trusted-extensions.md§2、§3、§11;D007;D387 - 验收:安全、质量
- 里程碑:MVP 后(R7 v1)
- 状态:部分自动化(
pnpm test:e2e:trusted-extensions);无头旅程覆盖插件发现/投影、项目范围、加载状态和诊断;原生选择器导入与显式启用仍需渲染器/平台验证
E2E-PLUGIN-imported-pi-package-wrapper:导入包的模块类型不妨碍插件初始化
- 前置条件:隔离的本地 Pi 包分别声明
type: module、type: commonjs或不声明type,各自包含扩展和技能贡献。另一个夹具模拟旧版导入的 ESM 包,包含生成的 CommonJSmain.js及指向它的 manifest。 - 步骤:运行
node --test apps/desktop/test/imported-package-skills-runtime.test.mjs。 使用生产导入器生成各个插件,通过PluginRuntime与真实子进程插件宿主加载, 读取技能目录与正文,并检查声明的扩展。对旧版夹具重新导入且不加载旧副本,再加载旧副本。 - 预期:每份新 manifest 都指向实际存在的
main.cjs,三类包均初始化成功。 源包及两份复制的package.json字节保持一致。重新导入得到不同的路径和 id 并成功 加载;生成新副本时旧 manifest、包装器和包文件保持原样。加载旧副本会把生成的main.js就地改写为main.cjs,复制的package.json字节不变,并初始化成功。 自定义过的main.js不会被改写。 - 关联规范:
07-plugins/16-trusted-extensions.md§3.2;ADR 0215。 - 验收:质量
- 状态:已实现导入到插件宿主的自动化夹具。合入最新
origin/main后,在已提交的 任务候选上执行,并在交付证据中记录候选、基线、结果和环境。本夹具不覆盖原生选择器、 npm 依赖安装、Windows 运行或模型回合中的第三方扩展执行。
E2E-PLUGIN-imported-pi-package-skills:显式导入包后按插件权限提供技能
- 前提条件:一个本地夹具包位于 npm 风格的
node_modules/@fixture/package-skills路径,声明pi.extensions和pi.skills。 技能包含直接指定的 Markdown 文件、含SKILL.md的目录,以及两个不同目录都含SKILL.md的集合。另有引用文件、素材、node_modules-note.txt资源和包内node_modules依赖目录;第二个夹具仅声明pi.skills。无需下载或执行第三方代码, 也无需安装依赖。 - 步骤:
- 通过插件页 → 导入 pi 扩展选择混合包,检查生成的 manifest 和复制的资源。 无界面验证时,将明确选定的路径传给同一个导入器。
- 在真实
PluginRuntime中加载生成目录,检查getSkills(),并通过loadSkillBody(id)读取每份文档。 - 重新加载时只授予
agent.extension,随后只授予agent.prompt.inject; 卸载插件后再次尝试读取旧技能 ID。 - 导入仅技能包,确认辅助
index.js不成为扩展。对生成的双技能夹具调用真实 Hostplugins.installFromPath,列出插件,读取安装后的 manifest 和技能文件,最后卸载。 - 尝试绝对路径、
..、缺失或不受支持的文件、包内依赖路径及后代符号链接; 超出 32 个技能或 256 个目录的扫描限制,并检查失败后的清理。
- 预期:四项技能均以独立且稳定的 ID 进入目录,同名
SKILL.md不相互覆盖; 加载返回正确正文并移除 frontmatter。导入的扩展保持独立贡献。资源和相对路径复制 后仍然存在;npm 安装路径的祖先目录不导致整个包被排除,只排除所选包内的依赖目录 路径段。缺失技能授权时技能目录为空,并产生既有权限审计;重新授权后 ID 不变。 卸载清除两类贡献,旧技能 ID 返回NOT_FOUND。仅技能包只声明agent.prompt.inject, Host 报告skills能力,并保留两份技能文档。无效声明导入失败,不读取包外数据, 也不留下部分复制的插件。不会自动导入~/.pi或执行 npm、包生命周期脚本。 - 链接规格:
07-plugins/16-trusted-extensions.md§3.2;07-plugins/02-plugin-manifest-schema.md;D007 - 验收:E(工具与权限)、G(插件)、品质
- 里程碑:MVP 后(R7 v1)
- 状态:部分自动化。
imported-package-skills.test.mjs覆盖导入发现、资源复制、 权限和无效路径;imported-package-skills-runtime.test.mjs通过真实插件宿主子进程 加载生成的空操作插件,验证技能目录、正文、权限撤销与恢复,以及卸载。2026-09-13 另用临时双技能夹具完成真实 Hostplugins.installFromPath→plugins.list→ 安装后 manifest/正文读取 →plugins.uninstall验证,仅报告agent.prompt.inject权限和skills能力。本场景尚未执行原生选择器、插件行渲染或提供商回合调用导入 技能的测试,不声称完整桌面旅程已通过。
E2E-242:扩展工具与 hooks 在回合中生效
- 前置条件:一个已启用的夹具扩展,注册工具
fx_add,在before_agent_start向系统提示追加标记,在tool_call以理由阻止bash,在tool_result替换fx_add的输出。 - 步骤:1)在 Agent 模式开始一个回合,夹具模型先调用
fx_add再调用bash。 2)检查 provider 请求。3)检查工具结果。4)切换到 Plan 模式重复。5)注册第二个 声明名为read的工具的扩展。 - 预期:系统提示带有标记;
fx_add在 sidecar 内执行,无权限提示,结果为替换 值;bash以扩展的理由被阻止,且阻止在记录中可见;审计记录含扩展 id、工具名 和耗时,不含参数;Plan 模式下fx_add遵循非核心模式门控;read冲突被拒绝并 记诊断,核心工具不变。 - 链接规格:
07-plugins/16-trusted-extensions.md§6、§7;ADR 0214 - 验收:B(agent)、安全、质量
- 里程碑:MVP 后(R7 v1)
- 状态:部分自动化(
pnpm test:e2e:trusted-extensions);Agent 模式工具调度、ToolSearch 延迟、hooks、阻止和结果替换已覆盖;Plan 模式门控与核心工具冲突仍需额外验证
E2E-243:扩展命令与 UI 提示经渲染层往返
- 前置条件:一个���启用的夹具扩展,注册命令
greet,依次调用ui.input、ui.select、ui.confirm、ui.notify,并重命名会话。 - 步骤:1)打开全局搜索并检查 Commands 区。2)在 composer 运行
/greet。 3)回答每个提示。4)再次运行/greet并在输入提示打开时中止回合。5)在没有活动 会话时运行/greet。6)连接远程控制器(夹具)并运行/greet。 - 预期:
greet排在内置与插件命令之后并带扩展标签;每个提示显示扩展标签与 路径;回答按顺序到达扩展;toast 出现;会话被重命名且session_info_changed触发;被中止的提示解析为undefined且命令结束;无会话时条目禁用并带提示;远程 控制下提示以UNSUPPORTED失败且命令报告该错误。 - 链接规格:
07-plugins/16-trusted-extensions.md§8、§9、§10;07-plugins/09-plugin-command-palette.md - 验收:A(应用控制)、质量
- 里程碑:MVP 后(R7 v1)
- 状态:部分自动化(
pnpm test:e2e:trusted-extensions);全局/Composer 命令发现、提示 broker 往返、中止、会话重命名、exec 和 Host 队列已覆盖;无会话与远程控制仍需额外验证
E2E-244:不支持的 API、加载错误与处理器超时降级为诊断
- 前置条件:三个已启用的夹具扩展:一个在顶层导入
@earendil-works/pi-tui并 调用ui.setWidget;一个模块在加载时抛出;一个context处理器永不返回。 - 步骤:1)开始一个回合。2)打开每个条目的诊断抽屉。3)等待超过 30 秒处理器 限制。4)禁用抛出的扩展并开始另一个回合。
- 预期:pi-tui 导入成功,
setWidget返回惰性dispose,每个成员记录一条诊断; 抛出的扩展显示error及消息和堆栈,composer 显示一行提示,其余扩展仍加载; 停滞的处理器在 30 秒后被放弃并记诊断,回合以未修改的上下文完成;禁用后提示在 下一回合边界消失,且没有运行中的回合被打断。 - 链接规格:
07-plugins/16-trusted-extensions.md§4.2、§4.4、§5、§6 - 验收:质量
- 里程碑:MVP 后(R7 v1)
- 状态:部分自动化(
pnpm test:e2e:trusted-extensions);加载错误与惰性 terminal-UI API 会降级为诊断;停滞处理器超时和边界禁用旅程仍需额外验证
E2E-245:打包后的 sidecar 经 jiti 加载 TypeScript 扩展
- 前置条件:桌面应用的打包构建;夹具
~/.pi/agent/extensions/typed.ts使用 TypeScript 语法,导入@earendil-works/pi-coding-agent与typebox,并注册一个 工具。 - 步骤:1)启动打包应用。2)启用
typed.ts。3)开始一个夹具模型调用该工具的 回合。4)检查 sidecar 打包清单中三个 pi 包的版本。 - 预期:扩展加载无转译或解析错误;别名导入解析到 sidecar 的副本;工具执行; 三个 pi 包版本一致且 CI 版本锁检查通过。
- 链接规格:
07-plugins/16-trusted-extensions.md§4.2、§13;ADR 0214 - 验收:质量、发布
- 里程碑:MVP 后(R7 v1,作为打包 spike 首先交付)
- 状态:由
packages/agent-runtime/src/extensions/bundle.test.ts提供单元覆盖(临时目录中运行 esbuild bundle);打包应用 jiti 旅程仍为草稿,无头 runner 不会伪造该覆盖。
E2E-PLUGIN-import-extension-installs-dependencies:导入带 npm 依赖的扩展会在首次加载前安装依赖
- 前置条件:本地 pi 扩展包包含
package.json、pi.extensions、固定版本的纯 JavaScript 依赖[email protected]和workspaces字段,且没有node_modules;workspace 构建产物和 npm 可用。 - 步骤:1)从本地目录生成导入插件。2)运行真实的有界安装器。3)检查复制后的 package、lockfile、 已安装模块、生命周期标记和受信任扩展加载报告。
- 预期:插件根目录复制了去除
workspaces字段的package.json。安装器运行两个 registry-only、--ignore-scripts的 npm 步骤;所有 lockfile 的resolvedURL 都只指向 registry,node_modules/is-number存在,没有写入生命周期标记,受信任扩展 runner 报告loaded并注册依赖驱动的命令。 - 链接规格:
07-plugins/16-trusted-extensions.md§3.2、§10.2;ADR 0244 - 验收:安全、质量
- 里程碑:MVP 后(R7 v1)
- 状态:由
pnpm test:e2e:plugin-import-deps自动化覆盖确定性的安装边界;完整的 picker/renderer/回合旅程 保留为独立验证面。
E2E-PLUGIN-import-extension-reports-missing-dependency:依赖安装失败或依赖无法加载会被呈现,绝不静默
- 前置条件:三个本地 pi 扩展包的
package.json分别使用不支持的file:、git 和 HTTP tarball 依赖源;都没有node_modules或 lockfile。 - 步骤:1)生成每个导入插件。2)调用真实依赖安装器。3)检查返回的错误和生成的插件目录。
- 预期:每次失败都被明确报告并发生在 npm 启动前;导入插件和 manifest 仍保留,但不会留下可加载的
node_modules或生成的 lockfile。renderer toast/加载错误旅程单独覆盖。 - 链接规格:
07-plugins/16-trusted-extensions.md§3.2、§4.4、§10.2;ADR 0244 - 验收:安全、质量
- 里程碑:MVP 后(R7 v1)
- 状态:由
pnpm test:e2e:plugin-import-deps自动化覆盖确定性的 registry 源拒绝边界;renderer 警告 toast 和load_error行为保留为独立验证面。
E2E-234:工作区安全拒绝名单与忽略层
- 前提条件:一个项目包含
.env、.env.example、server.pem、keys/id_rsa、notes.txt、node_modules/pkg/index.js、generated/out.txt、debug.log,以及 根目录下写有generated/的.pi-desktopignore。每个文件都包含单词needle。 会话为 Agent 模式,权限模式auto。 - 步骤:1)请求
Read.env,再请求Read.env.example。2)请求Write到keys/id_rsa。3)对needle运行无范围的Grep和Glob。4)以path: node_modules/pkg和path: generated运行Grep。5)分别在安装了系统rg和设置PI_DESKTOP_DISABLE_RG=1的情况下重复步骤 1。 - 预期:步骤 1 和 2 以
WORKSPACE_PATH_DENIED失败,.env.example的读取成功, 且不会创建keys/id_rsa文件。无范围搜索只列出notes.txt和.env.example:.env、server.pem、node_modules、generated和debug.log都不出现。显式路径 搜索各返回一条命中。进程内遍历器与rg快速路径产生相同的文件集。 - 链接规格:
03-runtime/15-workspace-ignore-rules.md、03-runtime/08-error-codes.md§3.3、D032 - 验收:B(工作区工具)、安全
- 里程碑:M3+
- 状态:由
crates/host-core/src/tools/mod.rs(security_denylist_blocks_read_write_edit_and_hides_search_results、default_ignores_and_workspace_ignore_file_hide_unscoped_walks_only)和tools/ignore_rules.rs单元覆盖;Electron 旅程已记录,并按无本地 E2E 策略延后
E2E-235:悬空软链无法写到工作区之外
- 前提条件:一个项目包含
dangling -> /tmp/outside/planted.txt(目标不存在) 和inner -> ./not-yet.txt。Agent 模式,auto权限。 - 步骤:1)请求
Write到dangling。2)请求Write到dangling-dir/new.txt, 其中dangling-dir -> /tmp/outside/dir。3)请求Write到inner。 - 预期:步骤 1 和 2 以
PATH_OUTSIDE_WORKSPACE失败,/tmp/outside下没有任何 东西出现。步骤 3 在项目内创建not-yet.txt。软链环路以 canonicalize 错误失败, 而不是挂起。 - 链接规格:
03-runtime/03-tools-and-permissions.md、03-runtime/15-workspace-ignore-rules.md§3 - 验收:B、安全
- 里程碑:M3+
- 状态:由
crates/host-core/src/workspace.rs(blocks_dangling_symlink_escape、dangling_symlink_inside_workspace_resolves_to_its_target、dangling_symlink_loop_is_rejected)单元覆盖
E2E-236:插件桌面控制需要用户的原生同意
- 前提条件:一个被授予
desktop.control的开发插件,其面板调用pi.desktop.invoke({ operation: "session/delete", args: [id], confirm })。 存在一个可丢弃的会话。 - 步骤:1)以
confirm: false调用。2)以confirm: true调用并在对话框上按 Escape。3)以confirm: true调用并点击拒绝。4)以confirm: true调用并点击一次 允许。5)调用一个read操作。 - 预期:步骤 1 以
CONFIRMATION_REQUIRED失败且不出现对话框。步骤 2 和 3 以PERMISSION_DENIED失败;会话仍然存在。对话框点名session/delete和目录描述, 绝不显示面板撰写的文本。步骤 4 删除会话且侧边栏刷新。步骤 5 不显示对话框。 每次调用都连同插件 id、操作和风险等级记入审计。 - 链接规格:
07-plugins/03-plugin-api.md(桌面控制)、07-plugins/04-plugin-security.md§8.2、07-plugins/13-plugin-permissions-matrix.md、ADR 0203、ADR 0208、D370、D372、D377 - 验收:D(插件)、安全
- 里程碑:M6+
- 状态:由
apps/desktop/test/plugin-desktop-control.test.mjs运行时覆盖; 原生对话框旅程已记录,并按无本地 E2E 策略延后
E2E-PLUGIN-session-orchestrator-real-workers:Session Orchestrator 创建并行持久化 Worker
- 前提条件:已安装并启用市场中的
pi.session-orchestrator插件;父 Agent 会话已配置可认证的 provider/model 和项目路径,并处于 Agent 模式。 - 步骤:1)请求父 Agent 并行审查 Frontend、Electron 和 Rust。2)确认
SessionTask.spawn返回三个不同的原始持久化sessionId,且每个 Worker 出现在普通会话列表中。 3)确认三个 Worker 都收到 prompt,不使用session/fork,并可以并行运行。 4)在 Worker 活动期间调用SessionTask.status,确认不读取 Worker transcript。 5)使用默认等待上限调用SessionTask.wait,再对每个 Worker 调用result。 6)从 Agents 面板打开一个 Worker,使用返回的原始sessionId发送 follow-up, 并停止另一个 Worker。7)重启插件,确认关系列表和持久化 Worker 会话仍然可用。 在已有大量会话的情况下重复并行创建步骤,并保持父会话可见。 - 预期:每个 Worker 都是真实持久化会话,继承父会话的项目、模型、thinking 和权限上限, 创建时拥有独立的空 transcript。父会话只收到有界的最终 report;完整 Worker transcript 仍可在各自会话中查看。
sessionId是 Worker 唯一的规范身份,后续send复用同一会话和 上下文,不创建替代会话。状态和面板刷新使用有界的轻量轮询;wait在其等待上限内返回timedOut,不占满宿主工具超时时间。cancel中止但不删除,无关会话和现有 Task 系列 保持不变,且不发生 localhost MCP 调用或 token 访问。Worker 不能再创建 Worker, 不属于调用方的 Session ID 必须被拒绝,并发上限超出时必须安全失败。spawn指定用户 未勾选「可供 AI 自动调度」的模型时,在创建 Worker 之前以PERMISSION_DENIED拒绝; 省略modelKey,或写出默认模型自己的键,仍按继承处理。Worker 创建和 prompt 通知突发时,会话列表刷新串行执行并合并,同时保留最终 Worker 列表和前台会话。 后到达的通知等待后续读取,不会因复用 Worker 创建之前已开始的读取而丢失。 - 链接规格:
07-plugins/03-plugin-api.md、07-plugins/04-plugin-security.md、07-plugins/11-plugin-storage-isolation.md、03-runtime/01-ipc-protocol.md、03-runtime/06-host-rpc-protocol.md、03-runtime/11-provider-model-system.md、 ADR 0237、ADR subagent-model-opt-in - 接受:C(并行持久化会话)、D(插件安全性)、品质
- 里程碑:M6+
- 状态:host ledger 覆盖由
pnpm test:e2e:collaboration自动化;marketplace 插件测试覆盖插件运行时,host-core 和 desktop 单元测试覆盖新增的宿主原子能力。完整真实 provider/Electron 旅程仍需在具备条件的 runner 中验证,遵循无本地 E2E 策略
E2E-SESSION-independent-top-level-communication:SessionTask 发现并与现有会话通信
- 前提条件:已安装并启用 marketplace 中的
pi.session-orchestrator插件。两个现有 Agent 会话通过普通的新建任务流程创建,彼此不是 Session Orchestrator worker。调用者是已配置认证 provider 的活动 Agent 会话。 - 步骤:1)调用
SessionTask,使用action: "list",通过持久化sessionId找到两个独立会话。2)向其中一个独立会话发送消息,确认消息进入该会话的 inbox。3)从目标会话向原会话回复。4)使用返回的 ID 调用status和result,检查两个会话的转录。 - 预期:
list返回有界的可通信 Agent 会话引用,不要求插件拥有历史,也不把它们当作 worker。使用真实目标 Session ID 的send可以双向工作,保留每个目标已有的模型、项目、上下文和权限,不创建替代会话。host 记录源/目标来源信息,结果绑定真实持久回合,列表不包含转录、项目路径、凭据或消息预览。非 Agent 会话仍按既有 host 策略拒绝。 - 链接规格:
07-plugins/03-plugin-api.md、07-plugins/04-plugin-security.md、03-runtime/01-ipc-protocol.md、03-runtime/04-data-storage.md、ADR 0239、ADR 0240 - 验收:C(对话与流式)、D(插件安全)、G(插件)、品质
- 里程碑:M6+
- 状态:host 发现和双向投递由
pnpm test:e2e:collaboration自动化;插件和 host-core 回归覆盖已自动化。真实 provider/Electron 多会话旅程仍需在具备条件的 runner 中验证,遵循无本地 E2E 策略
E2E-SESSION-completion-notice-allows-silence:可信完成通知允许无需确认即结束
- 前提:候选提交拥有独立构建的 host-core 和 runtime sidecar;本地 SSE 提供商确定性返回可见文本或成功的空响应,无需真实凭证。
- 步骤:1)通过真实 Host 协作账本及 sidecar 投递任务,读取成功结果并完成 协调者总结。2)使用生产 Main 输入解析器解析排队的完成回调,再让接收会话 收到空 SSE 响应。3)在同一接收运行时依次发送普通用户请求、复制的完成 来源文本、账本 task 和 message,并都返回空响应。
- 预期:原结果保持不变。完成通知只有一次提供商请求、无错误、一次终止 生命周期,账本状态为 completed,且不产生确认回调。每个普通输入仍只重试 一次并以
EMPTY_MODEL_RESPONSE结束,且静默通知之后接收方发出的任何请求 都不携带空的 assistant 消息。单测另覆盖缺少回复目标 ID、目标不符、工具 批次消耗例外、provider 重试保留例外、被接受的用户 steering 进入上下文后 撤销例外,以及被接受的静默不进入运行时条目和 pi 转录状态。 - 关联规格:
03-runtime/02-agent-runtime.md§5e、03-runtime/08-error-codes.md、ADR 0239(D446 修订段) - 验收:C(会话与流)、D(来源)、质量
- 里程碑:M6+
- 状态:由
pnpm test:e2e:session-completion在独立 worktree 中对已提交并 rebase 的候选版本自动验证。测试驱动真实 Host RPC、生产来源解析器、sidecar 和本地 SSE,并在 Host 结算前持久化运行时消息;不覆盖 Electron 队列/outbox 界面或真实提供商。候选与基线 SHA 及结果记录于验证报告;既有账本测试另用pnpm test:e2e:collaboration运行。
E2E-SESSION-hover-card-model-and-links:会话 hover 卡片展示可读模型并支持创建关系导航
- 前提条件:应用中存在一个协作创建的会话、一个独立会话,以及带可读目录名称的 provider/model。侧边栏包含这两个会话。
- 步骤:1)悬停或键盘聚焦协作创建的会话。2)检查模型元数据、创建者引用和已创建会话列表。3)使用键盘激活创建者引用和一个已创建会话引用。4)删除(或以其他方式移除)一个被引用的会话,或使用一个引用已经失效的会话,然后重新查看该卡片。5)检查独立会话的卡片。
- 预期:卡片显示 provider 可读名称和模型显示名称,而不是 provider ID。协作创建的会话显示其创建者,创建者显示有界的已创建会话列表。仍然存在的引用是可用键盘聚焦、带可访问打开名称的原生按钮;激活后打开对应持久会话并聚焦 Composer。会话已不存在的引用以文本呈现并带有“不可用”提示,且不是可键盘聚焦的导航控件;激活一个已不存在的会话(例如在快照与点击之间被删除的会话)会显示可见错误,而不是切换到空转录。独立会话保持有效本地会话,不伪造创建者链接。hover 轮询有界且不加载转录:超过截止时间的读取会被放弃,窗口失焦时保持较慢的空闲轮询,而不是停止或超时失控。
- 链接规格:
03-runtime/01-ipc-protocol.md§5.7、03-runtime/04-data-storage.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、ADR 0240 - 验收:C(对话与流式)、品质
- 里程碑:M6+
- 状态:源码契约和投影测试已自动化;渲染后的指针/键盘验证仍需在 runner 中执行
E2E-237:插件 fetch 在每次重定向时重新检查出网
- 前提条件:一个声明
net.domains: ["allowed.test"]和net.fetch的开发插件。allowed.test上的本地服务器对/hop返回 302 到http://undeclared.test/leak, 对/ok返回 200,对/limited返回 429 且带Retry-After: 2。 - 步骤:1)调用
pi.net.fetch({ url: "https://allowed.test/ok" })。2)调用pi.net.fetch({ url: "https://allowed.test/hop" })。3)调用pi.net.fetch({ url: "https://allowed.test/limited" })。 - 预期:步骤 1 返回 200。步骤 2 以点名
undeclared.test的PERMISSION_DENIED失败,未声明的服务器没有记录到任何请求。审计日志显示被拒绝的那一跳。步骤 3 原样返回 429 及其Retry-After头,服务器只记录到一次请求,审计日志记为ok: false并带有它通告的retryAfter—— 宿主不重试任何东西。 - 链接规格:
07-plugins/04-plugin-security.md§8.0、07-plugins/03-plugin-api.md§7 - 验收:D、安全
- 里程碑:M4+
- 状态:由
apps/desktop/test/plugin-egress.test.mjs运行时覆盖 (逐跳出网与失败调用的审计)
E2E-238:未知会话的工具请求不会回退
- 前提条件:host-core 运行中;一个 JSON-RPC 探针接到其 stdio 上。
- 步骤:1)发送
sessionId: "missing"的tools.execute,请求ReadREADME.md。2)以同一个 id 发送plans.enter。 - 预期:两者都以
SESSION_NOT_FOUND失败(分别对应数字码1007和PLAN_SESSION_NOT_FOUND);最近打开的工作区下没有任何文件被读取。 - 链接规格:
03-runtime/06-host-rpc-protocol.md§7、03-runtime/08-error-codes.md§3.1 - 验收:B、安全
- 里程碑:M3+
- 状态:由
crates/host-core/src/rpc/mod.rs(temporary_session_uses_its_own_scratch_workspace)单元覆盖
E2E-239:旧版构建会指出数据 schema 更新,而不是循环重启
- 前提条件:数据目录上次由更新版 PI-Desktop 打开,其 host-core 已把 schema 迁移到超出当前构建支持的版本。
- 步骤:1)用旧版打包应用打开该数据目录。2)观察横幅和
logs/app/runtime.log。 - 预期:host-core 只退出一次;日志中没有后续重启尝试。致命横幅说明当前 PI-Desktop 比本地数据更旧,显示两个 schema 版本号,并提示安装更新版本。数据 目录未被修改。
- 链接规格:
03-runtime/07-process-model.md(启动结果) - 验收:B
- 里程碑:M3+
- 状态:由
apps/desktop/test/host-boot-diagnostics.test.mjs源码契约覆盖
E2E-240:Apple Silicon 上的 Intel macOS 构建会指向原生下载
- 前提条件:Apple Silicon Mac;安装并通过 Rosetta 2 运行 x64 macOS 包。
- 步骤:1)启动应用。2)阅读标题栏下方的横幅。3)点击其关闭操作。
- 预期:应用正常启动。一条可关闭的提示说明当前是 Intel 构建运行在 Apple Silicon 机器上,并要求安装 Apple Silicon 构建。关闭后本次会话不再显示;原生 arm64 包不显示任何提示。
- 链接规格:
03-runtime/07-process-model.md(启动结果) - 验收:B
- 里程碑:M3+
- 状态:由
apps/desktop/test/host-boot-diagnostics.test.mjs源码契约覆盖
E2E-246:超大会话重试不会把整份 transcript 放进一条 RPC
- 前提条件:Agent 会话的实时转录本有数千条消息(
session.replaceMessages的 JSON-RPC 行会达到数十 MB),且最后一轮用户回合已完成或失败。 - 步骤:1)重试或重新生成最后一条用户提示。2)检查 host RPC、
turns.status、live jsonl 和message_revisions。3)若第一次仍显示为运行中,立即再点一次重试。 - 预期:Electron main 只调用带
fromMessageId的session.truncateFrom,不把 discarded 数组交给session.saveRevision,也不调用session.replaceMessages。保留前缀不会出现在 NDJSON 请求里。残留 running 回合在beginTurn前被标为aborted。被丢弃的尾巴已归档。UI 错误不是host RPC timeout: session.replaceMessages。 - 链接规格:
03-runtime/01-ipc-protocol.md、03-runtime/04-data-storage.md§4.9/§7、03-runtime/06-host-rpc-protocol.md§4、ADR 0216、D390 - 验收:C(对话)、F(持久化)、Quality
- 里程碑:M6
- 状态:由 host-core 单元测试覆盖(2026-09-10):
truncate_from_drops_the_tail_and_archives_the_discarded_branch、truncate_from_rejects_an_unknown_message、truncate_from_refreshes_the_stamped_revision、truncate_from_rpc_cuts_without_shipping_the_kept_prefix。桌面行程仍为草稿。
E2E-247:Windows 上 stdin EOF 后 host-core 退出,超大 RPC 立即失败
- 前提条件:已安装 Alt+Space 键盘钩子的 Windows host-core;一条超过 64 MiB 的 JSON-RPC NDJSON 请求。
- 步骤:1)向正在运行的 host-core 发送 stdin EOF。2)从 Electron 调用一条序列化后超过 64 MiB 的主机方法。3)若该行仍到达 host-core,检查
LIMIT_EXCEEDED应答的 id。 - 预期:stdin EOF 后 host-core 在 130 秒内退出。Windows 键盘钩子不会让 stdout 写线程一直活着。Electron 在
stdin.write之前以LIMIT_EXCEEDED拒绝超大调用。主机侧超大应答使用从截断前缀取出的请求 id,而不是null。UI 错误不是host RPC timeout。 - 链接规格:
03-runtime/07-process-model.md、03-runtime/06-host-rpc-protocol.md§7、ADR 0217、D391 - 验收:Quality
- 里程碑:M6
- 状态:由单元测试与源码契约覆盖(2026-09-11):
start_does_not_keep_the_stdout_channel_open、peek_jsonrpc_id_reads_a_string_id_from_a_truncated_prefix、apps/desktop/test/windows-host-runtime.test.mjs(弱引用)、apps/desktop/test/rpc-lifecycle-contract.test.mjs(客户端预检)、packages/shared/src/rpc-limits.test.ts。桌面行程仍为草稿。
E2E-250:上下文用量显示偏好切换检查器引导数值
- 前提条件:一个 Agent 会话已完成至少一个报告了令牌用量的回合。可打开设置 → 全局 AI → 默认项。
- 步骤:
- 确认输入框工具栏上下文环显示剩余容量(环近乎满圆,百分比 ≈ 剩余 %,tooltip 与 aria-label 使用剩余词汇)。
- 打开设置 → 全局 AI → 默认项,将上下文用量显示分段控件从「剩余」切到「已用」。
- 回到聊天,检查上下文环:圆弧按
usedRatio填充(低占用时近乎空环),百分比显示 ≈ 已用 %,弹层标题显示已用令牌 + 百分比,tooltip/aria-label 使用已用容量词汇。 - 确认警告/临界颜色仍按剩余容量判定:剩余 > 25 % 时环保持中性色,即使已用 % 较高。
- 切回「剩余」,确认恢复原始显示。
- 预期:显示模式一致翻转圆弧、百分比、令牌数、标题、tooltip 与 aria-label。两种模式下颜色阈值始终基于剩余容量。新配置文件的默认值为「剩余」。
- 链接规格:
04-ux/06-settings-ia.md、04-ux/08-component-spec.md、ADR 0223、08-meta/decisions-log.md(D398) - 验收:C(会话与流式)、Quality(偏好)
- 里程碑:M5
- 状态:单元测试覆盖(
context-usage.test.mjs、settings-general.test.mjs);完整场景为草稿
E2E-253:项目组支持拖拽和键盘手动排序
- 前提条件:侧边栏至少有 3 个项目组,其中包含一个已固定或已归档项目;每个项目都有稳定的 host workspace/path/directory。
- 步骤:
- 按住项目标题并拖到另一个项目组上方或下方,观察插入线后释放。
- 单击项目标题,确认仍会选中项目并切换折叠,且不会改变顺序。
- 聚焦同一标题,按
ArrowUp或ArrowDown各移动一次。 - 重启应用并检查项目顺序。
- 从重排后的项目打开会话,确认 host workspace、path 和 directory 未改变。
- 预期:项目组按释放后的顺序显示,手动顺序在重启后保留。没有重排手柄,也没有 400ms 等待。插入线标出放置位置。标题支持键盘排序,短按不会重排,
Escape可取消拖动,既有固定/归档优先级保持不变;排序不会改变项目的 host workspace、path、directory 或会话排序。 - 链接规格:
04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、03-runtime/04-data-storage.md、08-meta/decisions-log.md(D399、D402、D403) - 验收:D(工作区)、F(持久化)、品质
- 里程碑:M5
- 状态:源代码契约覆盖(
app-store-sidebar.test.mjs、sidebar-preferences.test.mjs、sidebar-project-reorder.test.mjs);渲染桌面旅程为草稿
E2E-254:技能在第一个 Agent 回合即可加载
- 前提条件:当前项目至少有一个已激活的 Skill;已配置提供商;会话处于 Agent 模式,且存在另一个按需能力(例如
BrowserPreview或某个插件工具)。 - 步骤:
- 打开新的 Agent 会话,发送一条匹配该 Skill 描述的提示。
- 检查第一个 provider 请求及其工具列表。
- 确认模型直接用精确 id 调用
Skill,且没有先调用ToolSearch,返回内容就是技能正文。 - 在输入框发送
/<skill-id>,检查随后的回合。 - 把会话切换到 Plan 模式,再次检查工具列表。
- 禁用或移除全部 Skill,再发起一个 Agent 回合。
- 预期:只要技能目录非空,
Skill就随第一个请求下发,且绝不出现在# On-demand tools中,因此匹配任务与/skill-id调用都能直接加载正文,不再多一次发现往返。ToolSearch仍服务于其他按需能力,且永远不会返回Skill。Plan 模式不提供该工具与# Skills段落;目录为空时不注册任何Skill工具。 - 链接规格:
03-runtime/02-agent-runtime.md(§7.1)、03-runtime/03-tools-and-permissions.md(§2.1)、04-ux/04-builtin-commands.md(§8)、08-meta/decisions-log.md(D404)、ADR 0048、ADR 0219、ADR 0230 - 验收:C(对话与流)、E(工具与权限)、品质
- 里程碑:M5
- 状态:单元覆盖(
packages/agent-runtime/src/runtime.test.ts);渲染桌面旅程为草稿(除非明确要求,不本地运行 E2E)
E2E-255:顿号打开斜杠菜单
- 前提条件:可用中文输入法;输入框草稿为空;至少存在一条斜杠条目(内置别名、模板、插件命令或 Skill)。
- 步骤:
- 保持草稿为空,输入「、」并观察输入框。
- 继续输入命令名并确认高亮行。
- 输入一段在其他字符之间包含「、」的草稿。
- 发送一段首字符为「、」且未确认任何行的草稿。
- 预期:「、」被就地改写为
/,普通斜杠菜单以与直接输入/相同的过滤和键盘行为打开,光标停留在替换后的字符之后。出现在草稿其他位置的「、」保持原样,@文件菜单不会对该标点作出反应。 - 链接规格:
04-ux/04-builtin-commands.md(§9)、04-ux/08-component-spec.md(§11)、08-meta/decisions-log.md(D405)、ADR 0024、ADR 0231 - 验收:C(对话与流)、本地化、品质
- 里程碑:M2
- 状态:单元覆盖(
packages/shared/src/composer-trigger.test.ts、apps/desktop/test/composer-ime.test.mjs);渲染桌面旅程为草稿(除非明确要求,不本地运行 E2E)
E2E-CLONE-accepts-a-lan-remote-and-rejects-metadata
- 前提条件:首页项目切换菜单的「克隆 Git 项目」可用。
- 步骤:1)输入
https://192.168.1.5/org/repo.git、http://10.0.0.7/org/repo.git和[email protected]:org/repo.git。2)输入https://169.254.169.254/org/repo.git和https://metadata.google.internal/org/repo.git。3)输入https://github.com/org/repo.git和[email protected]:org/repo.git。 - 预期:用户自己填写的局域网或回环远端被接受并解析出文件夹名,因为这是用户自己的地址。云元数据主机仍然拒绝,
file:与带密码的 URL 也一样,都在git clone运行前拒绝。公网 GitHub HTTPS 与 SSH 仍解析成功。 - 链接规格:
04-ux/01-ui-ia.md、ADR 0247、ADR 0304、D416 - 验收:安全、D(工作区)
- 里程碑:M5
- 状态:单元覆盖(
apps/desktop/test/git-clone.test.mjs)
E2E-258:新建项目对话框可以直接从 Git 仓库开始
- 前提条件:从「项目」标题栏打开新建项目对话框(不需要已有项目);已安装
git。 - 步骤:
- 将来源切换到「Git 仓库」。
- 粘贴
https://github.com/octocat/Hello-World.git,确认项目名称自动填为Hello-World,再改成自定义名称。 - 选择克隆保存位置,确认位置行显示该文件夹。
- 确认创建,检查工作空间、侧边栏与项目归档。
- 重新打开对话框,切到「Git 仓库」,粘贴私网或非法远程地址。
- 预期:对话框把文件夹列表换成仓库地址输入框加克隆保存位置行,并保留同一个项目名称字段;地址解析成功且选定文件夹前,创建按钮保持禁用。确认后先在所选文件夹执行
git clone,项目创建仍由渲染器负责:克隆出的目录成为主要根,输入的名称命名该项目组。私网、回环、链路本地、带凭据和非法远程地址会让创建保持禁用(ADR 0247),且不写入任何文件夹。 - 链接规格:
03-runtime/01-ipc-protocol.md§9、04-ux/08-component-spec.md、ADR 0273、ADR 0233、ADR 0247 - 验收:品质(项目入口)、D(工作区)
- 里程碑:M5
- 状态:单元覆盖(
apps/desktop/test/project-create-dialog.test.mjs、apps/desktop/test/git-clone.test.mjs);渲染桌面旅程为草稿(除非明确要求,不本地运行 E2E)
E2E-257:导入到已归档项目后恢复其可见性
- 前提条件:一个持久项目已在渲染器侧边栏偏好中归档,并从默认侧边栏隐藏。一个核心导入候选携带该项目路径,测试插件可以使用明确的 host project id 导入会话。
- 步骤:
- 打开设置 → 项目归档,确认已归档项目仍可用,而默认侧边栏不显示它。
- 扫描并导入项目路径属于该已归档项目的核心候选。
- 确认项目和导入的会话出现在默认侧边栏,然后再次归档项目。
- 使用插件的
session.importBatch和现有项目 id,检查 host 刷新事件后的侧边栏。 - 在不导入任何内容时刷新会话,导入一个无路径会话,并重复导入已有会话。
- 预期:每个成功且新增的项目绑定导入都会清除对应规范化项目路径的渲染器归档状态,并使项目/会话可发现。普通刷新、无路径会话、跳过的导入,以及没有明确项目绑定的插件历史路径都保持归档状态不变;host 项目行和转录本不被删除或重建。
- 链接规格:
04-ux/06-settings-ia.md、04-ux/08-component-spec.md、03-runtime/04-data-storage.md、ADR 0236、D407 - 验收:C(对话与流)、F(持久化)、G(插件)、品质
- 里程碑:M6+
- 状态:由源代码契约与单元测试覆盖(
sidebar-session-groups.test.mjs、project-import-archive.test.mjs、plugin-session-refresh.test.mjs);渲染桌面旅程为草稿(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-IMPORT-codex-scan-filters-synthetic-titles
- 前提条件:一个 Codex 归档,其中的会话以合成注入开头(
# Context from my IDE setup:、# In app browser:、# Browser comments:、# Files mentioned by the user:、# Diff comments:、# Selected text:、# Review findings:、# AGENTS.md、You are Codex、<environment>),且至少一个会话的存储时间戳损坏或越界。 - 步骤:
- 对该归档运行设置 → 会话导入 → 扫描。
- 检查候选会话的标题与每条会话展示的 createdAt/updatedAt。
- 导入一个首条真实用户消息位于合成注入之后的会话。
- 预期:候选标题取自第一条真实用户消息——合成注入绝不作为标题出现,而以
#开头的真实粘贴内容(例如# Role: …)予以保留。用户消息全为合成的会话不作为候选出现。损坏或越界的存储时间戳回退到源文件的 mtime,绝不回退到导入时刻。 - 链接规格:
03-runtime/01-ipc-protocol.md、04-ux/06-settings-ia.md、D320 - 验收:C(对话与流)、F(持久化)、品质
- 里程碑:M6+
- 状态:由单元测试覆盖(
importer-codex-scan.test.mjs);UI 旅程为草稿(该表面变更时需在具备条件的环境中运行)
E2E-LAYOUT-three-column-width-priority
- 前置条件:在非设置路由中打开一个桌面会话,存在已持久化的首选工作面板宽度,窗口足够宽以容纳三栏。
- 步骤:
- 打开工作面板并请求用户的首选宽度。
- 将内部分隔线向 MainChat 左边缘拖动,包含指针预览阶段,然后释放。
- 在布局收起左栏后手动重开左栏。
- 关闭工作面板并确认左栏恢复;再在手动收起左栏后重复一次。
- 用
ArrowLeft、ArrowRight、Home、End重复调整分隔线。 - Navigate to the real Plugins, Pull requests, and Scheduled routes with the work panel closed, then collapse the sidebar. In light and dark themes, measure both titlebar actions and compare their rest/hover styling with the shared work-panel toggle. Reopen the sidebar, collapse it again, and use New Task to return to an editable chat composer on each route.
- Route chrome expected: The ordinary
.main-titlebaractions (without a preview chrome ancestor) render as centered 28px square targets with the shared transparent rest surface, secondary ink, radius, and semantic hover wash/primary ink. Hover does not change geometry; sidebar and New Task remain usable. Automated bypnpm test:e2e:layoutusing real route components and DOM/CDP interaction. This is renderer evidence, not native Windows/Linux hit-test proof. - 预期:原生窗口宽度全程不变。MainChat 永不低于 360px —— 包含拖动过程中以及
sidebar-out仍占位弹性空间期间。工作面板有效上限为客户端宽度减去 360px 下限与展开的左栏宽度,且无固定像素上限。预算耗尽时展开的左栏立即收起,面板之后仍可继续增长。手动重开优先占用右栏宽度;能保住当前 MainChat 则保持,否则落在 370px 的重开目标。关闭面板只恢复由布局机制收起的左栏。分隔线的 ARIA 最小/最大值遵循同一动态预算。 - 在运行中的应用里实测的浮层覆盖:在 Plugins 路由上打开一个插件模态框;模态遮罩是标题栏带内工作面板开关处的命中首选;标题栏带不是命中首选,因此不会绘制在模态遮罩之上;关闭模态框后路由保持干净。这四项命中由
pnpm test:e2e:layout断言,属于渲染器 DOM/CDP 证据,不是原生命中测试证明。 - 链接规格:
04-ux/01-ui-ia.md、04-ux/07-ui-design-system.md§10、04-ux/08-component-spec.md§1 与 §5、04-ux/09-interaction-patterns.md§8、ADR 0238 - 验收:F(持久化)、品质
- 里程碑:M6 之后的桌面外壳维护
- 状态:已自动化(
scripts/e2e-three-column-layout.mjs,经pnpm test:e2e:layout—— 固定窗口宽度不变、指针拖动全程 360px 下限、左栏让位/恢复、370px 重开目标);单元覆盖见work-panel-resize.test.mjs
E2E-LAYOUT-work-panel-maximize
- Preconditions: A desktop session has an open work panel with a real tab.
- Steps:
- Record the panel width and enter preview mode.
- Inspect the header border box with the sidebar collapsed and expanded on macOS windowed/fullscreen and Windows/Linux. Check row/spacer drag ownership.
- With native pointer input, click centers and edges of sidebar and New Task controls. Reenter preview and operate tabs, close, add, restore, panel toggle and native controls. Drag empty header space. Repeat in light/dark themes.
- Expected: MainChat is absent while preview fills the client area beside the sidebar; native bounds and persisted preferred width are unchanged, the divider is inert, and restore retains the last chosen sidebar state. The row and spacer declare neither drag nor no-drag and pass pointer events through outside controls. The panel header is the sole drag owner in the preview pane; its border box and first tab start at least 8px after shell actions, including expanded-sidebar New Task. Left inset is 8px except collapsed-sidebar windowed macOS (88px through the shared lead-inset token); checks read resolved row padding rather than duplicating native geometry. Right exclusion is unchanged. Controls receive native clicks without window movement; empty-header drags move the window. New Task exits preview to an editable composer. Header-height paint fills the excluded lane without hiding panel controls.
- Specs linked:
04-ux/01-ui-ia.md,04-ux/07-ui-design-system.md§10,04-ux/08-component-spec.md§5,04-ux/09-interaction-patterns.md§8, ADR 0238 - Acceptance: F (persistence), Quality
- Milestone: Post-M6 desktop shell maintenance
- Status: Partially automated by
scripts/e2e-three-column-layout.mjs(preview entry/exit, DOM action behavior, header border-box exclusion, row/spacer ownership, all-platform/fullscreen CSS fixtures in both sidebar states). DOM/CDP clicks are not native hit-test proof. Native pointer, drag and visual checks remain required; branch runs are exploratory only.
E2E-LAYOUT-sidebar-project-group-fold
- 前提条件:通过宿主预置四个保留的侧边栏项目分组:一个横跨四个日期桶共五个会话,一个只有单个会话,一个没有任何会话,一个带十个已固定会话。未设置
prefers-reduced-motion。 - 步骤:
- 检查这些分组:主体分层、行数与日期标签数、空状态、每个展开分组贡献给下一个分组的尾部间距, 以及非项目列表的预算。
- 用真实指针点击项目目录行(先滚动进视野并确认命中该按钮)折叠多行分组,在约半秒内逐帧读取 分组主体的高度、opacity、解析后的
grid-template-rows,以及到下一个分组的距离, 同时记录该折叠自身的transitionrun/transitionend。 - 再次展开,确认打开的几何形态恢复。
- 在同一次运动中先折叠再展开。
- 在模拟
prefers-reduced-motion: reduce的情况下重复折叠。 - 把固定列表滚动到它的最后一行。
- 预期:项目分组是一个网格行(
grid-template-rows: 1fr),在 200ms 的正常时长内动画到0fr—— 没有max-height夹取,也没有 opacity 过渡 —— 因此折叠是一条连续的高度斜坡, 不会先出现平台期再瞬间跳变,并且每一帧的opacity都保持为 1:行是被裁剪的,从不淡出。 一次折叠只触发一次过渡,其自身事件报告 200ms 的正常时长。行由内层带min-height: 0的盒 裁剪,1px 行间隙与分组的 2px / 7px 内缩量位于该裁剪层内部的列表上,因此内缩量随行一起移动。 展开分组的 7px 内缩量加上滚动容器的 1px 间隙,与下一个分组之间形成 8px 尾部间距;列表中的 最后一个分组没有邻居,因此改为校验它自身的内缩量与裁剪层。折叠分组的尾部随行一起消失, 其区块等于标题加上 1px 滚动容器间隙,行仍挂载在被裁剪的边缘之外,同时分组处于aria-hidden与inert。中途反转会从它已经到达的那一帧转向,并回到打开高度而不越界; 空分组以同样方式折叠其空状态。在减弱动态效果下保留两端状态并去掉位移。固定列表在min(233px, 30vh)内显示八行并可滚动到其余行,独立列表保持其弹性列与 146px 预算。 分组的缩进、顺序与工作区状态均不变。 - 链接规格:
04-ux/01-ui-ia.md、04-ux/07-ui-design-system.md§6.1 与 §13、04-ux/08-component-spec.md§6.2、08-meta/decisions-log.md(2026-09-16 侧边栏列表节奏与项目分组折叠) - 验收:品质
- 里程碑:Post-M6 desktop shell maintenance
- 状态:已自动化(
scripts/e2e-three-column-layout.mjs,经pnpm test:e2e:layout): 宿主预置分组与固定项、经过命中校验的 CDP 指针点击、在真实折叠上逐帧采样高度与 opacity、 读取过渡自身报告的时长、中途反转,以及减弱动态效果模拟。 单元覆盖见apps/desktop/test/sidebar-collapse-animation.test.mjs与apps/desktop/test/sidebar-pinned-rendering.test.mjs。采样值是渲染器几何数据, 不是人眼视觉验收。
E2E-LAYOUT-sidebar-row-states
- 前提条件:宿主预置项目、置顶和独立会话,存在当前工作区;应用使用隔离的数据与 profile。
- 步骤:分别在深浅主题下选中项目会话,悬停项目标题、未选中和已选中会话; 检查失焦处理、拖拽目标样式、操作按钮悬停及 Tab/Shift+Tab 焦点;折叠后展开 选中会话的分组,切换置顶与独立会话,打开设置再返回;开启减少动态效果, 确认项目与会话行的悬停过渡均接近零时长。
- 预期:项目与会话共享整行悬停背景、圆角和过渡,标题按钮透明;选中背景 只属于会话且优先于悬停。工作区仅通过圆点表达,折叠不会转移选中态。 置顶和独立会话样式一致;焦点轮廓、独立操作按钮反馈和拖拽目标优先级保留。 失焦释放悬停而不清除选中;设置替换侧栏,返回后恢复会话与工作区上下文。 渲染测试另覆盖没有选中会话、切换中的目标和非聊天页状态。
- 链接规格:
04-ux/01-ui-ia.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md§9.1c - 验收:C、D、品质
- 里程碑:Post-M6 desktop shell maintenance
- 状态:经
pnpm test:e2e:layout调用scripts/e2e/sidebar-row-states.mjs自动验证, 使用真实 CDP 指针/键盘输入及计算样式断言。失焦/聚焦事件与拖拽类由测试注入, 这两项不等同于原生窗口焦点或真实拖拽测试。单元覆盖:sidebar-navigation.test.mjs、sidebar-pinned-rendering.test.mjs。
E2E-LAYOUT-sidebar-settings
- 前提条件:构建后的桌面应用、隔离宿主与 profile,聊天侧栏可见。
- 步骤:在深浅主题及 darwin/win32/linux CSS 分支比较主侧栏与设置导航的颜色、 背景图、尺寸和位置,检查祖先透明度及右侧不透明背景;返回时记录挂载、宽度及 animationstart。重复快速往返、原本折叠、入场被设置打断和减少动态效果场景, 确认真实展开仍有动画。另验证旧主题色、标准侧栏色及背景图覆盖。
- 预期:两处导航共用材质,设置导航和外壳不播入场,只有不透明内容区内部动画。 macOS 下侧栏祖先透明,右侧内容和顶部条不透明。返回时展开侧栏始终为 275px, 无 sidebar-in;原本折叠则保持不显示。真实展开仍有动画与宽度变化;旧主题颜色 作为共享回退保留,显式标准 token 优先。
- 链接规格:
04-ux/06-settings-ia.md、04-ux/07-ui-design-system.md、04-ux/08-component-spec.md§1.4、§1.7 - 验收:A、C、品质
- 里程碑:Post-M6 desktop shell maintenance
- 状态:
pnpm test:e2e:layout调用scripts/e2e/sidebar-settings.mjs,使用可信 CDP 指针/键盘、挂载时与后续几何采样、动画事件及计算样式。测试启用 CDP 焦点模拟, 防止原生窗口被遮挡后 Chromium 冻结动画与悬停输入。平台和主题为渲染层模拟, 不等同于原生 Windows/Linux 或系统材质/主题验证。可通过PI_DESKTOP_LAYOUT_ARTIFACT_DIR保存渲染截图。sidebar-settings-return.test.mjs覆盖首次显示、两种中断阶段、 隐藏时状态变化和反转;pnpm test:e2e:theme-surfaces在真实 Chromium 验证不透明回退及旧主题覆盖。
E2E-AGENT-alt-enter-steers-active-turn:Enter 排队跟进,Alt+Enter 向当前回合补充指令
- 前提条件:会话已配置模型,能够控制流式回复或工具完成时机;附件场景使用支持图像的模型。
- 步骤:
- 发起提示,再输入 follow-up 并按 Enter,确认出现 FIFO 行。
- 在同一回合输入修正并按 Alt+Enter;分别以图像芯片、当前请求结束前连续两次修正重复操作。
- 完成当前回复和工具批次,检查下一次模型输入、转录及持久回合 id;回合结束后观察 follow-up。
- 分别关闭回车发送、打开自动完成菜单,并测试 Shift+Enter、Alt+Shift+Enter 和中文输入法候选词确认。 检查 macOS 的
⌥+Enter和 Windows/Linux 的Alt+Enter发送按钮提示。 - 让补充指令分别与回合结束、Stop、待处理 Plan 审批同时发生;拒绝请求返回前切换会话。
- 运行中更改下一回合模型,再提交补充指令;确认当前模型和权限配置不变。
- 父代理等待后台委托时提交补充指令,保持委托运行,确认父代理在委托报告完成前收到修正。
- 完成后重新加载,并模拟补充指令预留流式回复后崩溃;检查行顺序、恢复文本和所属回合。
- 补充指令接收成功但尚未开始回复时重载渲染器,再按 Stop 并检查持久转录。
- 预期:Enter 排队普通 follow-up;Alt+Enter 在当前回合创建用户行,不创建队列行或新公开
agent_start。已启动工具先完成,下一次请求包含修正和图像;普通 FIFO 仅在持久回合最终落定后 启动。输入法确认和换行不提交,空闲时 Alt+Enter 正常发送。过期或关闭的目标将草稿保留在原会话, 不使当前回合失败。Stop 后已接收输入不会独立重放;重载渲染器并停止后,已完成回复和补充输入仍 留在历史中。终态助手快照原位替换临时快照;崩溃恢复保留最新检查点和相邻补充输入,无重复行。 - 链接规格:
03-runtime/01-ipc-protocol.md(§5.1a)、03-runtime/02-agent-runtime.md(§4.0)、03-runtime/04-data-storage.md、04-ux/09-interaction-patterns.md(§3.5)、ADR active-turn-steering - 验收:C(对话与流)、E(工具与权限)、品质
- 里程碑:M5
- 状态:草稿。现有回归套件覆盖周边行为,尚未运行渲染界面的 steering 完整流程 (除非明确要求,不本地运行 E2E)。
E2E-QUEUE-promote-orders-delivery-by-click:两次“立即发送”按点击顺序投递
- 前提条件:会话正在运行且至少完成一个工具批次,队列中已排队三条提示。
- 步骤:
- 对第三条排队行点击“立即发送”。
- 再对第一条排队行点击“立即发送”。
- 确认优先区块顺序为“第三条 → 第一条”,两行的上移/下移/编辑/删除都被锁定, 而剩余那条仍可编辑。
- 让当前边界通过,观察转录中的用户行。
- 预期:先点击的先投递、后点击的后投递——点击顺序即投递顺序,既不是“最后一次 点击顶掉前一次”,也不是原始队列顺序。两行作为同一回合里相邻的用户消息出现,模型只 回复一次;队列中不再列出这两条优先行。两条优先行显示为已决定,不能被编辑、删除或 重新排序;等待中的那一行保持可用,且不会先于任何优先行被投递。
- 链接规格:
03-runtime/01-ipc-protocol.md(§5.6)、04-ux/08-component-spec.md(§11)、ADR 0265 - 验收:C(对话与流)
- 里程碑:M6+
- 状态:草稿;顺序与相邻投递已由
turn_queue、turn-queue、agent-host单元测试覆盖
E2E-QUEUE-reorder-moves-plain-neighbours:上移/下移重排等待队列
- 前提条件:会话正在运行,队列中至少三条等待行且没有优先行。
- 步骤:
- 把第三条上移两次,确认它变成第一条。
- 对第一条再上移一次,确认没有任何移动。
- 先提升某一行,再尝试把相邻的等待行跨过它移动。
- 重新加载渲染器并检查队列顺序。
- 预期:每次移动只与其相邻的等待行互换,Host 持久化新的顺序,因此重新加载后顺序 一致。等待区块边界与优先区块都不可跨越:边界处的移动是空操作,绝不会到达 Host;任何 移动都不会改变优先行的位置。
- 链接规格:
03-runtime/01-ipc-protocol.md(§5.6)、ADR 0265 - 验收:C(对话)、F(持久化)
- 里程碑:M6+
- 状态:草稿;Host 侧重排已由
turn_queue单元测试覆盖
E2E-QUEUE-edit-restores-draft-only-when-input-empty:编辑仅在输入框为空时回填草稿
- 前提条件:会话正在运行,队列中有一条带文本与文件引用芯片的提示,输入框为空。
- 步骤:
- 先在输入框输入草稿,再点击该排队行的编辑。
- 清空输入框,再次点击编辑。
- 确认队列中不再有该行,且输入框持有该行的文本与其文件引用芯片。
- 发送它,并与原本排队时的提示对比转录。
- 预期:输入框非空(或带有附件芯片)时编辑被拒绝并给出可见提示,该行仍留在队列中。 输入框为空时该行离开队列、Host 也不再列出它,输入框得到完全相同的文本与原始文件引用 芯片——而不是被剥离标注的内联内容。重新发送产生的提示与排队时一致。
- 链接规格:
04-ux/08-component-spec.md(§11)、ADR 0265 - 验收:C(对话与流)
- 里程碑:M6+
- 状态:草稿;渲染器侧契约已由
composer-send-state.test.mjs覆盖
MCP 市场场景(pnpm test:e2e:mcp-market,协议级无头)
| ID | 场景 | 验证 |
|---|---|---|
| E2E-MCP-MARKET-NET-BOUNDARY | URL guard 拒绝凭据、云元数据、unspecified、multicast、reserved 及尾点绕过形态——即使由用户自己填写;对第三方跳(重定向目标、目录正文)拒绝回环、私网、special-use IPv4、v4-mapped、ULA、site-local 与 link-local;同一形态由用户填进源地址字段时被接受,明文 http 需 networkPolicy.allowInsecureUserEndpoints;direct/unknown 默认固定已检查的地址,完整 proxied 线路使用 session 传输,显式 allowFakeIp 可覆盖透明路由器 fake-IP 源但不允许真实私网答案 | 确定性 guard 断言;DNS pin、代理线路选择、fake-IP 选项范围与响应上限 source-contract 覆盖 |
| E2E-MCP-MARKET-SEMANTICS | Registry 记录映射为安装模板时保留包版本、named/positional runtime/package 参数与 required/optional 环境变量语义;远端 header 变量同时识别注册表的 {name} 与目录的 ${NAME} 两种写法,仅为已声明的可编辑值显示输入,保留未声明花括号字面量,并按各 header 的作用域处理默认值、固定值与可选标记,不合并不同 header 的同名输入(ADR registry-header-variable-spelling) | 确定性映射断言 |
| E2E-MCP-MARKET-INSTALL | 内置目录条目经 resolveCatalogEntry 解析并通过宿主 mcp.upsert RPC 安装;记录落盘 ~/.agents/servers/ | 真实宿主二进制,隔离临时 HOME |
| E2E-MCP-MARKET-HEADER-SCOPE | Registry header-local {token} resolves only in its header; same-named URL path/query tokens remain literal through mapping, resolution, host upsert/list and persistence. URL templates retain only legacy ${NAME} substitution. When headerBindings exists (even empty or partial), unbound tokens in every header stay literal and never consume another header's input or default | shared regressions plus real host binary with isolated temporary storage; remote entry disabled, no network call |
| E2E-MCP-MARKET-partial-header-bindings-stay-literal | Resolve a catalog with only Authorization bound and another header using the same {token} / ${token}; an undeclared ${UNBOUND} in a third header also remains literal through host upsert/list and disk persistence | real host binary, disabled remote entry, synthetic input and isolated temporary storage; no network call |
E2E-SKILL-MARKET-NET-BOUNDARY:用户自填源可达局域网,第三方内容不行
- 前提条件:共享 public-network helper,以及可注入 fetch/DNS/线路 的主进程公网 HTTPS 客户端。
- 步骤:1)把 trailing-dot localhost、IPv4 回环、IPv4-mapped IPv6、ULA、link-local、RFC1918 与
http://各分类两次:一次作为用户自己填写的源地址,一次作为目录正文里的文档 URL。2)将公网主机名解析到私网 A 记录。3)跟随 Location 为https://127.0.0.1/的 302。4)报告proxied线路与 TUN fake-IP 答案(198.18.0.1),同一答案在DIRECT线路、读不出线路、以及列表中含DIRECT的线路上的表现。5)让第一跳为proxied,其重定向目标为direct。 - 预期:用户自己填写的源地址可以是回环或局域网目录——
https始终允许,http仅在networkPolicy.allowInsecureUserEndpoints打开时允许;而同一地址作为目录内部的文档 URL 或重定向目标时一律拒绝;云元数据、unspecified、multicast、reserved在任何输入上都拒绝。公共 CDN 放行。用户源解析到私网地址会被正常抓取;第三方跳解析到私网地址会抛出策略错误,且不会请求私网目标。判定型拒绝不重试;本地解析没有返回答案时会重试,并且报为NETWORK_RESOLVE_FAILED(kind为unresolved),而不是报成地址校验拒绝——守卫并未得出判定,任何文案都不得声称它得出了。本地代理伪造的 fake-IP 答案(如 Clash 默认的198.18.0.0/15)在守卫判定它的线路上——direct或读不出线路——仍被拒绝且不重试,并以kind为fake-ip、reason为non-public-address、addressKind为benchmark记录,与真实私网目标(kind为policy、addressKind为private)清楚区分——对后者守卫判定了目标,对前者没有;同一答案在proxied线路上放行。其余每次拒绝都带上NETWORK_POLICY_BLOCKED(spec 08 §3.1)及其reason、被解析到的地址、地址类别与判定该地址的线路,使安装面板能给出原因并提供重试,而不是让安装按钮无解释地保持禁用;市场列表也能把被拒绝的源与单纯不可达的源区分开。其他所有非公网类别在任何线路上都拒绝;每一个重定向跳都按自己的线路判定(ADR 0272)。 - 链接规格:
05-security/01-security.md、ADR 0243、ADR 0272、03-runtime/01-ipc-protocol.md§12b - 验收:Security、Quality
- 里程碑:M6+
- 状态:已自动化(
pnpm test:e2e:skill-market、apps/desktop/test/public-https-fetch.test.mjs、apps/desktop/test/public-https-fetch-route.test.mjs、apps/desktop/test/skill-market-scan.test.mjs、apps/desktop/test/skill-market-failure.test.mjs、apps/desktop/test/skill-market-policy-refusal.test.mjs、packages/shared/src/public-network.test.ts)
E2E-SKILL-MARKET-EXPANSION:相邻 markdown 资源在安装前内联
- 前提条件:技能目录列出 FORMS.md 与 REFERENCE.md。
- 步骤:拆分 SKILL.md,把相邻 markdown 展开为附录,并确认超过 128 KiB 的文档被标记过大。
- 预期:预览/安装正文含技能文本与
# Attached resource:附录。非 markdown 兄弟文件省略。超过 hostMAX_SKILL_BYTES的正文不会写入。 - 链接规格:
04-ux/06-settings-ia.md、ADR 0243 - 验收:Quality
- 里程碑:M6+
- 状态:已自动化(
pnpm test:e2e:skill-market、apps/desktop/test/skill-market-scan.test.mjs)
E2E-SKILL-MARKET-INSTALL:市场安装经 skills.create 写入用户技能
- 前提条件:宿主二进制;隔离 HOME。内置目录条目与组装后的 markdown 正文。
- 步骤:handshake;用组装后的 name/description/body 调用
skills.create;读取~/.agents/skills/pdf.md;skills.list。 - 预期:文件含渲染后的 frontmatter 与指令正文;出现在
skills.list。不走skills.create以外的写入路径。 - 链接规格:
03-runtime/01-ipc-protocol.md§12b、ADR 0243 - 验收:Quality
- 里程碑:M6+
- 状态:已自动化(
pnpm test:e2e:skill-market)
E2E-SKILL-MARKET-ID-ALIGN:扫描得到的技能 id 与 host valid_capability_id 对齐
- 前提条件:共享
sanitizeSkillCatalogId。 - 步骤:净化
Frontend_Design、1-pdf以及空余量。 - 预期:得到 host 合法 slug(
frontend-design、1-pdf、skill-7),使installedIds能对上创建结果。 - 链接规格:
03-runtime/01-ipc-protocol.md§12b - 验收:Quality
- 里程碑:M6+
- 状态:已自动化(
pnpm test:e2e:skill-market)
E2E-PLUGIN-turn-ended-once-per-host-turn:每个宿主回合只产生一次回合结束事件
- 前置条件:已加载并启用一个带工具、且监听
session:turnEnded的插件;其面板记录每次收到的载荷,以及工具通过工具上下文拿到的turnId。 - 步骤:
- 发送一条提示,使回复在同一个回合内发起三次工具调用。
- 记录插件收到多少次
session:turnEnded,并把其中的turnId与该插件工具收到的turnId对比。 - 再发一条提示,然后用
Cmd/Ctrl + .中止它。 - 再发一条会失败的提示,使回合以错误结束。
- 在插件设置页检查是否出现新的权限复核。
- 预期:步骤 2 只收到一次
session:turnEnded,reason为completed,且其turnId与工具上下文的turnId相同。步骤 3 只收到一次事件且reason为aborted—— 中止之后不会再出现第二次completed。步骤 4 只收到一次事件且reason为error。从未开始的回合不产生事件;即使终态事件到达多次,任何插件也不会在同一回合收到两次事件。步骤 5 不出现新的权限复核,订阅未知事件名也不会报错。 - 链接规格:
07-plugins/03-plugin-api.md、07-plugins/13-plugin-permissions-matrix.md、ADR 0252 - 验收:品质(协议与插件契约)
- 里程碑:M6+
- 状态:由模块测试覆盖(
apps/desktop/test/session-turn-ended.test.mjs、apps/desktop/test/queued-turn-finalization.test.mjs);桌面旅程为草稿(该表面变更时需在具备条件的环境中运行)
E2E-CHAT-disclosure-toggle-keeps-reading-position
- 范围:滚动器已固定在底部时,手动展开转录或委派运行停靠区里的工具、思考或活动组标题(issue #324)。
- 先决条件:一个超过一屏的会话,停在底部并开启跟随模式;其中包含一行工具、一行思考和一个活动组,它们的展开详情都高于自身的标题栏;另有一个已展开、拥有嵌套滚动器的委派运行。分别在一轮已结束(
isRunning为 false)和一轮正在流式输出时各做一遍。 - 步骤:在固定于底部时,依次点击工具行、思考行和活动组的标题,并在该轮结束后再点一次。把视口停在转录中部再重复一次;用键盘激活一次(先 Enter,再在获得焦点的标题上按 Space),以及从收起栏操作一次。在已展开的委派停靠区内展开一行工具。然后用滚轮滚动、在标题获得焦点时按 ArrowUp、点击“跳到最新”控件,并发送一条新提示。
- 预期:详情动画展开与收起期间,被点击的标题保持它在屏幕上的位置,转录不会在它下方重新触底;跟随模式被退出,并出现“回到最新消息”控件。保持的位置在活动组动画的每一帧都成立,也覆盖标题上方同时发生的高度变化。嵌套停靠区保持自己的位置,其背后的转录同样不会重新触底。真实的滚动输入、跳转控件、新一轮以及任何导航都会释放保持并重新跟随实时尾部,而已经向上滚动的阅读者保持原来的位置。Space 仍能激活获得焦点的标题,方向键仍能滚动。
- 链接规格:
04-ux/09-interaction-patterns.md§9.1;ADR transcript-reading-ownership;D287、D302、D430 - 验收:C(对话与流式)、E(工具与权限)、品质
- 里程碑:M6+
- 状态:部分自动化。
pnpm test:e2e:transcript-disclosure在真实的 600 CSS px Electron 视口中挂载真实的转录滚动 hook 与真实的工具行,用真实 DOM 点击标题,并对转录以及嵌套跟随滚动器断言标题相对滚动器顶边的偏移与滚动偏移;没有该修复时,同一个夹具会报告标题移动了整个展开详情的高度。夹具未链接应用样式表,因此高度来自内联填充与组件自身的固有尺寸。disclosure-anchor.test.mjs覆盖纯锚点与输入归属数学,transcript-disclosure-reading.test.mjs覆盖接线。键盘、滚轮与活动组动画路径仍属补充验证。
E2E-CHAT-running-status-survives-output-pauses
- 先决条件:Electron 中的生产
ChatTranscript、真实 store 和 React DOM,使用确定性消息/状态转换,无需模型凭据。 - 步骤:发送一轮,输出部分文字后停止增量,开始并完成工具,切换和清空具体阶段,恢复文字后完成、停止或失败。在详细与紧凑模式重复;验证规划、权限、提问与提案等待、历史阅读及会话切换。
- 预期:输出暂停和工具完成后仍有且仅有一行本地化、可访问的底部状态。具体阶段优先于规划/目标或工作中。等待用户操作时隐藏;回合结束与历史阅读时移除。会话切换只使用对应会话的状态;已有内容与权限操作保持可用。运行中与含助手错误的回合不挂载空操作栏;默认字号下,部分回答的片段盒底部到状态文字顶部为 24 CSS px。结束后的无错误正文保留复制、分支与重新生成。详细和紧凑模式均需验证。历史阅读和权限卡保留正常消息间距;回合结束后贴底模式继续跟随底部,恢复正常操作栏高度;向上阅读时正文位置和滚动偏移保持不变。
- 链接规格:
04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md - 验收:C(对话与流式)、品质(交互和可访问性)
- 里程碑:M5
- 状态:
pnpm test:e2e:transcript自动化(scripts/e2e/transcript-status.tsx)。上游ecece8f570fc0c18f21e4941eafe52244a7c6e5f可复现 #669;不代表真实模型验证。
E2E-CHAT-initial-wait-spacing
- 发送用户消息且助手尚未输出:保留原有用户操作栏和间距,默认纯文字用例气泡到状态文字为 52px。
- 宽窗口和窄窗口下复制可聚焦、可命中;编辑、删除、版本按钮保持运行中禁用。 含版本分页时,状态与所有操作按钮的命中区域也不重叠。
- 状态/底部空白右键打开会话菜单,消息右键打开消息菜单。阶段变化、首次输出或输出前停止, 运行状态始终只有一处,结束后恢复消息操作。
- 覆盖:
scripts/e2e/transcript-status.tsx,pnpm test:e2e:transcript。
E2E-CHAT-runtime-status-keeps-row-position
- 先决条件:一个活动会话,其转录高于对话视口,且尾部由一行已完成的工具占用;窗格已构建(
pnpm build:js)并已安装 Electron。 - 步骤:
- 用固定消息挂载生产环境的
ChatTranscript,尾部由已完成的活动组占用,并让会话处于运行中。 - 在没有任何运行时活动上报的情况下,记录内容高度、滚动器的滚动高度/偏移,以及首行与末行的渲染位置。
- 只把该会话的运行时活动切换到等待模型阶段,让布局稳定下来。
- 再次清空运行时活动,让布局稳定下来。
- 结束该轮(
isRunning为 false),检查尾部。
- 用固定消息挂载生产环境的
- 预期:
- 等待行占用预留的状态通道:在 Working 切换到具体等待状态再恢复时,内容高度、滚动高度、滚动偏移,以及每一个已渲染行的位置都保持不变(误差在 0.01px 以内)。
- 通道在深色与浅色两种主题下都保持预留和透明,没有背景、边框或阴影;清空具体阶段后恢复 Working。
- 状态行保持其活动区域语义(
role="status"、aria-live="polite"),等待用户操作时预留通道为空,不携带可供播报的文本。 - 已结束的空闲转录完全不渲染状态通道,因此其布局不变。
- 链接规格:
04-ux/08-component-spec.md - 验收:C(对话与流式)、品质
- 里程碑:M5
- 状态:由单测覆盖(
active-turn-surface.test.mjs),并有通过pnpm test:e2e:transcript(scripts/e2e/transcript-render.tsx,无需供应商凭据,需要已安装的 Electron 与图形会话,Linux 上可用 Xvfb)自动化的 React/Chromium 几何回归。当预留通道被移除时,该场景会以 40.125px 的内容高度误差与 40px 的行位移失败(issue #323)。
E2E-PLUGIN-fs-root-follows-the-calling-session:插件工具的 fs 根跟随自己的会话,而不是可见工作区
- 先决条件:两个项目 A 与 B,各自持有一个对方没有的文本文件,以及一个已启用的插件;该插件注册的智能体工具会在 execute 里对根相对路径调用
pi.fs.readText与pi.fs.glob,并声明带workspace根的fs.read。两个项目都有该插件工具可用的活会话,且窗口显示项目 A。 - 步骤:
- 从会话 A 调用该工具读取只属于项目 A 的文件,再从会话 B(项目 B)调用它读取只属于项目 B 的文件,期间不切换窗口的可见工作区。
- 把可见工作区切到项目 B,重复上面两次调用。
- 在可见工作区为项目 B 时,让会话 A 的工具请求只存在于项目 A 下的路径,再让会话 B 的工具请求同一路径。
- 打开该插件的面板,在项目 A 可见时通过面板桥做同样的读取,然后在项目 B 可见时再做一次。
- 把其中一个会话移到临时对话,使没有可见工作区,从该会话调用该工具,再在同一状态下调用面板桥。
- 预期:步骤 1 与 2 每次都以调用会话自己的项目作答:会话 A 读取并 glob 项目 A 的文件,会话 B 对应项目 B 的文件,可见工作区在这两个项目之间移动时答案都不变。步骤 3 对会话 A 返回项目 A 的文件,对会话 B 返回
NOT_FOUND,因此会话永远无法触达另一个项目的根。步骤 4 与之前完全一致:每次面板调用解析可见工作区,所以它的答案跟随屏幕上的项目,而不是任一会话。步骤 5 按会话继续工作 —— 会话的插件工具解析自己的项目 —— 而面板调用仍以NOT_FOUND(「没有打开任何工作区」)失败。任何闸门都没有放宽:被拒的名称、..或绝对路径、指向根外的符号链接,以及超出声明范围的路径,其行为与只有一个可见工作区时相同。 - 链接规格:
07-plugins/03-plugin-api.md§3、07-plugins/13-plugin-permissions-matrix.md§6、ADR 0016、ADR 0249、ADR 0266、D093 - 验收:G(插件)、安全性、品质
- 里程碑:M6+
- 状态:部分自动化(
apps/desktop/test/plugin-fs-session-root.test.mjs):工具调用在发起会话的项目下写入与读取,面板调用与宿主未跟踪的会话回退到可见工作区,userSelected模式保留选定的目录,窗口不显示任何项目时会话根依然生效。双活会话的桌面旅程与面板步骤为草稿(仅在此表面变化时于具备条件的环境中运行)
E2E-TRAY-bounded-session-navigation
- Scope: Native tray groups, hidden/recreated window activation, and unread semantics (issue #293, ADR tray-session-shortcuts).
- Preconditions: At least four running, four unread, and four pinned sessions across two projects; include overlaps, read-latest/older-unread notifications, archived/deleted sessions, an archived project, empty titles, multiline titles, long CJK/emoji titles, and literal ampersands. Also cover a single populated group of seven and one of more than nine, with the other two groups empty, to exercise reclaimed share. Use an isolated profile. Repeat native activation on macOS and Windows/Linux.
- Steps: Hide the main window and open the tray menu. Inspect group order, counts, duplicates, titles, and unchanged unread records. Choose the third row from another project, then View more from Settings with a collapsed sidebar and a retained search query. Finish/abort tasks while hidden; read a result, pin/unpin, rename, archive/restore, and delete a session. Close the macOS window while a task runs and let it finish, then activate its tray row while the new renderer bootstraps a pending plan. Delay a Host read while a newer preference update, delete, or Host restart arrives. Retry a transient read failure by hovering/right-clicking the tray on Windows/Linux; macOS retries from the next session or inbox event instead of mouse-enter. Repeat after clearing all group memberships and changing shipped locales. Choose Quit then Cancel, then Quit and confirm.
- Expected: Running → Unread → Pinned; at most nine rows in total. Each non-empty group keeps up to three rows and overflowing groups reclaim the share smaller groups leave unused, in priority order: seven running sessions with no unread or pinned show all seven, and a single group holding more than nine shows nine behind View more. Deduplicate before limits, so hidden Running overflow cannot appear as Unread/Pinned. Empty groups and stale shortcuts disappear. Unread uses the latest terminal result per session, newest first. Titles remain one line within the 32-column cap, half as many characters for CJK/emoji, including literal ampersands. Opening the macOS menu leaves the window hidden and records unread. A row opens exactly that session/project, acknowledges it normally, and wins over startup navigation. View more returns from Settings, closes search, and expands session navigation. Hidden/closed windows receive fresh groups; stale reads, archived/deleted targets, and a failed backend cannot restore stale shortcuts. Open, localization, quit cancellation, and shutdown work.
- Specs linked:
03-runtime/01-ipc-protocol.md§13b,03-runtime/07-process-model.md,04-ux/08-component-spec.md,04-ux/09-interaction-patterns.md, ADR tray-session-shortcuts. - Acceptance: A (app/window lifecycle), C (conversation navigation), F (unread persistence), Quality (bounded localized menu).
- Milestone: Post-M6 desktop shell maintenance.
- Status: Draft; native E2E requires an explicitly authorized run.
E2E-MODEL-catalog-window-correction-reaches-saved-bindings:目录修正回流已保存绑定,且不覆盖用户手改值
- 目标:models.dev 修正某模型上限后回流到已保存的绑定(不必删除重建), 而用户在设置里手改的数值永不被覆盖。
- 步骤:
- 配置一个提供商,勾选 models.dev 已发布
limit.context的模型并保存。展开该行的 高级区,读取上下文窗口字段与其提示。 - 用修正后的目录记录(不同的发布上限)替换该模型记录,重新打开设置,读取该行、 上下文检查器,以及新会话启动时使用的窗口。
- 在高级区输入窗口——先用预设档位,再手输
128000——保存,然后再喂一次目录修正, 重新打开设置与检查器。 - 保存并重新打开一个绑定不带
contextWindowSource的提供商行:一次使用通用128000种子,一次使用任意其它已存值。
- 配置一个提供商,勾选 models.dev 已发布
- 预期:步骤 1 显示发布值并带「跟随 models.dev」提示。步骤 2 在所有使用 effective window 的地方(设置行、上下文检查器、会话启动)都显示修正后的值,无需删除重建。 步骤 3 在设置行、检查器和实际请求中都保留用户输入的值,包括在目录窗口更大时手输的
128000,且提示消失。步骤 4 表现确定:128000种子跟随目录,其它值保持原样。 每一步中标记都能在提供商行的保存/读取往返后保留,早于该标记写出的配置仍可读。 - 关联规范:
03-runtime/13-model-catalog-and-selection.md§9.1、03-runtime/12-provider-config-schema.md§2、03-runtime/11-provider-model-system.md§2、04-ux/06-settings-ia.md§2 - 验收:B(模型配置)、F(持久化)、Quality
- 里程碑:M6+
- 状态:部分自动化:
apps/desktop/test/model-binding-catalog-source.test.mjs驱动 主进程解析(目录来源的修正会到达对外暴露的行、用户值不受修正影响、通用种子仍跟随 目录、继承值保持catalog标记);packages/shared/src/model-catalog.test.ts覆盖 四条来源规则;crates/host-core/src/providers/catalog.rs覆盖配置往返、无标记记录与 被丢弃的未知标记。端到端的设置旅程与实际启动窗口断言为草稿。
E2E-PLUGIN-official-channel-resolves-through-the-platform:官方渠道的安装通过平台解析,并从第一个可用镜像安装
- 先决条件:全新配置停留在官方渠道;
plugins.aiuo.net/catalog.json中存在一个插件;平台与两个镜像主机各有请求日志(可用本地存根代替)。 - 步骤:1) 打开扩展 → 市场,确认来源行显示官方渠道,且目录来自
plugins.aiuo.net。 2) 安装该插件。 3) 抓取平台收到的请求。 4) 检查是哪个镜像提供了安装包。 5) 再安装第二个插件,然后重新安装第一个插件的同一版本。 - 预期:每次安装或更新只发出一次
POST /api/v1/download/resolve,JSON body 含deviceId、pluginId,用户选定版本时含版本号;安装包来自downloads中第一个可应答的条目,且在解压之前其字节与返回的sha256和sizeBytes一致;不可达或失败的镜像被放弃并自动尝试下一个,无需用户操作;重新安装同一版本会发出新的 resolve 调用,而不是复用上一次的应答,因为响应从不缓存;插件通过常规权限审查安装,其记录标明来源为官方渠道。 - 链接规格:
07-plugins/07-plugin-marketplace.md§2、07-plugins/15-plugin-center.md§10 - 验收:G(远程市场来源)
- 里程碑:M6+
- 状态:草稿
E2E-PLUGIN-mirror-digest-mismatch-falls-through-to-the-next-mirror:字节与摘要不符的镜像在解压前被拒绝
- 先决条件:一次官方渠道安装,其
downloads至少有两个条目,第一个镜像提供的字节与返回的sha256不符(旧分发内容,或提供pi.todo-0.6.5的 CNB 时代字节的存根);同时可以查看安装缓存与插件目录。 - 步骤:1) 启动安装。 2) 观察第一个镜像的下载与摘要校验。 3) 在安装结束前检查安装缓存与插件目录。 4) 让安装继续。 5) 换成一个只在第一个镜像上让声明的
sizeBytes不符的存根重做一遍。 - 预期:不符的字节被丢弃,不会被解压或交给安装器,插件目录里不会留下任何内容,拒绝会显示在安装进度里而不是被吞掉;下一个镜像的字节按同一摘要校验,安装从那里完成;大小不符的情形表现完全相同;所有条目都失败时安装以明确的失败结束,而不是留下半安装的插件。
- 链接规格:
07-plugins/07-plugin-marketplace.md§2 - 验收:G(远程市场来源)+ 安全性
- 里程碑:M6+
- 状态:草稿
E2E-PLUGIN-platform-unreachable-install-falls-back-to-the-catalog-url:平台不可达时安装回退到目录地址
- 先决条件:官方目录已成功刷新并缓存;安装期间
plugins.aiuo.net不可达(被阻断的存根,或被拒绝的 DNS/代理线路)。 - 步骤:1) 在平台可达时刷新目录,然后让它不可达。 2) 安装一个目录条目里带相对
url的插件。 3) 确认是哪个主机提供了安装包,以及平台是否收到 resolve 请求。 4) 恢复可达后,依次安装平台分别以403 NOT_PUBLISHED、403 PLUGIN_ARCHIVED、404、429和503拒绝的版本。 - 预期:安装从目录自身的地址解析安装包——
artifactBaseUrl加相对url——并在同样的 shasum 校验之后完成;该次安装不会向平台发出 resolve 请求,也不计入统计;resolve 失败的原因会出现在安装日志里而不是被隐藏;平台恢复应答后,各拒绝码各自给出对应的提示——尚未发布不重试、已归档会从安装与更新选择中隐藏该插件、平台没有该版本、限流按Retry-After等待一次、503报部署问题——并且任何拒绝都不会静默切到别的渠道或别的版本。 - 链接规格:
07-plugins/07-plugin-marketplace.md§2 - 验收:G(远程市场来源)
- 里程碑:M6+
- 状态:草稿
E2E-PLUGIN-device-identifier-is-stable-and-never-the-machine-code:设备标识跨启动稳定,且不是机器码原文
- 先决条件:一个能读到机器标识的主机(Windows
MachineGuid、macOS 平台 UUID,或/etc/machine-id),一个读不到机器标识的环境,以及为POST /api/v1/download/resolve记录请求的存根。 - 步骤:1) 在同一会话内触发两次安装,比较记录的
deviceId。 2) 重启应用后再触发一次安装。 3) 把该值与主机的机器标识原文比较。 4) 在设置、市场页面与已安装插件详情中查找该值。 5) 在读不到机器标识的环境里重复步骤 1 与 2,然后检查应用数据目录。 - 预期:同一安装发出的每次 resolve 请求都携带同一个 64 位小写十六进制值,包括重启之后,以及机器标识未变时的应用重装之后;该值既不是机器码原文也不是它的前缀,并等于
sha256("pi-desktop.device.v1:" + 机器标识);该标识从不出现在界面上,也没有任何设置可以显示或重置它;读不到机器标识时,该值是另一个 64 位十六进制字符串,只生成一次并持久化在plugins/market/device.json,之后跨重启一直复用。 - 链接规格:
07-plugins/07-plugin-marketplace.md§2、ADR 0276 - 验收:G(远程市场来源)+ 安全性
- 里程碑:M6+
- 状态:草稿
E2E-PLUGIN-install-progress-shows-phases-and-mirror:官方渠道安装显示各阶段与正在尝试的镜像并完成
先决条件:全新配置停留在官方渠道;一个插件的 resolve 应答至少列出两个条目;第一个镜像失败或很慢,以便观察到第二次尝试;渲染进程已订阅
plugin.installProgress。步骤:1) 从市场详情面板发起一次手动安装。 2) 记录安装期间收到的报告。 3) 安装成功后把指针悬停在对话框上。 4) 安装结束后查看已安装插件。 5) 用一个较大的安装包再次安装,并统计至少一秒窗口内的报告数量。
预期:对话框按顺序显示各阶段——
resolve、download、verify、install、enable——以及mirror n/N · name和由receivedBytes/totalBytes得出的确定进度条;每条报告都带pluginId与version,只有指明镜像的报告才带source,attempt按 1 在attempts内计数,切换镜像会递增attempt而不改变attempts;字节报告最多每 200 ms 一条,另有每次阶段变化一条与最终一条;安装以不带error的状态结束,插件通过常规权限审查后完成安装并启用;成功后对话框约 2 秒自动关闭,悬停时该倒计时暂停;后台自动更新以同样方式安装,但完全不打开对话框。链接规格:
07-plugins/07-plugin-marketplace.md§2、07-plugins/15-plugin-center.md§10、ADR 0276 §7验收:G(远程市场来源)
里程碑:M6+
状态:草稿
Local fallback regression: Set the process default data directory to a different temporary root, then create a plugin manager with its own root. All bundled fallback package URLs must stay beneath the manager's root; installing the bundled package must still report byte progress, validate its size and checksum, and preserve cancellation behavior.
E2E-PLUGIN-cancel-during-download-installs-nothing:在下载期间取消会停止安装、不留下任何已安装内容,并让对话框静默关闭
- 先决条件:一次官方渠道安装,其安装包足够大或镜像足够慢,使下载阶段持续一段时间;能够应答
market.cancelInstall;同时可以查看插件目录、安装缓存与已安装列表。 - 步骤:1) 启动安装并等待进入下载阶段。 2) 按下对话框中的取消操作。 3) 观察对话框并抓取 RPC 应答。 4) 安装结束后检查插件目录、安装缓存与已安装列表。 5) 对同一 id、对一个并未在运行的安装,以及在下载已结束之后,分别再次发出
market.cancelInstall。 - 预期:对正在运行的安装,取消调用返回
{ cancelled: true, id },安装以PLUGIN_CANCELLED(JSON-RPC 码 1019)失败;对话框不报告错误就关闭;没有任何东西被安装——没有插件目录、没有已安装行、没有已启用的插件——缓存中也不残留任何不完整的安装包;对同一 id 的第二次调用、对并未在运行的安装的调用,以及下载已结束之后的调用都返回{ cancelled: false, id }且不改变任何状态,因此取消永远不会中断插件目录的写入。 - 链接规格:
07-plugins/07-plugin-marketplace.md§2、ADR 0276 §7 - 验收:G(远程市场来源)+ 安全性
- 里程碑:M6+
- 状态:草稿
E2E-PLUGIN-failed-install-lists-tried-mirrors:安装失败时对话框保持打开,并列出尝试过的镜像与复制操作
- 先决条件:一次官方渠道安装,其所有镜像都失败——例如第一个摘要不符、第二个网络错误;渲染进程已订阅
plugin.installProgress,且可以读回剪贴板。 - 步骤:1) 启动安装。 2) 让所有镜像失败。 3) 读取最终报告与对话框。 4) 使用复制操作,然后在镜像恢复正常后使用重试操作。
- 预期:最终报告带有
error,并按尝试顺序为每个镜像提供一条tried[]记录,各自标明source、url与该镜像给出的错误;对话框保持打开,显示可读的错误与这份列表;复制操作把尝试过的镜像放入剪贴板;重试操作会对同一版本发起一次新的安装,并在镜像正常应答后完成,且不会复用失败尝试的不完整状态;失败的尝试没有安装任何东西。 - 链接规格:
07-plugins/07-plugin-marketplace.md§2、ADR 0276 §7 - 验收:G(远程市场来源)
- 里程碑:M6+
- 状态:草稿
E2E-RPC-unicode-separators
- 前置条件:已构建 host-core、shared 与 agent-runtime;隔离的临时数据目录;仅回环的 fixture provider。不使用真实凭证。
- 步骤:追加一条含 U+2028/U+2029、中文、emoji 以及转义 CR/LF 的用户消息;读取、重启 host 后再读。通过 AgentSidecar 发送 Unicode 提示;经 parent host proxy 恢复历史;流式返回并持久化 Unicode 回复。发送一条含相同字符的未知方法,再发健康检查。
- 预期:文本在持久化与所有 stdio 方向上保持不变。请求在 RPC 超时前完成;错误回复之后的请求仍可用。无需迁移现有会话。
- 自动化:
pnpm test:e2e:rpc-unicode;packages/shared/src/ndjson.test.ts额外覆盖每个 UTF-8 切分位置、连续帧、CRLF、EOF 与销毁。 - 规格:03-runtime/06-host-rpc-protocol §2。
- 验收:A(运行时),C(会话)。
- 里程碑:M6+。
- 状态:已自动化;针对 task/PR 集成候选运行。
E2E-PROVIDER-ORDER: Reorder configured AI services
- Open Model configuration with three providers and drag the last before the first, then after the last; verify pointer-following cards, live slot preview, and saved order. Check movement threshold, action-button exclusion, edge scrolling, Escape cancellation, and automatic-scroll cleanup.
- Cancel a drag and use Up/Down on a focused provider card. Verify no configuration or default-model changes. Failed saves report an error and retain accepted order; overlapping moves are blocked and late catalog reads cannot undo a move.
- Reopen the Composer model menu: groups follow the provider order. Restart the host and renderer and verify persistence. Check English and Chinese labels.
- Automated:
pnpm test:e2e:provider-orderruns the real settings component, sandboxed preload, provider IPC registrar, and an isolated Rust host. It uses synthetic Chromium pointer/key events and test providers without credentials; model-catalog/OAuth discovery is stubbed and no provider requests are sent. - Host unit tests cover no metadata, disabled/plugin rows, new/deleted IDs, stale moves, no-op moves, preserved configuration, and reopening the database.
The provider-order suite also runs a 200-card fixture under a controlled animation clock. A burst of 200 pointer events may arm React state once, but stationary frames must produce no further renders or pending callbacks. Card geometry is read at press time, and move/release/cancel/unmount paths must clear transient transforms and queued frames. Release before the scheduled frame must still save the latest destination. These assertions measure work counts, not device FPS.
E2E-CHROME-window-controls-survive-work-panel
- Preconditions: A built desktop and matching host-core binary; isolated profile and data directory with one local session; no real provider calls.
- Steps: Open the work panel from the titlebar, toggle the sidebar, maximize and restore the panel, maximize and restore the native window, close the panel, visit Settings and return, reopen the panel, minimize/restore the window, then click Close with close-to-tray configured in the disposable profile. Also enter and leave native fullscreen with the panel open. Check the three controls with Chromium hit testing throughout, including light/dark Windows/Linux CSS.
- Expected: Windows/Linux minimize, maximize/restore, and close stay visible and hit-testable at the window edge. Native actions work with the panel open. Preview sidebar navigation remains clickable. On macOS no duplicate renderer window controls appear; the native traffic-light and fullscreen contracts stay unchanged. CSS emulation does not qualify another operating system's native UI.
- Specs:
04-ux/01-ui-ia.mdtitlebar; ADR 0021 and ADR 0025. - Acceptance: Window actions remain accessible independently of pane state.
- Milestone: Post-MVP regression coverage.
- Automation:
pnpm test:e2e:window-controls; macOS traffic-light geometry also hasapps/desktop/test/traffic-light-reserve.test.mjscontract coverage. - Status: Automated for the executing native platform; run on macOS/Linux runners for native qualification. Optional screenshots are written only to
PI_DESKTOP_CHROME_ARTIFACT_DIR.
E2E-PLUGIN-slow-tool-is-not-cut-off-by-host-dispatch
- Preconditions: A loaded plugin that registers an agent tool; the tool awaits work longer than 60s but shorter than the 110s plugin tool budget (for example an
agent.completecall that takes ~70s). - Steps: Ask the agent to use the tool, approve it if prompted, and wait for the call to finish. Repeat with the permission card left open for a while before approving.
- Expected: The call returns the plugin's result. host-core does not answer
TOOL_TIMEOUTbefore Electron's budget expires, and no transport layer reportshost RPC timeout,sidecar RPC timeout, orparent host proxy timeoutfor the call. Budgets nest from the inside out: pluginagent.complete90s < MCP call 100s < plugin tool 110s < host-core dispatch 150s, while the widest MCP leg (a lazy handshake, then atools/listtraversal, then the call) is 140s. The transport deadline covers the permission wait, the admission queue wait, and that dispatch, so a call that waited for a saturated plugin class before it was dispatched is still inside it. - Specs:
07-plugins/12-plugin-ipc-and-host-services.mdagent-tool dispatch; ADR 0038; ADR 0174. - Acceptance: G (plugin agent tool).
- Milestone: Post-MVP regression coverage.
- Automation:
apps/desktop/test/plugin-timeout-budgets.test.mjschecks the budget order and the dispatch call site across the TypeScript and Rust sources;packages/shared/src/protocol.test.tscovers the transport deadline; host-coredesktop_dispatch_outlasts_every_electron_budget_it_wrapscovers the dispatch default. - Status: Contract-covered; no end-to-end driver waits out a real 70s call.
E2E-IMAGES-remove-configured-model
- 前提: API 边界 fixture,服务商包含已标记的生图模型及另一个模型; 分别测试旧版单绑定和另一服务商仍有可用生图候选的情况。
- 步骤: 不操作生图复选框,直接移除已标记模型;先取消,再重复并保存, 重新打开设置和编辑器。
- 预期: 取消保留配置;保存清除已移除模型的生图候选并清空生图默认值,即使 仍有其他可用候选;保留仍存在的聊天默认模型,被移除的聊天默认模型按原有 规则改为第一个剩余模型,重新打开后状态保持。
- 规格: 03-runtime/21-image-generation。验收: B。
- 里程碑: 维护。状态:
scripts/e2e-image-generation-ui.mjs使用中英文自动覆盖。
E2E-IMAGES-desktop-conversation
- Preconditions: Isolated desktop profile and workspace, built image feature, local chat and OpenAI Images HTTP fixtures; no live provider credentials.
- Steps: Open Models settings with no image model configured; verify the image summary row is absent. Open a provider's Advanced model settings and verify Set as image model is grouped with the image/document attachment capabilities and can be checked for multiple models. Save, use the image summary menu to choose one marked model as the default, then submit a two-image request through the composer and edit the first output through a follow-up message. Collapse tool details.
- Expected: After saving, the image summary appears with a 12px default-row gap and lists all marked candidates, the selected default is changeable without changing the chat default, decoded image previews stay outside collapsed process details, multipart source upload is used for editing, originals are preserved, and no image HTTP request occurs while unconfigured.
- Settings interactions: The unconfigured state renders no image summary. Mark candidates in the attachment capability group, save, and select one from the summary menu. An existing missing/disabled candidate shows only Currently unavailable. Covered in
scripts/e2e-image-generation-ui.mjs. - Conversation selection: Every marked image provider/model is absent from default and Composer candidates. Other providers retain same-ID models. An existing session pinned to any marked image binding is rejected before inference.
- Transport contracts: Real stdio reverse RPC retains a thrown local image error's stable code in the production ParentHostProxy. Local HTTP tests check single/multiple binary multipart fields and boundaries, DALL-E
b64_jsonrequests, GPT Image parameter omission, bounded responses, and rejection of more than four references before I/O. These are protocol tests, not official provider account/live compatibility certification. - Proxy fake-IP: 明确开启“设置 → 通用 → 网络”的代理 fake-IP 选项后,解析 到 Clash 基准测试段的图片 URL 会通过代理感知传输下载;未开启时仍返回
IMAGE_UNSAFE_URL,真实私网地址在两种情况下都继续阻止。该行为由packages/agent-runtime/src/image-generation/download.test.ts覆盖,不由下面的驱动脚本覆盖。 - Status: Automated in
node scripts/e2e-image-chat.mjs; optional screenshots usePI_IMAGE_CHAT_EVIDENCE_DIR. The images are deterministic raster fixtures, not evidence of real-model quality or provider compatibility.
E2E-SCHEDULED-desktop-automation-lifecycle
补充覆盖: 共用模型/推理等级首层菜单、可搜索模型子菜单、键盘选择、保存重开
high、应用默认设置不变,以及运行会话中的已存推理等级。前提: 独立桌面配置、构建后的任务候选版本、本地 SSE 模拟模型;不使用真实 服务凭据或付费 API。
步骤: 点击页脚时钟;创建每天上午 09:00 的任务;选择另一个已保存项目、Auto 权限和非默认模型;编辑名称;暂停/启用;立即运行; 打开结果会话;验证周期/四个时段主题下拉菜单、星期多选及选中标记、保存回显、空选择与固定时间; 验证方向键、Home/End、Enter、Escape/Tab 和外部点击关闭; 验证每小时无时间输入且首次等待一小时;设置每天任务在下一个真实分钟执行; 观察自动完成;删除已结束的任务。普通 Agent 对话经模型工具调用发现、创建、查询、 修改任务到 15:30,再删除;验证页面显示具体时间,改名保存不覆盖。模型为本地确定性夹具。
预期: 项目、权限和精确 provider/model 只保存在该任务,重新打开仍显示相同值并实际传到 sidecar,其他任务不受影响;项目、权限和模型控件保持嵌在“指令”框的 Composer 风格底栏中,窄窗口也不产生横向溢出;缺少新增字段的旧记录保持原默认行为。配置持久化并显示下次时间;暂停后不触发;手动与自动入口均调用真实 Agent sidecar;历史记录链接到持久化会话;自动执行不依赖渲染器发送提示词。 宿主测试补充验证重复准入、错过时段、无效输入和重启恢复。
规格: 04-ux/01-ui-ia §3.4;03-runtime/04-data-storage §4.11; ADR scheduled-desktop-automations;ADR 0305。
验收: 定时执行与可恢复的运行历史。
里程碑: MVP 后的桌面自动化。
状态:
node scripts/e2e-scheduled.mjs自动化覆盖;按 AGENTS.md 在任务候选 版本上运行,并记录已测提交与基线。
E2E-CONFIG-SYNC-webdav-portable-configuration
- 前提: 已构建的任务候选版本、隔离的 Host 配置,以及支持 strong ETag 和条件 PUT 的本地 WebDAV fixture。不使用真实 WebDAV 账户、provider 或生产桌面。
- 步骤: 1)打开设置 → 云同步,填写 fixture URL、设备标签、目录和备份密码。2)运行能力测试,确认使用临时对象。3)选择 provider/MCP/skill 类别,保持凭据和 memory 未选中;在第二次预览中启用凭据,确认只显示脱敏计数。4)配置设备 A,创建 user provider 和 MCP 定义并同步。5)让设备 B 连接同一 vault,同步后检查待激活/映射,并验证审批前不会运行命令或任务。6)批准一个变更后的安全实体,拒绝一个暂存实体,在两台设备上编辑不相交设置并再次同步。7)测试并发 head writer、错误密码、weak ETag、密文损坏、redirect、归档路径穿越和网络中断。8)使用回环/私有地址的 fixture 勾选“允许在受信任的内网地址使用 HTTP”,确认刷新状态后仍保留;即使勾选,公网 HTTP 地址也必须被拒绝。
- 预期: 测试拒绝不可靠的条件写入。HTTP 默认关闭,仅允许 localhost、
.local或私有/链路本地地址;公网 HTTP 地址会被拒绝,界面会提示凭据暴露风险。若 fixture 探测到该 endpoint 对不存在对象返回 502,后续只兼容该 endpoint 的这一行为;忽略条件头的服务器仍必须标记为不受支持。WebDAV 只能看到已认证的密文和不透明对象名;原始秘密不会出现在 Renderer 状态或日志中。相同和不相交的编辑会收敛,冲突保持可审查,明确删除使用 tombstone,类别退出不是删除,可执行导入在本地审批和映射前保持不激活。恢复不会暴露部分应用的本地配置。 - 规格:
03-runtime/22-config-sync.md、03-runtime/14-secrets-storage.md、05-security/01-security.md、ADR 0300。 - 验收: F(持久化)、Security、Quality。
- 里程碑: M6+。
- 状态: Draft;合并/密码学和进程内 WebDAV 条件写入覆盖已存在。完整双设备进程路径和逐检查点本地恢复故障注入仍待自动化。
E2E-DIALOG-long-text-boundaries
- 前提: 已构建渲染器,使用隔离的 Electron 组件测试窗口。组件、状态、翻译 和样式均为生产实现,只在 preload IPC 边界提供确定性宿主回复。
- 步骤: 用长 Windows 路径打开扩展输入框,输入、提交、重新打开并关闭;在 520×480 下测试明暗主题及中文的长标题、确认文本、选项;检查重命名、项目 指令、记忆、删除、插件安装、设置、权限确认与 OAuth 弹窗,并操作关闭按钮。
- 预期: 来源路径与其他长文本完整换行显示,不截断,无横向溢出,保留 420px 提示框宽度; 高内容在框内滚动,关闭、Escape、输入提交、单选操作正常。
- 规格:
04-ux/08-component-spec.md,弹窗长文本边界。 - 验收: 文字保持在边界内,弹窗操作可达。
- 阶段: 发布后维护。
- 自动化:
pnpm test:e2e:dialog-overflow;源代码检查不能替代实际布局验证。 - 状态: 已实现,原生 Windows 已验证,macOS/Linux 尚未实机验证。
E2E-PROVIDER-certificate-trust-and-terminal-errors
- 前提: 已构建、包含当前
origin/main的任务候选版本,已安装 Electron,使用 隔离的测试进程/配置和回环 HTTPS 夹具。不使用真实 provider、凭据或用户配置, 也不写入操作系统证书库。 - 步骤: 运行
node scripts/e2e-provider-certificates.mjs。启动真实桌面 sidecar,使用不受信任的 localhost 证书提交聊天提示。再将其 CA 放入NODE_EXTRA_CA_CERTS后重启,并通过 SAN 中不存在的主机名请求同一证书。 - 预期: 子进程默认 CA 集合包含系统根证书和额外 CA。首次请求只失败一次, 返回不可重试的证书错误且没有重试状态;信任 CA 后请求返回文本;主机名不匹配 仍只失败一次。TLS 和主机名校验始终保持启用。
- UI: 运行
node scripts/e2e-provider-certificate-ui.mjs,在隔离 Chromium 中验证真实错误组件。证书错误显示本地化指引;DNS 和协议错误保留通用摘要。 errno/原始 details 仍可见,详情可以关闭并重新打开。可选的PI_CERTIFICATE_EVIDENCE_DIR会记录截图;--baseline使用相同夹具和样式的 upstream 错误组件。 - 低层覆盖:
provider-certificate-flow.test.ts通过真实 Agent/pi-ai wiring 进入主 session 的prompt()和 delegate 的run(),仅 mock 外部 fetch。两条 路径都只请求一次并保留证书原因;错误分类和恢复测试覆盖直接、嵌套、扁平化、 非证书以及包装后的证书错误。 - 限制: OS 根证书的纳入在不安装根证书的条件下检查。TLS 成功夹具只使用 子进程额外 CA,不复现某个具体杀毒软件安装,也不宣称已完成 macOS/Linux 实机验证。
E2E-CHAT-turn-process-and-thinking-display
- 先决条件: 一个回合包含进度段落 A、多次搜索及思考、进度段落 B、多条命令及 思考和最终回答;详细与紧凑显示模式。
- 步骤: 在详细模式静态审查嵌套披露路径,包括相互独立的组/条目切换、父级收起 与恢复、单项片段、字面最后一项选择、失败/被拒/恢复和保留窗格重新挂载。 随后在紧凑模式重复,并覆盖权限/提问/计划/目标操作卡、中止的部分回答、 助手错误和委派子任务。
- 预期: 两种模式都使用一个整体过程披露,并把最终回答、助手错误、中止后的末尾 文字和待处理操作留在过程之外。详细模式的活动中/已完成过程默认展开;活动多项组 展开,未操作组在完成时收起。紧凑模式的过程、组和载荷默认收起,隐藏推理;记录过 失败/被拒工具的未操作活动过程会在恢复期间保持展开,并在完成后收起。单项没有组。 详细模式只自动展开最后一个活动组中符合条件的字面最后工具/搜索项;不会越过思考 向前查找,失败/被拒叶子保持关闭。父级、子级和同级状态相互独立;窗格拥有的用户 选择跨更新、模式切换和重新挂载保留,渲染器重启后重新应用默认值。已保存模式跨重启 保留,缺失或未知设置按详细模式处理。
- 2026-09-20 变更的验证范围: 本次变更只包含多级折叠与活动组呈现;精确转录搜索 定位不在本次范围内,保持原有搜索行为。
- 规格: 04-ux/06-settings-ia、04-ux/08-component-spec、 04-ux/09-interaction-patterns;ADR turn-process-and-thinking-display。
E2E-CONTEXT-estimate-calibration-stays-safe
- 先决条件: 可脚本化上报用量的确定性提供商夹具;占用接近硬边界的会话;不使用真实凭据。
- 步骤: 连续上报低于预测的用量,确认显示占用不低于下限且压缩仍然触发;上报远离合理区间的用量,确认数值不动;跑一个 中文为主的会话,比较显示占用与上报用量。
- 预期: 校准后的占用保持在原始估算的 0.85×–6× 之内;有证据即上调;只有方向一致的样本才下调;处在硬限制 1.18× 的 投影仍触发压缩;误报不产生任何方向的移动;中文文本不再只有实测成本的四分之一。
- 规格: 03-runtime/02-agent-runtime §5.1、08-meta/decisions-log D606。验收: C(对话与流)、F(持久化)、品质。 状态: 单元测试覆盖(含中文与边界用例);桌面 E2E 待补。
E2E-RUNTIME-unique-tool-call-ids-per-request
先决条件: 一份把同一次工具调用携带两次的会话转录(重试追加让该调用落在第二个行 id 上),在确定性提供商夹具下加载进 重新创建的运行时;不使用真实凭据。
步骤: 发一条提示,使运行时组装并发出请求。读取夹具收到的出站请求与
agent日志通道。再用工具调用本就唯一的转录重复一次。预期: 出站请求对那个 id 只携带一个
toolCall与恰好一个对应结果,因此提供商不可能回tool_use ids must be unique; 日志通道出现一行,带会话与 id。唯一转录的请求逐字不变,且不产生任何日志。规格: 03-runtime/02-agent-runtime §5、08-meta/decisions-log D608。验收: C(对话与流)、品质。 里程碑: Post-MVP 回归覆盖。
自动化:
packages/agent-runtime/src/runtime.test.ts用真实的运行时覆盖两半:重复历史(丢弃 + 一行日志)与唯一历史 (同一对象、无日志)。状态: 单元测试覆盖;没有端到端驱动对重复转录发出真实提供商请求。
E2E-RUNTIME-loop-context-ownership
- 先决条件: 一个确定性提供商夹具:前两轮各回一次工具调用,并在第二轮之后像用户 Stop 那样结束回合;不使用真实凭据。
- 步骤: 发一条提示,让运行时跑完两轮工具调用并在工具轮上结束回合;再发第二条提示。读取运行时保留的状态与夹具收到的出站请求。
- 预期: 保留的状态里每个流式辅助消息与每个工具结果都只有一份;没有结果与它回答的调用被隔开;夹具收到的请求对每次调用只带一个结果。 不产生去重日志行,因为请求守卫没有任何东西需要丢弃。
- 规格: 03-runtime/02-agent-runtime §5c、08-meta/decisions-log D620。验收: C(对话与流)、品质。 里程碑: Post-MVP 回归覆盖。
- 自动化:
packages/agent-runtime/src/runtime.test.ts(loop context ownership)通过runtime.prompt()驱动真实 pi 循环, 并读取convertToLlm交给提供商的视图;subagent-loop-context.test.ts覆盖委托侧的回合边界。两者都到不了适配器自身的输出, 因此由tool-call-dedupe.test.ts(request wire contract)直接驱动 pi-ai 的消息变换,固定「请求把调用与结果隔开时同一个 call id 会拿到两条输出」这一机制。 - 状态: 单元测试覆盖;没有端到端驱动从「循环追加过的上下文」构建新回合的请求。
E2E-MCP-HTTP-ACK — HTTP acknowledgement and authorization status
- Preconditions: A local mock Streamable HTTP server returns JSON for initialize,
202with plain-textAcceptedfor notifications/initialized, SSE for tools/list, and JSON for tools/call. No provider credentials needed. - Steps: Connect, discover a tool, and call it. Render the MCP settings row with idle, connecting, ready, non-authentication failure, and authentication failure statuses, including an expired stored OAuth credential.
- Expected: The acknowledgement does not enter the JSON parser; discovery, calls, and session headers remain functional. Only explicit authRequired status shows the authorization-required badge. Non-authentication failures retain their original error. Authentication failures remain actionable.
- Specs: 07-plugins/01-plugin-system §12.2; ADR 0038; ADR 0283.
- Acceptance: HTTP client integration and settings component rendering.
- Milestone: Maintenance.
- Status: Covered by the existing HTTP client integration fixture and a focused component-render validation; no live IDA process required.
E2E-PLUGIN-crash-report-names-the-exit-code
- 先决条件: 已加载的插件宿主进程自行死亡——夹具为写一行 stderr 后
process.exit(7)——并带一个常驻服务,使监督器路径也被覆盖。 隔离的桌面配置;不访问市场或网络。 - 步骤: 加载插件并让宿主进程死亡。读取加载错误、
failed服务状态、plugin.crash审计记录与plugin日志通道。若有条件, 再用一次硬故障(Windows0xC0000005一类退出)重复;最后在插件宿主存活时退出应用。 - 预期: 上述每一处都给出退出码(
exit code 7;硬故障为exit code 3221225477 (0xC0000005)),而夹具的 stderr 行不会出现在 加载错误或崩溃审计记录中。干净退出完全不上报崩溃:退出是关闭而不是崩溃。 - 规格: 07-plugins/05-plugin-lifecycle §3.1 / §8、08-meta/decisions-log D607。
- 验收: G(插件宿主生命周期)、品质(可诊断性)。里程碑: Post-MVP 回归覆盖。
- 自动化:
apps/desktop/test/plugin-services.test.mjs真实 fork 宿主进程、以夹具退出码杀死它,并断言服务状态与审计记录上的 退出码及原始 stderr 缺失;plugin-isolation.test.mjs与关闭用例覆盖"退出不是崩溃"那一半。 - 状态: 运行时层已自动化;无 UI 驱动读取插件页的错误文本。
E2E-IMAGE-generation-and-editing
- Preconditions: Built task candidate containing latest origin/main; isolated host data directory, local image HTTP fixture, no production credentials.
- Steps: Choose a model in Advanced, cancel and verify no change; save and replace it through another provider's Advanced settings. Clear the binding via the test settings API to verify recovery. Generate same-prompt variants and distinct images, edit a generated image, inspect partial failures, then restart the host/session. Attempt the tool in a durable Plan session and verify no HTTP request occurs.
- Expected: One image binding persists without changing the chat default; generated files, edit sources and transcript references survive restart. Images render in chat; unconfigured errors navigate to Models settings.
- Specs: 03-runtime/21-image-generation; 03-runtime/13-model-catalog-and-selection.
- Acceptance: Configured image generation/editing, safe cancellation and persistence.
- Milestone: Post-MVP.
- Status: Automated by
scripts/e2e-image-generation.mjsandscripts/e2e-image-generation-ui.mjs; backend test uses real Rust/stdio/HTTP/files, UI test uses real Chromium and production components with API-boundary fixtures.
| Scenario | Acceptance | Specification | Automation |
|---|---|---|---|
| E2E-IMAGE-generation-and-editing | Image capability and recovery | 03-runtime/21-image-generation | Host and UI suites above |
E2E-CHAT-parenthesized-url:用户消息中的完整网址
- 步骤:在用户消息中发送
https://en.wikipedia.org/wiki/React_(software)并点击链接,再验证正文用圆括号包裹该网址、网址后跟句号及紧接另一链接或文件引用的情况。 - 预期:打开包含
(software)的完整网址,进入 React 软件词条;正文外层的右括号和紧跟 URL 右括号的句末标点不属于链接,相邻引用仍能独立点击。嵌套圆括号、查询和片段内的圆括号、百分号编码的圆括号均保持完整。 - 覆盖:
chat-links.test.mjs;桌面端通过正常浏览器目标实际点击验证。 - 链接规格:
04-ux/08-component-spec.md§8.3。
E2E-IME-escape: 取消组词保留草稿
- 步骤:编辑用户消息并输入草稿,派发组词中的 Escape 和 Cmd/Ctrl+Enter;打开全局搜索,从输入框派发组词中的 Escape。对 legacy keyCode 229 重复检查,再验证普通 Escape 和重试快捷键。
- 预期:组词事件不会丢弃编辑、关闭搜索或重试发送;普通 Escape 仍取消或关闭(包括搜索输入框之外的焦点),普通 Cmd/Ctrl+Enter 仍重试。
- 覆盖:
node scripts/e2e-ime-escape.mjs使用真实组件、冒泡 DOM 键盘事件及宿主边界测试数据;不代表已验证操作系统输入法候选窗口。
E2E-SCHEDULED-manual-to-hourly
- Preconditions: Built host candidate, isolated data directory, no provider.
- Steps: Through
tools.execute, create a paused Manual task, update only its id and cadence to Hourly, rename it, restart, and list tasks again. - Expected: Update succeeds without calendar fields; the next occurrence is one hour away. Prompt, paused state and saved configuration survive. Rename does not reset the interval. RPC tests also cover required Daily/Weekly times and retention of an existing custom schedule.
- Specs: 04-ux/01-ui-ia §3.4.
- Acceptance: C / F — task configuration and persistence.
- Milestone: Maintenance.
- Status:
node scripts/e2e-scheduled-hourly-update.mjsexercises the real Rust host, permission path, stdio and SQLite; no model inference or desktop UI.
E2E-SCHEDULED-manual-workspace-binding
- Preconditions: Built request candidate; isolated host data and two project directories. No provider credentials or paid API.
- Steps: Create Manual and Hourly tasks in project A and without a project; restart the host, switch to B, Run now, rename through the editor payload, and Run now again.
- Expected: Each result session and edited task retain the original binding, including no-project tasks. Legacy cadence-only tasks keep their previous fallback until explicitly configured (covered by host RPC tests).
- Specs: 04-ux/01-ui-ia §3.4.
- Acceptance: Saved workspace binding across run, edit and restart.
- Milestone: Maintenance.
- Status: Automated by
node --experimental-strip-types scripts/e2e-scheduled-workspace.mjs, using production Electron dispatch and real Rust/stdio/SQLite. Only external inference is replaced with an observer.
E2E-PROVIDER-stored-binding-array-reads-entry-by-entry:存储的模型绑定数组逐条读取
- 前置条件:一次性数据目录与 host-core 可执行文件;通过
providers.create建立一个至少含三条完整绑定的提供商;不使用真实提供商或凭据。 - 步骤:调用
providers.list,确认全部绑定都返回。编辑存储的config_json,删掉其中一个绑定的maxTokens,再次 list。补回该字段后再 list。最后把某个绑定的contextWindow改成字符串,再 list 一次;用可读子集 携带一个无关字段变更调用providers.update。 - 预期:除损坏条目外,三条绑定都按存储顺序返回。丢失
maxTokens的绑定以 通用默认输出上限读出,补回字段后数值恢复。宿主日志为无法解码的条目带上提供商 id、条目下标与原因。显式模型数组更新返回MODEL_BINDINGS_DEGRADED,而存储的config_json保持不变。 - 链接规格:
03-runtime/12-provider-config-schema.md§2、08-meta/decisions-log.mdD610 - 验收:F(持久化)、品质
- 状态:单元覆盖(
providers::catalog::tests、providers::tests::a_stored_array_survives_an_entry_that_lost_a_field); 宿主 RPC 路径由scripts/e2e-smoke.mjs覆盖提供商的创建与列举,但没有套件 驱动手工编辑的config_json。
E2E-SESSION-temporary-attachment-fork:临时任务预览与独立分支附件
- 步骤:在没有项目的任务中粘贴超过长文本阈值的内容并发送,点击对话中的附件。 返回后创建分支并打开同一附件;删除原任务,再打开分支附件,并继续创建分支。
- 预期:每次预览均显示原始文件内容;各分支引用自身的 scratch 输入目录, 不需要打开项目,也不授予跨任务读取权限。按消息截断的分支不复制后续消息独有 或未引用的输入文件。
- 自动化:
scripts/e2e-composer-paste.mjs覆盖长文本保存、真实 Electron 文件读取、临时任务预览及返回。主机测试fork_preserves_referenced_pasted_files_independently和sessions::fork_files覆盖附件归属、删除、重复与截断分支、保留的压缩检查点引用、过期输入、失败回滚 及符号链接拒绝。
E2E-011f 补充:入队确认前的操作锁定
- 前提:会话正在回复,可控制 Host 入队请求的完成时刻。
- 步骤:发送一条后续消息,暂停入队;尝试编辑或删除;释放入队后再次编辑或删除, 然后刷新队列。用带文件引用的草稿重复;让本地 Host 入队请求超过现有 RPC 超时后重试。
- 预期:未确认时五个行操作都禁用且提示正在保存,立即发送按钮也显示正在保存, 条目不消失,也不回填第二份草稿; 确认后删除能移除持久化条目,编辑完整回填草稿及文件引用,刷新不会恢复已移除条目。 本地 Host 超时后移除未确认行、显示错误并恢复被拒绝的草稿,重试可正常入队。
- 规格:
04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md。 - 验收 / 里程碑:C、Quality / M6+。
- 状态:组件与状态层用户路径由
queue-pending-actions.test.mjs覆盖。
E2E-SCHEDULED-dispatch
- 场景:独立分发到期任务。
- 预期:Electron runner 无需等待其他任务的提示词准备即可准入彼此独立的 到期任务;本地所有权按任务 ID 和 Host 实例保留,Host 继续负责 enabled、 due 和重叠检查。停止只阻止新轮询,错误仍可观察,迟到 90 秒的规则不变。
- 自动化:
node --experimental-strip-types scripts/e2e-scheduled-dispatch.mjs
E2E-SCHEDULED-project-removal
- 场景:删除项目与自动任务。
- 预期:删除项目会暂停其绑定任务,并保留任务定义、schedule、工作区绑定和 运行历史;已准入的任务会阻止删除。其他项目和未绑定旧任务不受影响。用户显式 恢复或 Run now 可以重新创建项目,暂停状态下的自动轮询不会这样做。
- 自动化:
node --experimental-strip-types scripts/e2e-scheduled-project-removal.mjs
E2E-SCHEDULED-legacy-pause
- 场景:旧任务维护。
- 预期:Agent 工具可对缺少 schedule 的旧版自动任务修改标题、提示词或暂停, 包括回传未变化的 cadence;维护不会启用任务或捕获前台工作区。显式启用、改变 cadence 或提供 schedule 时仍执行校验,Manual 转 Hourly 的默认间隔行为不变。
- 自动化:
node --experimental-strip-types scripts/e2e-scheduled-legacy-maintenance.mjs
E2E-SCHEDULED-calendar-intent
- 场景:日历配置意图。
- 预期:可选的
config_json.calendarConfigured区分明确设置的每日/每周 日历时间与 Hourly 内部占位 schedule。旧版 Daily/Weekly 保留日历语义; 旧版 Hourly 转换时必须明确提供 schedule。已知配置可跨 Hourly 与重启保留, 包括午夜;仅修改元数据以及 Manual 转 Hourly 的行为不变。 - 自动化:
node --experimental-strip-types scripts/e2e-scheduled-calendar-intent.mjs
E2E-SCHEDULED-paths
- 场景:工作区身份。
- 预期:保存和读取工作区绑定时统一使用现有项目路径规范化规则。Windows 路径的斜杠方向、大小写、末尾分隔符和扩展路径前缀差异不影响同项目会话; 缺失的旧版绑定与显式 null 保持不同语义,其他项目不能查询或修改绑定任务。
- 自动化:
node --experimental-strip-types scripts/e2e-scheduled-paths.mjs使用隔离的真实 Host 与 SQLite 配置,不向真实提供商发送推理请求。
原生搜索续跑契约(离线 sidecar)
范围: ADR 0297;供应商原生搜索内容、估算、本地工具、Task 委派及历史恢复。不调用真实模型或搜索服务。
入口: pnpm test:e2e:hosted-search 自行重建 shared 与 agent-runtime sidecar。 通过普通 stdio RPC 驱动该产物,使用隔离数据目录、合成 Host 与仅回环地址的 Responses 服务。 只测源码或使用已有桌面安装包不满足本测试。
| 场景 | 必须观察到的结果 |
|---|---|
| 搜索后新用户消息 | 合法搜索项被回放,第二轮正常结束。 |
| 搜索后普通本地工具 | 真实工具轮完成,下一模型请求准备无类型或估算错误。 |
| 搜索后 Task | 实际委派流程使用合成模型,父代理继续且不重复报告本地错误。 |
| 保存搜索历史后恢复 | 新运行时保留搜索回放,不添加假工具字段或改写原记录。 |
| 真实前缀变化使 usage 失效 | 隔离 sidecar 在真实 Read 轮中更新指令;此前非零 usage 不再覆盖该前缀,续跑正常结束。 |
| 非法历史回放容器 | 恢复返回安全的 INTERNAL/context-validation;不发送模型请求或持久化 Host RPC,进程仍健康。内存中输入不变性由回放单元测试验证。 |
| 未知历史搜索阶段 | 明确拒绝该记录,而不是静默丢弃搜索内容;其余断言同上。 |
配套检查: Responses/Azure/Anthropic 请求契约、搜索阶段类型、有效/零/失效 usage、 真实与无变化的系统前缀及工具增删、搜索数据增长、中文输出预算;结构化本地准备失败不重试、不 fetch,普通传输重试与取消保持正确。依赖升级必须对锁定版本运行这些检查,记录测试的 bundle。
证据: 记录构建和测试退出码、基线 SHA、依赖版本、产物标识及独立评审,报告位于 docs/project/hosted-search-contract-verification.md。不得记录真实会话或凭据。未执行明确标为 NOT RUN,不得标为 PASS。
E2E-HOOKS-cancel-and-dispose
#816 扩展验收: 命令弹窗显示期间 Stop,真实渲染层移除弹窗,后续弹窗和 exec 不再发生,下一命令或回合正常。Electron 驱动通过 CDP 检查 DOM;Runner 测试覆盖 长命令、上下文等待取消、工具进度退役及头部副本;进程测试通过就绪信号同步真实父子进程。
前置条件: 隔离 Desktop 配置、本地确定性模型,以及含等待型请求前处理器的可信插件。
步骤: 发送消息,在处理器等待时停止,释放旧处理器后再次发送。另测等待时销毁 Runtime,并加载启动/关闭挂起以及注册未接通事件的夹具。
预期: 被停止或销毁的请求不调用模型,后续消息正常完成。迟到结果不能重启工作。 关闭只执行一次,各处理器等待有界,未接通事件产生诊断但不禁用正常处理器。
规范: 07-plugins/16 §6。
验收: 取消及时生效,扩展生命周期等待有界。
里程碑: Hooks P0。
状态: Runner 与真实 Runtime/本地 HTTP 集成通过
extensions/runner.test.ts、extensions/runtime-lifecycle.test.ts自动验证。 可信扩展 Electron 驱动通过 CDP 在真实渲染层执行 Stop 路径,并记录弹窗显示和 退役后的状态。
Composer 指令源、手动压缩与空记录读取(#795)
范围: composer 的斜杠分发、手动压缩 RPC,以及渲染层的持久化记录读取。不调用真实模型或 提供商。
| 场景 | 必须观察到的结果 |
|---|---|
composer/commands 失败时输入 /compact | 提交被拒绝并显示 chat.slashCommandSourceUnavailable,草稿保留,没有任何提示词进入会话。失败的读取不入缓存,因此下一次发送会重试。 |
| 指令源缓存仍热 | 内置指令仍在本地分发;模板与未知别名仍走提示词路径。 |
| 手动压缩超过其传输超时 | 宿主重新读取持久化压缩记录:发现新检查点已落盘就报告成功并记录该不一致,未落盘则原样抛出超时。sidecar 自己报告的判定绝不被改写。 |
| 有历史的会话读到空记录窗口 | 选择流程再读一次,随后保留用户已有的快照,否则显示 chat.sessionTranscriptEmpty;这类空页绝不入缓存,因此悬停预取不会反复提供它。 |
自动化: node --test apps/desktop/test/slash-command-source.test.mjs、 node --test apps/desktop/test/session-transcript-empty-read.test.mjs、 node --test apps/desktop/test/plugin-timeout-budgets.test.mjs、 pnpm --filter @pi-desktop/shared test、pnpm --filter @pi-desktop/host-runtime test。