04. E2E 测试 Plan
E2E-106-session-permissions
Run node scripts/e2e-plan-ui.mjs --permissions-only against the candidate Desktop and Host with isolated data and no provider. Eight native approval, menu, rejection and reload cases verify explicit Ask/Accept edits/Auto, inherited global permissions and ignored legacy device history. Each seeded Session owns a distinct probe tool-call identity. Approval persists through the real preload/Main/Host path; missing-provider execution is expected.
E2E-FILE-INDEX-git-deadline
对真实测试文件调用 getWorkspaceFileIndex 和 searchWorkspaceFiles, 保持 Git 的 close 事件未到达,推进截止时间后两者均返回有界目录列表。 覆盖:fs-index-git-deadline.test.mjs。原有索引测试覆盖 Git 忽略规则、 Unicode 字节分块及深层搜索。
E2E-QUEUE-late-read-after-event
呈现两个排队提示,保持生产刷新,发送 queueChanged 事件移除第一个提示,再返回旧读取;只显示第二个提示,后续权威刷新继续生效。queue-refresh-event-order.test.mjs 覆盖事件与读取顺序、逆序响应及会话独立性;e2e-queue-refresh-event-order.mjs 在真实 Electron 中呈现生产 ComposerStatus/useAppStore。不向提供商发送提示。
E2E-TRANSCRIPT-window-cache-edit-race
保持生产有界会话读取,发布带新历史窗口的空闲编辑缓存,再返回旧窗口;从缓存恢复并呈现编辑后的正文。同时覆盖未变化时正常填充及运行中实时尾部合并。session-window-cache-race.test.mjs 验证 runtime API;e2e-session-window-cache-race.mjs 在真实 Electron 中使用生产 MessageRow 呈现恢复快照。仅控制 API,不执行提供商。
E2E-MODEL-idle-write-order
保持空闲配置的首个响应,选择第二个模型再顺序释放响应,验证只有一个写入 进行中且最终保留最新模型。session-model-write-order.test.mjs 使用真实 slice 与协调器,仅控制 Host API 边界。运行中配置继续排队。
E2E-EXTENSION-thinking-request-state
通过生产扩展桥接设置 high,发送提示并捕获提供商流参数。真实 Agent 循环 发送 high,与扩展查询值一致。runtime.test.ts 仅替换提供商流边界。
E2E-TRANSCRIPT-full-read-cache-race
保持全量历史读取请求,发布新的流式快照,再返回旧读取结果。调用方得到历史, 会话缓存保留新快照。覆盖 full-transcript-refresh-race.test.mjs 和现有会话 更新回归,不执行模型请求。
E2E-SESSION-rejected-stop-state
在 API 边界拒绝远端 Stop,生产会话 slice 保留运行状态并传播错误。再次成功 停止后,当前标记和目标会话运行条目清除。覆盖 failed-session-stop-state.test.mjs,不执行提供商请求。
E2E-SUBAGENT-system-notice-reconstruction
重建运行中的 Task 后接系统通知的会话,生产条目构建、运行状态选择及结果 解析保留 Task 运行状态。结束会话或追加普通用户提示后,历史 Task 不再存活。 覆盖:live-delegation-system-row.test.mjs;turn-process.test.mjs 验证对应 回合边界,turn-process-header.test.mjs 验证渲染状态标签。
E2E-STEERING-recovery-reply-order
在早先回复和下一次恢复回复之间送入已消费的 steering,旧片段结束,下一条 回复使用新 ID。runtime.test.ts 覆盖提供商、溢出、空回复及进度恢复。 e2e-transcript-render.mjs 验证生产事件投影,以及无 steering 时原有的 单气泡溢出恢复行为。
E2E-COMPOSER-screenshot-without-directory-scan
e2e-composer-paste.mjs 使用真实 Composer、preload IPC 和有界 scratch 写入, 先粘贴原生目录,再粘贴合成截图。使用 --screenshot-only 验证这条路径, 跳过无关的 MP4 预览;截图不进入原生目录探测,Main 精确校验 scratch 字节。 composer-directory-paste.test.mjs 验证字节不变及原有目录/混合选择流程。 不修改系统剪贴板内容。
E2E-FILE-MANAGER-git-deadline
file-manager-git-timeout.test.mjs 对真实测试文件调用内置 fm.list 和 fm.read,仅控制子进程边界。子进程从不关闭时推进 Git 截止时间,目录及 预览仍完成,Git 元数据不可用。原有 Electron 目录定位和文件修改旅程验证 正常读取、保存和目录浏览。
E2E-SCHEDULED-independent-loading-and-route-return
- Preconditions: Synthetic tasks; controllable task, latest-run, history and project API responses. No provider execution is required.
- Steps: Hold latest-run and project reads while resolving task definitions; select a task and run, leave and re-enter with refresh held; open Create/Edit; fail and retry a history read; switch tasks while a previous history is held; invalidate the Host lifecycle before an old read resolves.
- Expected: Definitions appear independently. Route return immediately shows the last successful in-memory task/outcome/history snapshot and selection. Project options are requested only for editing; the form waits for successful options and failures expose retry. Pending/failed run reads never say the task has not run. Failed refresh retains known history, and late responses cannot replace another task's data or repopulate an invalidated snapshot. Run now and Delete require known run status and retain authoritative Host admission.
- Coverage:
apps/desktop/test/scheduled-workspace-loading.test.mjsexercises the production hook with controlled API promises and poll ticks;scheduled-workspace.test.mjschecks rendered loading/error states. These are renderer regression checks, not a native Electron E2E run.
Also invalidate the Host while Create/Edit options are pending, with the old request resolving, rejecting or remaining pending. The next normal poll must replace that read and recover the same editor without Cancel/reopen. A failed replacement exposes Retry; retry success mounts the editor. Old responses must not overwrite recovered options. Successful options remain lazy rather than being fetched on every subsequent poll.
E2E-SIDEBAR-upgrade-layout-recovery
- In an isolated profile, retain two synthetic Projects and organize metadata. Close one Project and restart with the same app data root but fresh Chromium storage. Restore the retained Project and metadata; keep the closed tab closed.
- Migrate a valid legacy cache, including an empty list. If neither copy is valid, recover the Host catalog once; preserve subsequent explicit closes.
- Reject secondary-renderer/subframe IPC and oversized/invalid writes. Preserve unreadable backups, newer user edits, and the last good file on failure.
- Relocate internal retained paths and metadata keys without changing external paths, display names or the source profile.
- Automated path:
node scripts/e2e-sidebar-recovery.mjs, plus the sidebar backup, recovery, bootstrap and offline relocation regression tests.
翻译说明: 本页是与 英文源规格 一一对应的机器辅助翻译。代码、协议字段和标识符保持原文;如翻译与英文源事实有歧义,以英文版本为准。
范围:MVP 接受场景加上 PI-Desktop 当前发货的产品增量 状态:已接受(protocol/Electron 自动化已激活;完整桌面 Playwright 仍在计划中) 交叉引用:验收标准 · 里程碑 · ai-开发工作流程 · 变更清单
E2E-RUNTIME-text-tool-call-diagnostic
- Preconditions: Isolated runtime with a fixture provider and synthetic data.
- Steps: Return a complete text-form Qwen
TodoWritecall, alone and after normal prose. Repeat alongside a valid structured Bash call. Then return a normal answer. Repeat with fenced/inline/quoted/indented XML examples, incomplete blocks and an unknown tool name. - Expected: The literal known-tool call retains its text and displays
TOOL_CALL_NOT_EXECUTEDwith error message status. It never executes or activates the text-form tool, and never retries automatically. Structured sibling calls execute once and normal continuation succeeds. Examples, unknown names and incomplete blocks remain ordinary text. - Coverage:
text-tool-call.test.ts,malformed-tool-recovery.test.ts.
E2E-PI-130-kernel-upgrade
- Preconditions: Pi 1.3.0 with migrated patches, isolated fixture state, and no real credentials. Submit malformed tool JSON followed by a corrected call, test literal Unicode escapes, resume same/foreign-model thinking, and run two turns with extensions that guard
messageType === "user". - Expected: No malformed call reaches Host execution; raw errors reach the model, corrected calls succeed, foreign thinking is omitted, same-model reasoning survives, and prompt additions remain ordered without accumulating. Existing hosted-search, native-session, MCP, and cancellation suites remain authoritative. See the English source and
docs/project/pi-130-adoption.mdfor exact coverage and candidate validation.
E2E-PI-140-kernel-upgrade
- 前提: 在专属 worktree 中精确固定 Pi 1.4.0 release group 并迁移包补丁,使用隔离 fixture 状态,不提供真实 Provider 凭据。
- 步骤: 通过本地模型 fixture 发起 Desktop prompt 并捕获 Provider 请求身份;再通过 subagent 重复。用纯请求 fixture 覆盖 OpenRouter、NVIDIA 和 OpenCode 身份请求头映射。 在已安装的 1.4.0 包上运行现有 hosted-search、原生 Pi 会话和可信扩展离线套件。
- 预期: 主 Agent 和 subagent 请求保留 pi-ai 详细版本化的
User-Agent,并关闭 pi-agent-core 默认 Pi 归属请求头。Hosted-search 重放与校验、Provider 请求错误、原生会话 上下文估算、工具校验、扩展兼容、重试和取消行为保持不变。不使用 Provider 凭据或线上服务。 - 覆盖:
pi-upstream-1.4-identity.test.ts、pi-upstream-1.4-contract.test.ts、pi-1.4-adapter-features.test.ts、runtime.test.ts、subagent.test.ts,以及现有test:e2e:hosted-search和test:e2e:trusted-extensions命令。具体范围与候选验证记录见英文源及docs/project/pi-140-adoption.md。
E2E-PI-150-kernel-upgrade
- 前提: 在专属 worktree 中精确固定 Pi 1.5.0 release group(含新增的
pi-schema)并 重建包补丁,使用隔离 fixture 状态,不提供真实 Provider 凭据。 - 步骤: 在已安装的 1.5.0 包上运行现有 runtime、原生 Pi 会话和身份测试;经 1.5.0 增量解析的 Anthropic 适配器流式传输 hosted-search 块;构建拆分的 sidecar bundle;运行现有 hosted-search 和可信扩展离线套件。
- 预期: Desktop 用 TypeBox 构建的工具 schema 在 pi-schema 上校验不变。Hosted-search 捕获、实时查询更新、引用、重放与本地请求错误保持不变。Release group 只解析一个
undici8.x,sidecar 入口保持在体积阈值内。身份请求头报告 1.5.0。不使用 Provider 凭据或线上服务。 - 覆盖:
pi-upstream-1.5-identity.test.ts、pi-upstream-1.5-contract.test.ts、pi-1.5-adapter-features.test.ts、official-native-search.test.ts、native-pi-session.test.ts、agent-runtime-bundle-package.test.mjs、anthropic-oauth-retry.test.mjs,以及现有test:e2e:hosted-search和test:e2e:trusted-extensions命令。具体范围与候选验证记录见英文源及docs/project/pi-150-adoption.md。
E2E-LEGACY-DICTATION-idle-model-release
- 前提: 通过
VoiceService接口测试保留的本地 Dictation 服务,使用确定性的 Controller/ModelManager stub 和受控时钟。不访问物理麦克风,也不下载模型。 - 步骤: 启动并停止一次录音,将时间推进到 10 分钟空闲边界之前和边界时,检查 卸载调用。分别在边界前开始新录音、下一次启动被拒绝、进入 error 终态以及服务 dispose 时重复检查。
- 预期: 已结束的空闲模型只在边界到达时卸载一次,不会提前卸载。新录音会取消 计时器,其终态会启动新的等待窗口。新录音启动被拒绝时不会遗失卸载计划。回调时 处于活动阶段或服务已 dispose 都不会触发卸载。
- 覆盖:
apps/desktop/test/voice-service-idle-unload.test.mjs使用真实VoiceService生命周期,仅 mock 运行时边界和时钟。独立 Linux RSS 测量验证TranscribeModel.dispose()会向操作系统归还内存,且现有延迟加载路径可重新加载。 - 规格: 实时语音。
E2E-LIVE-VOICE-gemini-work-schema
- Preconditions: Production Gemini adapter with a deterministic transport fixture and a bound work profile; no credentials or hardware devices.
- Steps: Start the call with a work target, accept a valid setup, unmute and send PCM. Reject another setup with close 1007 and a UTF-8 reason. Repeat with a transport error followed by close; cancel and close the adapter.
- Expected: Gemini sends the supported Schema subset and connects; OpenAI's schema and Host argument validation remain strict. Setup rejection settles immediately as
LIVE_PROTOCOL_ERROR, transport errors remain network errors, and only one failure is reported. The raw reason reaches only the provider log; even the connect rejection crossing IPC contains only the public code. Cancellation suppresses terminal noise and close removes socket observers. - Specs:
03-runtime/live-voice.md,09-logging-and-observability.md. - Acceptance: C, E, Security, Quality.
- Milestone: Maintenance, issue #1812.
- Automation:
live-voice-gemini-adapter.test.mjs,live-voice-service-lifecycle.test.mjs, and voice-runtimelive/protocol.test.ts. - Status: Adapter/service contract-covered; full Electron/Gemini account and physical microphone acceptance remain separate and are not certified here.
E2E-LIVE-VOICE-public-settings-and-reconnect
- 前提: 生产 Renderer 构建、真实 Electron/Main/Host、隔离数据和测试项目、关闭开发者模式、本地 TLS Realtime fixture 及合成麦克风。仅在测试子进程中信任 fixture CA,不关闭 TLS、sender、沙盒或麦克风权限校验。
- 步骤: 在功能关闭时从 Composer 打开 Live,点击“打开设置”。通过设置搜索找到 Voice,绑定测试账号并启用;连接、取消静音、接收音频和字幕、静音并挂断。取消延迟启动,断开服务后显式重连;使用同一隔离配置重启,检查持久化设置并关闭功能。
- 预期: 普通用户可以到达入口;启用设置不会开始采集。真实构建中的 Renderer 通过精确主 frame 所有者校验。只使用所选账号,静音停止输入,每次结束释放媒体/socket,失败不会自动发起新通话。重启保留设置但不重连;旧 Dictation 设置不变,纯通话不创建 Agent 会话。
- 覆盖:
pnpm test:e2e:live-voice驱动真实构建及具体 Realtime GA adapter,连接本地 WSS;live-voice-owner.test.mjs打包生产 owner 模块并验证其他文件/frame 被拒绝。合成音频不代表物理设备或真实 Provider 验收;命令和结果见docs/implementation/live-voice-public-readiness.md。 - 规格: 实时语音。
E2E-LIVE-WORK-session-admission
- 前提: 使用隔离的 Live Provider fixture、本地 AgentHost 会话和确定性意图分类器;候选请求须从现有 Live adapter 回调进入,不使用真实账号或付费端点。
- 步骤: 验证纯通话没有工作作用域时会拒绝任务候选;再以明确选择的本地会话和关闭的上下文共享启动工作通话。提交一个已声明工具请求并检查回执、Host admission 和
voiceOrigin。覆盖忙时独立排队、过期 steer、精确回合 stop、submit 返回前到达的终态事件,以及基于已记录结果的只读查询。请求项目/会话列表,确认只返回标签和本次 call 的 opaque 引用;通过面板动作打开已列会话、在已列项目中新建会话,并确认工作绑定仍固定。分别保持 Provider 生成、用户讲话和本地播放活跃,确认反馈须待三者空闲和静默窗口结束后才发送。覆盖 silent、静默时的显式查询、过期反馈降级;当提交处于等待用户决策状态时,确认紧凑栏保留等待提示、详情显示待决请求并提供打开该精确会话的操作,且其他会话的待决请求不会被归到绑定会话;当提交处于等待用户决策状态时,确认紧凑栏保留等待提示、详情显示待决请求并提供打开该精确会话的操作,且其他会话的待决请求不会被归到绑定会话。随后用语音回答该 AskTool 提问,确认会话卡片经由 Host 输入路径解决;再分别用提问从未提供的标签、只回答一部分、以及同时存在两个待答提问重试,确认每次都被拒绝、不产生写操作且不重试。结束通话时确认已受理工作仍保留。 - 预期: 工作始终绑定到通话开始时选定的会话;关闭上下文共享时不读取历史;现有 AgentHost 执行 prompt、steer、queue 和 stop;重复 Provider ID 不重复派发;结果查询只投影准确操作摘要且不会创建新回合。项目/会话选项不暴露原始路径或 ID,引用按 call 隔离并过期;打开只导航,创建必须先列出注册项目并由用户点击确认。自动反馈同时观察 Provider 生成状态和本地播放活动,任务执行与反馈投递状态分开。结束 Live 不取消已受理任务;Renderer 信号不代表用户已经听到结果。
- 覆盖:
apps/desktop/test/live-voice-service.test.mjs覆盖纯通话拒绝、显式工作作用域和 Provider/用户/本地播放的反馈门控及独立投递状态;packages/host-runtime/src/live-work/coordinator.test.ts覆盖回执顺序、路由、过期 steer、去重、关闭通话、早到终态和选择流程;packages/host-runtime/src/live-work/feedback-scheduler.test.ts覆盖防抖、播报间隔、silent、过期降级、去重与溢出;packages/host-runtime/src/live-work/context.test.ts覆盖上下文投影与 opaque 引用;packages/host-runtime/src/live-work/result-summary.test.ts覆盖精确回合摘要和诚实回退;packages/agent-host/src/agent-host.test.ts覆盖无历史快照、队列和语音来源;packages/voice-runtime/src/live/protocol.test.ts覆盖 Provider 工具及反馈编码;apps/desktop/test/live-work-scope.test.mjs覆盖引用作用域/过期,live-work-operations.test.mjs覆盖面板动作,live-work-decision.test.mjs覆盖等待决策投影、会话作用域、有界纯文本渲染与打开绑定会话的面板操作,voice-runtime/src/live/playback-monitor.test.ts覆盖本地音频信号检测;packages/host-runtime/src/live-work/ask-answer.test.ts覆盖语音回答意图与路由(解决、拒绝、unknown 且绝不重发),apps/desktop/test/spoken-answer.test.mjs覆盖只选既有选项的匹配、有界朗读内容与投递预算。该自动化覆盖仍不等于 W2-001—W2-096 全矩阵,也不代表真实 Provider/设备验收。 - 状态: 部分完成;其余场景和实机矩阵见
docs/implementation/live-work-evidence.md。
E2E-LIVE-WORK-v2.1-reliability
- 前提: 隔离的本地 Host 会话、fixture 分类器和 fake runtime/provider I/O。不使用真实账号、用户项目或正在运行的 Desktop 实例。
- 步骤: 在 Provider 候选入口捕获 null/活动回合目标并延迟分类;让通过校验的 stop 与普通分类竞争;让 Host 派发保持未决,随后返回精确的只读准入证据;先到达终态事件再返回写操作 ACK;在派发期间关闭 Live;在 AgentHost 准入前更改已授权的工作区;提交独立工作队列项;延迟最终助手消息持久化;在 stop 控制操作后查询任务结果。
- 预期: 过期/null 目标不会变成之后的回合。stop 屏障会撤回尚未跨过准入边界的较早写操作。忙碌时的普通提交不会悄悄进入队列。Host 状态不确定时显示 unknown,并且只核对、不再次写入。终态证据不会被迟到 ACK 复活或覆盖。工作区不匹配不会创建回合或队列项。空闲时的显式队列项由 AgentHost 排空,恢复后挂起的条目保持挂起。结果查询只返回准确的任务结果,不返回准入或 stop 确认。
- 覆盖:
packages/host-runtime/src/live-work/coordinator.test.ts、packages/host-runtime/src/live-work/operation-ledger.test.ts、packages/agent-host/src/agent-host.test.ts、apps/desktop/test/live-work-production-composition.test.mjs和apps/desktop/test/agent-host-bridge-work-ack.test.mjs。生产组合 fixture 实例化 Live Work bridge、AgentHost bridge、已注册的agentPrompthandler 和真实 AgentHost;Host Core 持久化、具体 Provider adapter/transport 和真实设备旅程不在该测试边界内。 - 状态: 自动化覆盖为部分完成,逐项记录于
docs/implementation/live-work-v21-coverage.md。实机验收未运行。 - 规格: live-work-session、live-voice。
E2E-POWER-keep-awake-setting
- 前提: 设置值缺失的隔离桌面配置;无需真实模型服务。
- 步骤: 在「设置 > 常规」开启「保持电脑唤醒」,确认主进程即使空闲也只持有 一个
prevent-app-suspension阻止器。使用同一配置重启应用,确认恢复一个 阻止器。开启和关闭「阻止屏幕休眠」时,确认「保持电脑唤醒」的系统请求仍在。 关闭「保持电脑唤醒」,确认立即释放,并在退出时确认清理。 - 预期: 设置持久化并立即生效,不会重复创建阻止器;关闭开关或退出应用 时释放。屏幕开关拥有独立阻止器,不能关闭系统休眠阻止器。手动睡眠和合盖 不在该功能的保证范围内。
- 状态:
pnpm test:e2e:keep-awake使用隔离的真实 Electron/Host 配置; 当基线不存在其他 Electron 电源请求时,以 Windowspowercfg /requests验证系统请求。控制器生命周期和 Host 设置往返另有定向测试。
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 验证英文与中文交互;不代表真实服务商测试。
E2E-TRANSCRIPT-lossless-tool-images
- Entry:
PI_DESKTOP_HOST_BIN=<candidate-host> pnpm test:e2e:tool-result-persistenceafter building Host, shared, agent-runtime and host-runtime. - Environment: Real isolated Host, SQLite and JSONL, production Desktop IPC and outbox; only Electron shell and unused importer/search services are stubs.
- Build 35 deterministic valid PNGs / 548 rows. Verify the old redundant full request exceeds 64 MiB, each stored image occurs once, and restarting restores every canonical image, detail field and original JSON text spelling.
- Build a second history whose unique image bytes alone exceed 64 MiB through individually legal appends. Exercise bounded ID operations and retained-image restoration, revision/fork, missing anchors and duplicate operation retries.
- Unit/process gates also cover old-format files/outbox, corrupted references, failed conversion/drain, late callbacks and real sidecar restore errors.
- The original Windows incident did not identify the failed RPC method; this deterministic regression does not claim to recover that missing evidence.
1. 目标
- 记录 MVP 必须验证的每个用户可见和协议可见的行为。
- 提供映射到验收标准 (A–H) 和里程碑 (M1–M6) 的场景目录。
- 作为可追溯性主干:场景 ID ↔ 验收标准 ↔ 规范。
- 定义代码 pull request 的相关 E2E 合入门。
- 让验证证据与准备合入的可执行提交保持关联。
E2E-LIVE-VOICE-four-stage-ui
- 标题: 已禁用、准备、紧凑通话栏和主动打开的详情。
- 前提: 使用隔离的开发构建 Electron 配置,开启开发者模式,Live Voice 初始为禁用状态,并使用确定性的本地服务商/媒体 fixture 和本地工作会话 fixture。不使用真实账号、麦克风、扬声器、付费端点或用户正在运行的 Desktop。
- 步骤:
- 确认禁用时 Composer 中既没有 Live Voice 图标,也没有 Work 图标。仅从 「设置 → 语音」启用 Live Voice,并确认空闲时恰好显示一个语音图标。
- 打开准备界面,展开并折叠工作选项,但不开始通话。确认没有准备请求、 麦克风获取、媒体初始化、服务商连接或工作派发。让选中的绑定不可用, 同时保持另一个绑定就绪:界面显示确切的选中身份和原因、禁用开始操作, 且不会隐式切换服务商。
- 绑定就绪后,明确开始并观察紧凑栏显示「连接中」和「取消」,确认不会 自动打开详情。在准备或媒体获取仍未完成时取消,再送达延迟的完成事件。 再次开始并连接,确认默认静音,然后取消静音/静音并检查状态。
- 打开详情,检查转录、服务商和工作状态,然后分别通过关闭、点击外部和 Escape 关闭详情。通话保持连接;通过通话栏恢复暂停的声音,观察恢复失败, 再明确重试并恢复。
- 在聊天、设置、插件和会话间导航。通话控件是停靠的桌面挂件窗口:拖动它自身的 通话栏,确认位置在这些导航后仍然保留、并会被夹取回工作区内,同时确认主窗口 不再绘制任何通话栏。在 Main 延迟终止和 Renderer 延迟清理的 情况下结束通话或禁用功能,并分别测试两种清理完成顺序。在两者完成前, 始终显示「结束中」,且不允许再次开始。模拟释放未确认,确认阻止状态可见。
- 重新打开准备界面,确认工作授权和上下文确认均未勾选。仅展开区域或选择 会话不会开始通话;未授权工作时开始只启用语音。明确授权工作请求、选择 有效的本地目标并开始;在详情中检查固定绑定和工作结果。结束后重新打开 准备界面,再分别变更/创建目标、取消工作授权或取消通话,并确认上下文 确认被重置。结束 Live 不会停止已受理的工作。
- 使用已配置的切换快捷键直接开始纯语音通话并结束。启动期间,如果弹窗 消费了 Escape,则它只会关闭弹窗,不会取消启动;没有弹窗消费时,启动取消 快捷键才生效。Escape 不会结束已连接的通话。用英文和简体中文分别检查 可见控件,并确认所有已发布语言目录都齐全。
- 预期: 四种明确的界面状态、主动启动和工作授权、确切绑定的就绪状态、 默认静音、全局清理状态可见、控件无障碍属性和焦点返回正确,且 IPC、持久化或 权限边界没有变化。恢复播放和错误处理不需要打开详情。
- 规格: Live Voice、 Live Work、 组件规格、 设置 IA。
- 验收: C / E / Security / Quality。
- 里程碑: MVP 后实验性交互维护。
- 覆盖: 定向控制器/展示回归测试和
pnpm test:e2e:live-voice-interaction覆盖已挂载的生产控件、控制器、store、快捷键、i18n、清理顺序、工作授权, 以及使用模拟 shell 导航和伪造外部边界的错误恢复。该测试不会挂载完整的 AppShell/设置/插件路由,也不会实际使用服务商、设备、权限提示或音频输出。 - 状态: 请求候选 fixture 的 48/48 项交互检查通过。完整 AppShell、真实服务商 和设备兼容性尚未验证。
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 及 arm64 发行目标 (D126/D285、D638 / ADR 0318) |
| 公司简介 | 干净的 ~/.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-keep-explicit-model-overrides:目录刷新不覆盖用户限额与能力选择
- 前提条件:已配置一个目录发布上下文窗口、最大输出 token 和图片输入能力的模型。
- 步骤:编辑该模型,将最大输出 token 设为通用种子值 8,192;将图片输入开关先关再开,使显式选择与当前目录值相同;保存并重开设置。再用更新后的目录记录刷新该模型。
- 预期:最大输出 token 仍是 8,192,且来源标记为
user,即使上下文窗口来源为catalog;图片输入仍保持用户显式选择。目录修正只更新来源标记为catalog的限额,不会把用户显式设置重新解释为跟随目录。 - 关联规格:
03-runtime/11-provider-model-system.md、03-runtime/12-provider-config-schema.md、03-runtime/13-model-catalog-and-selection.md - 状态:由 shared/runtime 与 host-core 回归测试覆盖;真实桌面 UI 验收待执行
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、03-runtime/21-image-generation.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 已被本次发现结果描述过时,行为与之前一致:一行可用,通用种子,列表既不卡住也不重复。 该 id 以另一种拼写发布(路由前缀、日期戳、部署自己追加的标记)同样算命中: 答复到达即就地升级该行,不必等到保存。 - 实时发现的模型行若由服务报告正安全整数上下文/输入或输出上限,则该字段优先使用服务值;未报告字段独立回退到目录元数据。对于未锚定的中继,若模型所有者发布了匹配记录,则优先使用所有者记录;带点/连字符版本别名和一个白名单部署标签不会改写服务端 ID,所有者记录冲突时不会猜测限额。 所有者的兄弟键仅在公开键没有表态时补充证据;若至少两个独立发布者一致声明同一规范模型指向所有者前缀下的另一名称,可按 models.dev 的 canonical 声明转换名称,容量仍取自所有者自己的记录,且不改写服务端 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与apps/desktop/test/provider-endpoint-metadata.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) 让服务离线后再点「获取列表」;确认分类错误以 toast 出现且先前的行仍在。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 文档化的非空占位符——压缩后不得静默回填空串。每条 assistant 消息只带一个推理字段,字段名取最新一条 assistant 行所用的字段;存放在其他字段名下的真实推理会被移到该字段,而不是被占位符替代,占位符只填充完全没有推理的行(D677)。思考面板和回放历史都不会把开头回显的占位符当作推理显示或回传。中转请求不携带通用 OpenAI 的reasoning_effort字段,因为有中转被报告拒绝它(#1664);官方api.deepseek.com请求同时发送thinking: {type: "enabled"}与所选的reasoning_effort(#1821)。不会仅因模型 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-005F:自定义端点输入护栏
- 前提条件:应用已运行;新增提供商对话框已打开并选中自定义端点。
- 步骤:1) 只填
api.gateway.example.com(不带协议),离开 Base URL 输入框。2) 确认输入框归位为https://api.gateway.example.com,并开始发现。3) 粘贴https://api.gateway.example.com/v1/messages后离开输入框。4) 确认输入框归位为https://api.gateway.example.com/v1,接口格式显示 Anthropic Messages,且表单提示该格式为自动识别。5) 手动把接口格式改为 OpenAI Chat Completions,再粘贴/v1/responses地址:确认手动选择的格式保持不变,后缀在应用建议前原样保留。6) 把值改成ftp://gateway.example.com后离开输入框。7) 重新填入有效地址,确认可以开始发现;粘贴完整/models路径后离开输入框。8) 指向一个仅在/v1下响应/models的网关,确认输入框与保存后的行都显示该地址。 - 预期:裸主机名会补上
https://,仍限定在用户填写的来源内;含凭据、查询或片段的地址一律拒绝。粘贴的接口路径既指明格式并自动选中,也会从基础地址中移除,/models同样移除;只有当它与用户手动选择的格式冲突时才保留,并改为给出建议。当格式来自端点推断时,表单会在选择器旁说明。当只有/v1候选应答时,输入框与保存的行显示该地址,而不是隐藏改写,且每个被探测的候选都限定在用户填写的来源内。非 http(s) 地址显示内联可访问错误、不启动发现、保持保存禁用;有效地址恢复发现。较长的地址输入框在宽对话框下独占整行,并在响应式断点下与其他凭据自然堆叠。 - 链接规格:
04-ux/06-settings-ia.md、03-runtime/12-provider-config-schema.md - 验收:B(自定义提供商配置)
- 里程碑:M2
- 状态:单元覆盖(端点解析与候选探测);界面场景待完成
E2E-005G:按供应商自定义 HTTP 请求头
- 前提条件:一个 API 密钥 AI 服务(含 OpenCode Go)和一个已登录的厂商 OAuth 账户。
- 步骤:打开高级设置弹框,确认只有标题栏关闭按钮而没有底部操作行;用常用预设添加 User-Agent,导入超过五行可见区域的 JSON,再复制请求头 JSON;确认列表最多显示五行,更多请求头在自身区域滚动;确认剪贴板是与持久化相同的规范化对象(忽略空名称,后者覆盖前者),并有本地化成功反馈。然后填入含全角字符的值(
0、123),确认编辑器提示将按半角保存,保存后重新打开显示为 ASCII;再填入含汉字或 NUL 的值,确认保存前有提示、保存被拒并指出字符与下标;最后用规则生效前写入的数据打开一次回合。 - 预期:复制输出与保存到该行的
pairsToRecord映射一致;导入仍接受两种 JSON 形状;窄宽度下工具栏换行而不溢出。空映射恢复适配器默认值。含全角字符的值保存为半角、读取时再次半角化,因此规则生效前存下的行仍能正常跑回合;含汉字或控制字符的值在保存时被拒(给出字符与字符下标),运行时直接丢弃而不是让Headers.set抛错。 - 链接规格:
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,再发送一次回合。5)在同一账户上选择 Claude 模型(Anthropic Messages)并发送回合,记录认证与身份标头。
- 预期:每次 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 仍是最后的覆盖层。Claude 请求携带Authorization: Bearer <Copilot token>,不携带X-Api-Key,并保留相同的 IDE 身份与上下文标头。 - 链接规格:
03-runtime/11-provider-model-system.md、03-runtime/12-provider-config-schema.md、ADR 0095 - 验收:B(模型配置)、F(运行时提供商请求)
- 里程碑:M2
- 状态:单元覆盖(行级模型标头、请求上下文标头、Anthropic Messages Bearer 认证与令牌轮换);真实 Copilot 账户旅程待验证
E2E-005K:OpenCode Go Claude 模型使用目录发布的 Anthropic 线路
- 前提条件:使用内置预设配置 OpenCode Go;确定性目录 fixture 包含
claude-haiku-5-5,其声明api: "anthropic_messages"和 OpenCode Anthropic 根baseUrl,另有一个 DeepSeek Chat Completions 对照模型。 - 步骤:1)选择 Claude Haiku,通过捕获 fixture 发送文本回合。2)记录请求 URL 和适配器请求体。3)换成 DeepSeek 对照模型重复。4)将相同目录元数据放到自定义 OpenAI 兼容提供商上重复。
- 预期:OpenCode Claude 使用 Anthropic 适配器并访问
/zen/go/v1/messages,不再 发往 Chat Completions,也不返回ModelProtocolUnsupported。DeepSeek 保持/chat/completions。自定义提供商即使匹配到外部目录 API,仍使用保存的 wire 格式与 Base URL。单元覆盖位于packages/agent-runtime/src/provider-binding.test.ts。 - 链接规格:
03-runtime/11-provider-model-system.md、03-runtime/13-model-catalog-and-selection.md、issue #1728 - 验收:F(运行时提供商请求)
- 里程碑:M2
- 状态:单元覆盖;真实端点场景草稿
E2E-MODELS-list-layout:受限窗口中的模型列表可完整浏览
- 前置条件: 60 个已发现并配置的模型;隔离的 Electron 环境使用真实 服务与账号对话框、状态逻辑及生产样式。
- 步骤: 分别在 850×600、850×850、1000×560、1200×700、1600×1000 的内容区打开两种对话框,把每份列表滚到最后一个和第一个模型。
- 期望: 每份列表至少能完整显示一行;首尾模型均完整位于列表、 对话框正文和视口内。窄窗口上下堆叠,较矮的宽窗口保持左右布局。
- 命令:
pnpm test:e2e:provider-model-layout - 边界: 模型发现 IPC 使用测试数据,不涉及真实凭证或宿主持久化; React、浏览器布局和滚动均为真实实现。
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 文件不会触发预览调用。第二个提示的第一个请求可以恢复 有效上下文中仍存在的成功激活标记,包括已激活但尚未调用的工具。新结果 使用details.addedToolNames,历史details.activated和顶层addedToolNames也可读取;失败、中断或缺少结果的行不会恢复工具。目录 或模式变化会阻止恢复,且恢复不会授予主机权限或工作区逃逸。 - 链接规格:
03-runtime/02-agent-runtime.md§7.1,03-runtime/03-tools-and-permissions.md§2.1、ADR 0048、ADR 0225、08-meta/decisions-log.md(D185、D400) - 验收: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-011c-1:命令完成后保留新草稿
- 先决条件:会话 A、B 均存在;可暂停
/compactAPI 响应。 - 步骤:在 A 发送
/compact并暂停响应。将命令文字替换为新草稿后释放响应; 分别重复测试:在未修改的命令文字旁添加图片,以及完成前切换至 B。测试reentered变体时, 在 compact pending 期间先将编辑器改为临时文字并 dispatchinput,再输入完全相同的/compact并再次 dispatchinput,然后释放响应。最后运行一次完全未编辑的命令。 - 预期:完成后 A 的新文字和图片附件仍保留,B 的草稿不受影响,返回 A 后可恢复其新草稿。
reentered即使最终文字与提交值相同,成功后仍显示/compact;未编辑的提交草稿则正常清空。 - 覆盖:
scripts/e2e/composer-submission.tsx,由pnpm test:e2e:composer-paste执行;真实桌面录屏使用隔离数据,以及响应时间受控的本地模型。
E2E-011c-2:迟到的发送失败不会恢复旧草稿
- 前提条件:会话 A 处于空闲状态;可分别暂停并结算该输入框的两次发送结果。
- 步骤:提交草稿 N,在 N 结算前再提交 N+1。先接受 N+1,再拒绝 N。然后保持 N 未结算,重复测试并拒绝 N+1。
- 预期:N 的迟到失败不会把 N 的文本或文件引用恢复到 N+1 上。如果 N+1 被拒绝,只恢复 N+1 自己的快照。
- 关联规范:
04-ux/09-interaction-patterns.md(按会话保存的草稿) - 验收:C(输入框和会话隔离)
- 里程碑:M2
- 状态:异步所有权行为由
apps/desktop/test/composer-submit-race.test.mjs覆盖;完整 UI 场景仍为草稿
E2E-SESSION-reference-conversation-link:粘贴的会话链接把那段对话带进本轮
- 先决条件:隔离的桌面配置;一个项目内有会话 A 与 B,另一个项目内有会话 C; 已配置一个会依据给定上下文作答的模型。
- 步骤: 1) 在 A 的对话溢出菜单选「复制会话链接」,读取剪贴板。 2) 打开 B,把链接粘贴进输入框草稿并发送,检查用户消息。 3) 在 B 里追问一次,不再重复链接。 4) 把 C 的链接粘贴进 B 并发送。 5) 在 A 里发送 A 自己的链接。 6) 从 A 的对话溢出菜单给它改名,然后回到 B。
- 预期:剪贴板内容是
pi-desktop://session/<A 的 id>。B 的用户消息保留可见的链接 文本,并显示聊天图标芯片,标签为语言目录里的引用标签加 A 的标题;激活芯片会打开 A。 回答使用 A 的内容,追问仍然读到同一份引用,而无需再次写入链接。C 的链接与 A 自己的 链接什么都不附带:消息保持纯文本,模型收不到它们的任何摘录——解析器在任何读取之前 丢掉自引用,并丢弃属于其他项目的对话摘录。 事后给 A 改名,B 里的芯片会跟着改名——芯片按被引用对话的当前标题命名;模型已经读过的 摘录则保留引用产生时记录的名称。 - 关联规范:
04-ux/08-component-spec.md§20B、03-runtime/04-data-storage.md、03-runtime/02-agent-runtime.md - 验收标准:C — 对话与流
- 里程碑:M2
- 状态:Draft — 解析器与芯片已由源码级测试覆盖:
apps/desktop/test/session-references.test.mjs、apps/desktop/test/session-reference-ui.test.mjs
对话顶部栏
E2E-087:对话顶部栏在聊天路径上呈现
- 先决条件:已配置提供商;至少存在一个会话。
- 步骤: 1) 打开聊天路径。 2) 检查顶部的 46px 栏 交谈区。 3) 确认其显示简洁的 session/task 标题和新任务/搜索/命令 动作按钮;确认 侧边栏切换仅在侧边栏折叠时出现(展开时, 侧边栏拥有该控件)。 4)切换到 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;再检查@模式中的长文件名。 在 800px 和 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) 不响应权限卡。2) 确认卡片仍可见且工具仍在等待。3) 点击拒绝并观察结果。
- 预期:没有倒计时或自动拒绝;请求会一直等到明确拒绝,工具不会执行。
- 链接规格:
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) 要求代理生成临时文件(例如一次性脚本)。2) 观察写入位置及是否出现权限卡。3) 检查
git status和工作面板。4) 在 Windows 上选择 Git Bash,打印$PI_SCRATCH_DIR并通过该路径写入文件。5) 删除会话并检查<data_dir>/scratch/。 - 预期:文件在没有权限卡的情况下写入
<data_dir>/scratch/<sessionId>/;项目git status保持干净;临时写入不会打开文件或 Review 工件标签;Git Bash 收到与提示一致、可直接使用的正斜杠路径,而 PowerShell 和 cmd 保留原生路径格式;删除会话会删除临时目录。 - 链接规格:
03-runtime/03-tools-and-permissions.md §4b、03-runtime/04-data-storage.md - 接受:E(与工作区隔离的临时文件)
- 里程碑:M5
- 状态:部分自动化(host-core 单元测试:双根解析、暂存读写、Windows Git Bash
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-MCP-stdio-windows-npx:官方 Node 与 fnm 都能启动 npx MCP(issue #789)
- 先决条件:Windows;Node 要么是官方
Program Files\nodejs安装 (PATH 上有node.exe、npx.cmd、npx-cli.js),要么由 fnm 管理且不在 GUI PATH 中。 - 步骤:1) 从 MCP 市场添加 Memory(
npx -y @modelcontextprotocol/server-memory)。 2) 测试连接。3) 再用用户手写的npx服务器重复一次。 - 预期:官方 Node 改写为
node.exe+npx-cli.js并完成握手。PATH 没有 node 时从%LOCALAPPDATA%\fnm\aliases\default发现 fnm。其余.cmd经cmd.exe /d /s /c启动,参数加引号保持字面量,不用shell: true。 与npx.cmd同目录的 Git-Bash 无扩展名npx不会被选中。真正缺失时仍报告command not found: npx。 - 链接规格:ADR 0038、D624、
07-plugins/04-plugin-security.md - 接受:质量
- 里程碑:M6+
- 状态:自动化(
apps/desktop/test/mcp-stdio-launch.test.mjs)
会话持续性
E2E-020:会话在重新启动后仍然存在
- 先决条件:存在具有消息历史记录的会话。
- 步骤:1) 退出应用程序。 2)重新启动。 3) 打开会话列表。
- 预期:出现上一个会话;消息可恢复。
- 链接规格:
03-runtime/04-data-storage.md、03-runtime/10-session-state-machine.md - 接受:F(会话在重新启动后仍然存在)
- 里程碑:M2
- 状态:自动(协议烟雾:主机级持久性;完全重启通道手动)
E2E-021:删除会话作品
- 先决条件:展开的侧边栏底部附近有一个会话。
- 步骤:1) 打开该会话的溢出菜单。 2) 确认菜单和“删除”操作均处于窗口范围内。 3) 在较短的窗口中重复,并滚动菜单到最后一项。 4) 删除会话并观察会话列表。
- 预期:菜单保持在视口内,最后一项可访问;删除后会话从列表消失且数据已移除。
- 链接规格:
03-runtime/04-data-storage.md、04-ux/09-interaction-patterns.md - 接受:F(删除会话)
- 里程碑:M2
- 状态:单元测试覆盖(
sidebar-floating-menu.test.mjs、sidebar-navigation.test.mjs);视口交互场景仍为草案
E2E-021c:底部项目菜单贴近锚点显示
- 先决条件:展开的侧边栏中,最底部可见项目行下方空间不足以容纳项目菜单。
- 步骤:1) 右键该项目行并查看菜单。2) 关闭菜单,再打开该行的溢出按钮菜单。3) 确认所有操作都处于视口内。
- 预期:两种入口在下方空间不足时,都会按菜单实际高度紧贴项目行上方显示;空间足够时则显示在锚点下方。菜单不会跳到远离项目的位置或被裁切。
- 链接规格:
04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md - 接受:品质(侧边栏交互)
- 里程碑:M2
- 状态:自动化(
node scripts/e2e-three-column-layout.mjs --sidebar-only、sidebar-floating-menu.test.mjs、sidebar-navigation.test.mjs)
E2E-036:设置会话导入目的地已移除
- 预期:设置中没有会话导入选项卡或扫描面板。插件仍可通过宿主拥有的会话 API 导入会话,相关边界由 E2E-214 / E2E-215 / E2E-216 覆盖。旧本地会话扫描器只由导入器单元测试覆盖。
- 链接规格:
04-ux/06-settings-ia.md、04-ux/08-component-spec.md、07-plugins/03-plugin-api.md、ADR 0319 / D645 - 状态:D645 退役;设置 UI 已移除
E2E-037:核心会话导入项目流程已退役
- 预期:会话导入时的项目绑定与渲染器刷新由插件会话导入 E2E-216 验证。设置不再调用旧的核心会话扫描/导入路径;插件归属、持久项目记录和项目存档行为保持不变。
- 链接规格:
03-runtime/04-data-storage.md、04-ux/01-ui-ia.md、07-plugins/03-plugin-api.md、ADR 0319 / D645 - 状态:由 E2E-214 / E2E-216 取代
E2E-038:设置拥有项目存档目标
- 先决条件:应用程序已开启开发者模式,并至少配置一个提供商、保留一个项目且有一个已存档项目。
- 步骤:
- 打开设置并检查完整的设置导轨。
- 打开“常规”,使用可搜索主题选择器更改外观卡中的主题。
- 打开“AI”并检查权限和默认项卡,包括命令 Shell 行;确认上下文管理没有设置卡。
- 打开快捷方式并检查键盘快捷方式卡。
- 打开指令并保存全局指令。
- 打开“模型”并检查提供商工作室和内嵌导入操作。
- 打开“技能”和“MCP”,确认各自都有外部导入操作。
- 按顺序打开“项目”和“信息”。
- 搜索设置“项目”或“存档”。
- 在“项目”中,将每个分组条的计数与其呈现的行进行比较。
- 将排序控件从“最近”切换到“名称”。
- 搜索已知的会话标题,确认所属项目被选中且检查器列出匹配会话,然后显示八个以上的会话;通过清除控件清除搜索。
- 打开检查器菜单,使用 Escape 和外部按下来关闭。
- 恢复存档的项目,然后激活它。
- 归档一个项目会话,从项目归档中打开它,然后返回项目归档。
- 返回应用程序外壳并打开插件。
- 预期:导轨按顺序包含外观、对话、快捷键、语音、行为、模型、指令、技能、MCP、子智能体、项目、系统、云同步、远程主机和信息,分别使用其语义 Lucide 图标(Palette / MessageSquare / Keyboard / Mic / Sliders / Bot / FileText / BookOpen / Server / Sparkles / Archive / Monitor / CloudDownload / Globe / Info),没有两个目的地共用同一字形;目的地按“偏好”(外观、对话、快捷键、语音)、“智能体”(行为、模型、指令、技能、MCP、子智能体)、“工作区”(项目)和“系统”(系统、云同步、远程主机、信息)分组。组间使用留白而非分割线,搜索保持目的地结果扁平,并同时隐藏空分组及其标题。模型、技能和 MCP 页分别拥有内嵌的外部导入工作台;打开工作台不会扫描。设置没有会话导入面板,会话导入由插件 API 提供。选中行带强调色底片与强调色字形,内容区以同一字形在页面尺度开场。没有「常规」也没有「AI」目的地(D684):主题与字体归外观,转录与输入框归对话,网络/存储/电源归系统,权限模式、默认模式和命令 Shell 行归行为 → 会话默认;可用的已选 Shell 由选择器表示,不重复显示“已配置”状态;默认、回退和无实际 Shell 状态仍明确展示;上下文管理没有设置卡;键盘快捷键和全局指令都有自己的目的地;“开发者”位于“信息”下;项目存档显示活动、关闭和存档的持久行,无需可见性切换,将它们分组在一个面板内始终可见的固定/所有项目/存档分组条 (D168/D267) 下,并带有每个部分的计数。该目的地不呈现英雄区块,也没有页面级计数器串:引导区是一条安静的说明行,每个分组条的计数与其呈现的行一致;按名称排序会重新排序每个部分内的行,而不隐藏任何行;搜索匹配项目字段和会话标题并报告匹配计数,会话标题结果扩展其所属项目,按最新活动和相对更新时间列出会话,并以八个批次显示历史记录;清除搜索将恢复完整索引。行菜单在 Escape 和外部按下时关闭。恢复使存档保持打开状态,激活返回以与侧栏中保留的恢复项目聊天。打开归档会话时,应用会先成功选择该会话,再清除它的归档状态;聊天页选中该会话,项目侧边栏重新显示它,返回项目归档后它也不再处于归档状态。主页侧边栏和全局页面结果没有独立的项目目标;设置搜索找到项目存档;插件仍然是一个独立的应用程序外壳目的地。 打包构建会从导轨和设置搜索中省略云同步;见
04-ux/06-settings-ia.md。 - 链接规格:
04-ux/06-settings-ia.md、04-ux/01-ui-ia.md、03-runtime/11-provider-model-system.md - 接受:B(模型配置),F(项目存档与持久化)
- 里程碑:M5
- 状态:单位覆盖(
settings-project-archive.test.mjs、sidebar-navigation.test.mjs);渲染场景草稿
E2E-091:外观卡通过可搜索主题和语言选择器选择
macOS OS-event acceptance: PI_E2E_SYSTEM_APPEARANCE=1 node scripts/e2e-browser-system-appearance.mjs. The opt-in probe temporarily changes OS appearance and restores it in a finalizer. It mounts the bundled Browser with production lifecycle functions in sandboxed Electron, checks light/dark/light paint, fixed app/plugin palettes, view recreation, process restart and listener removal. Host/plugin-registry edges are synthetic; this does not claim an installed full-app settings journey. With Browser open, select System and switch OS light → dark → light. The shell and Browser toolbar update together. Explicit Light, Dark and registered plugin palettes remain fixed across OS changes; repeat after shutdown/restart.
- 先决条件:应用程序在 macOS 上运行;测试工具可以切换英语、简体中文、繁体中文、土耳其语、德语、西班牙语、法语、韩语和巴西葡萄牙语系统区域设置。
- 步骤:
- 打开设置 → 外观。
- 打开外观卡中的主题选择器。确认系统、浅色、深色钉在顶部;选择深色并确认触发器显示「深色」,且 UI 切换为深色。
- 选择浅色并确认 UI 切换为浅色。
- 打开语言行的可搜索选择器。确认“跟随系统”钉在顶部并显示检测到的本地名称,且列表按本地名称列出 English、简体中文、繁體中文、Türkçe、Deutsch、Español、Français、한국어 和 Português (Brasil)。系统区域设置为简体中文时选择“跟随系统”会应用简体中文;系统区域设置为繁体中文时会应用繁体中文;系统区域设置为巴西葡萄牙语时会应用 pt-BR。
- 依次选择 English、简体中文、繁體中文、Türkçe、Deutsch、Español、Français、한국어 和 Português (Brasil),确认外壳文案无需重新加载即可切换。确认
zh-Hant和zh-HK解析为繁体中文,de-DE解析为 Deutsch,es-MX解析为 Español,fr-CA解析为 Français,ko-KR解析为 한국어,pt、pt-BR、pt_BR和pt-PT均解析为 pt-BR。 - 在语言搜索中输入本地名称或英文名称,确认不匹配的语言消失。在主题搜索中输入主题名称,确认不匹配的选项消失。
- 预期:主题和语言都是可搜索的选择行(不是卡片网格,也不是原生 select);关闭时的触发器按当前文案收缩、不超过设置控件列且不溢出该行。主题列出系统、浅色、深色,插件主题在分隔线之后。“跟随系统”通过主进程 (
app.getLocale()) 解析操作系统区域设置,安全地通过沙盒 preload 桥传递,并在菜单内嵌显示检测到的本地名称;zh-TW、土耳其语、德语、西班牙语、法语、韩语和巴西葡萄牙语都是包含发行说明文案在内的完整外壳目录;切换选项会立即更新 UI,无需重新加载。 - 链接规格:
04-ux/06-settings-ia.md、04-ux/02-i18n-english-first.md - 验收:A(核心壳)、H(本地化)
- 里程碑:M4
- 状态:部分自动化(
scripts/e2e-settings-scroll.mjs覆盖 pt-BR 选择和界面切换);完整多语言列表及 OS“跟随系统”矩阵仍为文档化验证。
E2E-SETTINGS-ai-tab-pickers-use-in-app-menus:行为页下拉使用应用内菜单
- 先决条件:应用正在运行,主机至少报告一个已配置的命令 Shell,以及至少一个在当前平台不可用的目录内 Shell。
- 步骤:
- 打开设置 → 外观,展开主题和语言选择器,记下药丸触发器与展开后的表面。
- 打开「行为」。
- 展开权限卡的权限模式控件,依次选择询问、接受编辑、自动。
- 展开默认项卡的命令 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) 以默认窗口宽度打开“常规”,并记录内容卡宽度。 2) 将窗口扩展到 1600px 宽。 3) 打开模型、技能、MCP 和项目。 4) 将窗口缩小到支持的最小 800px。
- 预期:右侧内容卡在每个测试宽度上随可用窗格展开和收缩; 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,以及带相邻 Bash 行的 多项命令片段。2) 检查已展开的活动组;在更多输出到达时手动收起该组。3) 完成回合, 确认恰好一个过程披露拥有全部回答前的行;收起它并确认其下的组与命令行一并收起,再 重新展开并确认组选择恢复。4) 展开一个已完成组和其中一个条目,复制输出,收起并重新 展开该组,再独立打开同级组。5) 在
过程显示的「平铺显示」「命令工具优先」 「全折叠显示」之间切换,检查分组、组边界、命令行的渲染位置以及披露数量。6) 分别检查 单项片段,以及最后一个活动组的字面最后一项为工具/搜索、思考、失败工具和被拒工具。 7) 关闭并重新打开Show thinking,检查活动思考、失败后恢复及完成状态。8) 在保留窗格内 重新挂载行,然后重载渲染器并重新打开会话。 - 预期:一个回合只渲染一个过程披露,其中包含全部回答前的行,
Bash/run 行也不例外; 末尾回答、助手错误和中止后的末尾文字留在披露之外,待处理操作也一样。活动组默认展开, 并且只在用户未操作时于完成后收起;其他已完成组默认收起。收起回合披露会一并收起其下 的组与命令行,重新展开则恢复用户的组选择。「全折叠显示」(默认)中,命令行留在所属组内, 只有普通助手进度文字会拆分活动组;「命令工具优先」中,命令行作为组旁的独立行渲染, 相邻调用保持分离,详情在打开前保持收起,普通助手进度消息或命令会结束当前组; 「平铺显示」中不渲染嵌套组,各行在外层披露内保持转录顺序。所有模式都只渲染一个披露。 单项没有组包装。除显示模式的思考默认值外,没有任何条目载荷会自动展开:工具/搜索行在 用户打开之前保持标题行,而思考项绝不会选择更早的工具。回合标题按Running、Finished、Stopped或Failed报告结果,失败与停止优先于正常完成,当前步骤为推理时显示Thinking,绝不回显当前命令;转录未记录时长时不显示时长。当Show thinking关闭时, 不再渲染任何已完成的推理行,仅在推理流式输出期间保留活动的 TextShimmerThinking指示器;重新打开时恢复。父级、子级和同级选择彼此独立;收起/重新展开父级会保留后代, 流式更新和完成不会覆盖用户接管的选择。保留窗格中的重新挂载保留选择;渲染器重启重新 应用默认值,但工具名称、参数、结果和状态仍会恢复。组标题显示有界运行和问题摘要,不会 把整个回合标记为失败。 - 链接规格:
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
- 状态:草案。单元与样式检查覆盖显示模式、思考默认值与标题规则;本场景仍是转录 重新加载的预期人工行为。
E2E-TRANSCRIPT-invalid-utf8-window-remains-readable
- 先决条件:隔离的 Host 配置中有一个会话转录,损坏行前后都有有效消息。完整行内含非法 UTF-8 字节:一份夹具把字节放在消息文本中,另一份使消息行无法解析。不使用真实用户数据。
- 步骤:通过渲染器的有界
session.get路径以messageLimit打开会话,分页经过损坏行, 再通过 Host API 读取完整历史。比较读取前后的转录文件。 - 预期:会话仍可读取。前后有效消息按顺序出现;可解析的损坏文本以 U+FFFD 替代非法字节, 无法解析的行会跳过。源转录逐字节不变,分页位置保持一致。
- 链接规格:
03-runtime/04-data-storage.md、ADR 0127。 - 验收:C(对话和流)、F(持久性)、质量。
- 里程碑:维护。
- 自动化:
pnpm test:e2e:transcript-cache通过隔离 Host 的有界session.get验证替代文本 和源文件不变;crates/host-core/src/transcripts_layout_tests.rs直接覆盖坏行与布局读取。完整 Renderer 用户旅程仍为草案。
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;对声明了manifest.renderer的插件,删除它命名的文件后重复一次。 3) 恢复它,声明contributes.skills而不声明agent.prompt.inject,然后再次运行check。 4)pnpm pi-plugin pack <dir>。 5) 从插件页面安装生成的.piplug。 6) 要求代理在同一目录上运行PluginCheck和PluginPack。 - 预期:脚手架插件检查干净并报告其文件计数和大小;缺少
main或manifest.renderer入口是一个阻止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:脚手架→检查→按模板打包,存储方法标头,每个检查规则,以及把permission.high-risk列表与权限矩阵 high 行对齐的一致性测试);安装步骤已记录
E2E-023:全局搜索中的插件命令并执行
- 前提条件:插件已加载并启用。
- 步骤: 1) 打开全局搜索(Cmd/Ctrl+K 或 Cmd/Ctrl+Shift+P)。 2) 在“命令”部分下找到插件命令。 3)执行。
- 预期:命令出现在全局搜索结果中;执行产生预期结果。
- 链接规格:
07-plugins/09-plugin-command-palette.md - 接受:G(插件命令出现并执行)
- 里程碑:M4
- 状态:草案
E2E-PLUGIN-command-ownership:命令注册遵循插件归属
- 前提条件:两个插件分别运行在独立的插件宿主进程中。
- 步骤:1)由一个插件注册命令。2)尝试通过另一个插件覆盖并注销该命令。 3)由所属插件更新并注销命令。4)验证加载期间发生 ID 冲突和并发注册的情况。
- 预期:跨插件覆盖命令返回
CONFLICT错误,跨插件注销返回PERMISSION_DENIED错误;调用被拒绝后,原命令仍可正常执行。所属插件更新命令时保留原 ID,重复注销不产生 副作用,释放后的 ID 可被重新使用。插件加载失败或未成功注册命令的插件被卸载时,不会 移除已成功注册的命令。并发注册同一 ID 时,仅一个插件能够成功取得该命令的归属权。 - 关联规范:
07-plugins/03-plugin-api.md、07-plugins/12-plugin-ipc-and-host-services.md - 验收项:G(插件命令隔离)
- 里程碑:M4
- 状态:已自动化;
pnpm test:e2e:plugin-command-ownership通过真实插件宿主进程和 宿主代理,验证公开插件 API 的调用行为。
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:刷新官方市场目录
- 先决条件:网络可访问
plugins.aiuo.net。 - 步骤:1) 打开扩展 → 市场。 2) 使用标题栏的刷新市场操作。 3) 确认官方目录刷新且卡片网格更新。
- 预期:市场使用
plugins.aiuo.net/catalog.json;页面没有来源选择器;刷新失败时仍可使用缓存的官方目录。 - 链接规格:
07-plugins/07-plugin-marketplace.md - 接受:G(远程市场来源)
- 状态:已记录/涵盖 host-core 单元
E2E-024P:市场忽略旧版来源设置
- 先决条件:配置文件可预置旧版
pluginMarketSource值github、mirror、custom,其中包括一个自定义 URL;官方目录网络可用,或可使用本地请求存根。 - 步骤:1) 分别使用每种预置配置启动应用。 2) 打开扩展 → 市场并刷新。 3) 检查目录请求。 4) 从市场安装一个已发布插件。
- 预期:页面没有来源控制;所有配置都请求
https://plugins.aiuo.net/catalog.json,并忽略持久化来源与自定义 URL;安装使用官方平台 resolve,必要时从官方目录 URL 回退并校验摘要;旧版设置仍可读取,无需迁移。 - 链接规格:
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) 通过胶囊关闭面板,以及通过禁用或卸载插件关闭面板。4) 从「扩展 → 已安装」打开一个初始页面加载一直 pending 的测试面板;等待 15 秒后再打开正常面板。 - 预期:面板在沙盒 window/partition 中运行;桥接调用经过许可检查;主机在面板关闭时保持稳定,主进程不得抛出
TypeError: Object has been destroyed或弹出未捕获异常对话框。初始加载持续 pending 达 15 秒的面板以PANEL_LOAD_TIMEOUT失败,通过发起操作显示错误,并销毁隐藏窗口;页面自行加载失败时保留原始错误,超时后仍可正常打开其他面板。 - 链接规格:
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:插件主题适用并在撤回时回退
- 先决条件:可从市场或
.piplug包安装的examples/plugins/hello变体(demo.hello),其midnight主题 CSS 引用已声明的包内相对图片art/preview.png;CSS 使用@import或远程url()的拒绝用插件及仅在注释中出现这些关键字的变体;另有声明图片资源和windowAppearance背景、分别带/不带ui.window.appearance权限的主题变体。 - 步骤:1) 从市场安装 Hello 包变体或导入其
.piplug包,在设置 → 外观 → 主题中选择Hello Midnight。2) 重启应用。3) 禁用提供主题的插件。4) 重新启用后卸载。5) 加载不安全 CSS 插件。6) 加载仅在注释中提到关键字的变体。7) 在 Windows/Linux 与 macOS 上选中资源变体主题,验证包内相对图片通过plugin-asset:在外壳和插件面板中渲染,并加载引用未声明相对资源的样式表以验证其被拒绝。8) 去掉ui.window.appearance后取消选中该主题。 - 预期:带包内相对图片的插件包成功安装,图片在插件根目录内解析;插件主题出现在选择器中并立即应用,图片通过
plugin-asset:提供;选择在重启后仍保留为plugin:demo.hello:midnight;禁用或卸载提供程序会退回到system而不是无样式的 shell;不安全 CSS 在加载时被拒绝并记录原因,且不注入<style>元素;仅在注释中提到关键字的样式表正常加载并提供主题,因为消毒器只检查浏览器实际生效的 CSS;声明的资源在宿主外壳与插件自己的面板中都经plugin-asset:渲染,未声明引用被拒绝并记录原因,声明的背景在 Windows/Linux 上给原生窗口着色、在 macOS 上不下发;取消选中主题或收回权限后窗口回到宿主背景;整个外壳都跟随主题,包括工作面板列、标题栏以及浏览器/文件查看器条目栏,其表面色读取--ds-bg-dock/--ds-bg-dock-raised而不是字面量。 - 链接规格:
07-plugins/02-plugin-manifest-schema.md、07-plugins/04-plugin-security.md§3.1、04-ux/07-ui-design-system.md、D175 - 接受:G(主题贡献)+安全
- 状态:单元覆盖(
plugin-themes.test.mjs、theme-cssSDK 测试、host-core 包内相对资源/安装测试);视觉场景草稿
E2E-024K:插件 MCP 服务器工具到达代理
- 先决条件:针对可信局域网存根声明一个
stdio和一个非回环 HTTP MCP 服务器的插件;已授予mcp.server.local和mcp.server.remote;HTTP 主机已列入net.domains;保存存根凭证的设置密钥。 - 步骤: 1) 启用插件并确认尚未启动服务器进程。 2) 要求代理调用已发现的工具。 3) 检查存根收到的 environment/headers。 4) 使存根调用失败,再执行耗时超过 10 秒握手预算但少于 100 秒调用预算的 HTTP 工具,最后让另一次调用超过自身预算。 5) 让 stdio 存根的目录超过旧的 64 个工具上限并重新发现。 6) 禁用插件。
- 预期:服务器在首次使用时延迟连接;工具在
risk: "medium"上显示为plugin_demo_*_<serverId>_<tool>,并进行每次调用审核;stdio 子级仅接收声明的env值加上 PATH/temp/locale,从不接收主机提供程序密钥;非回环 HTTP 端点只有在主机列入白名单后才会接受,未加密传输会在审查中显示;跳转到未声明主机时会在第二次请求前阻止;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-MCP-CANCEL:停止操作只中断调用方会话的 MCP 请求
- 先决条件:两个 Agent 会话共用用户或插件 MCP 服务器,均有待处理工具调用,服务器记录取消通知。
- 步骤:两项调用待处理时停止第一个会话,再让第二项调用完成;分别对远程 HTTP 和 stdio 服务器重复。
- 预期:第一个请求收到
notifications/cancelled,停止后不会产生成功的工具结果;第二个会话的调用正常完成且连接可继续使用。关闭应用会取消其余请求并清理监听器。 - 关联规格:
03-runtime/01-ipc-protocol.md§12a、07-plugins/04-plugin-security.md§8.1 - 状态:客户端及会话隔离已有单元测试;完整桌面流程待验证
E2E-MCP-tool-requires-approval:用户 MCP 工具在 ask 与 accept-edits 下需要审批
- 先决条件:一个绑定项目的 Agent 会话;一个用户配置的 stdio MCP 服务器,其工具列表把某个工具标注为只读/低风险。
- 步骤:1) 会话处于
ask时,让代理调用该 MCP 工具。2) 以“允许一次”回应卡片后再次调用,再以“本会话允许”回应并第三次调用。3) 在新会话中切到accept-edits并重复调用。4) 切到auto调用。5) 依次切到 Plan、Goal 调用。 - 预期:在
ask与accept-edits下,每次调用都显示审批卡片,原因为 "MCP server tool requires approval",风险为medium,与服务器自行声明的标注无关。“允许一次”只覆盖该次调用;“本会话允许”只在该会话内对同一mcp_<serverId>_<tool>名称不再提示,不覆盖该服务器的其他工具。auto不显示卡片直接执行。Plan 与 Goal 即使存在会话授权也拒绝。 - 关联规格:
03-runtime/03-tools-and-permissions.md、05-security/01-security.md、D640、ADRmcp-tool-approval-risk - 状态:已有单元测试(host-core
permissions.rsMCP 风险与模式测试);桌面流程待验证
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 (≥800×560),在 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 或 arm64 打包应用;本机 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-192a:Linux arm64 发布通道发布原生 arm64 包
- 先决条件:
vX.Y.Z标签与apps/desktop/package.json一致;仓库可使用 GitHub 的 arm64ubuntu-22.04-arm运行器。 - 步骤:1) 运行标签发布工作流程。2) 确认 arm64 通道运行在
aarch64运行器上,且打包出的target/release/pi-desktop-host-core是 AArch64 二进制。3) 检查 已发布的 Release 资产中是否有PI-Desktop-X.Y.Z-linux-arm64.AppImage、pi-desktop_X.Y.Z_arm64.deb、pi-desktop-X.Y.Z-aarch64.rpm、PI-Desktop-X.Y.Z-linux-arm64.asar,以及带.sha256的pi-host-X.Y.Z-linux-arm64.tar.gz。4) 确认latest-linux.yml仍列出 x64 AppImage,而latest-linux-arm64.yml列出 arm64 的那个。5) 在 arm64 Linux 机器上安装 arm64 的 AppImage、deb 或 rpm 并启动它。 - 预期:两个更新源各自只描述一个带架构标记的 AppImage, arm64 包携带 arm64 host-core,应用能在 arm64 Linux 上启动, 且合并通道永远不会用 arm64 更新源替换 x64 更新源。
- 链接规格:
06-delivery/06-release-runbook.md、01-product/01-product-scope.md - 验收:质量(发布工件与打包兼容性)
- 里程碑:M6+
- 状态:矩阵、更新源命名、工件命名和 ASAR 导出由单元/源代码契约覆盖 (
ci-workflow.test.mjs、release-asar.test.mjs); 原生 arm64 安装仍需运行器验证。麦克风采集在 arm64 Linux 上仍仅限 Raspberry Pi (D638 / ADR 0318)。
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-048c:分离含聊天的项目文件夹并单独删除
- 前提: 一个保留的逻辑项目包含一个主文件夹和一个有至少一条已保存聊天的附加文件夹;所有聊天均处于空闲状态。
- 步骤: 1) 打开“编辑项目”并移除附加文件夹。2) 保存后检查剩余项目组及该文件夹对应的独立项目行。3) 打开独立项目中的聊天,确认对话记录完整。4) 对独立项目选择“删除项目”,并在确认对话框中明确确认。5) 检查剩余项目组、项目列表、聊天列表及磁盘上的文件夹。
- 预期: 从项目组移除文件夹会保留其项目记录、聊天和对话记录,并将其作为独立项目显示。新聊天使用该项目的路径级上下文;共享说明和记忆仍属于原项目组。确认删除后会移除该项目的会话及对话记录,但保留磁盘上的文件夹;原项目组仍保留主文件夹。
- 链接规格:
03-runtime/04-data-storage.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、ADR 0249、ADR 0251。 - 验收: C(项目和聊天交互)、F(持久化)、品质。
- 里程碑: M5
- 状态: Host RPC 集成测试已覆盖;完整 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×与中键关闭。将一个标签拖到另一个标签的前后,确认显示插入指示器、 顺序更新且当前激活标签不变;在标签条左右边缘保持拖拽,确认隐藏标签自动滚动到可见, 插入指示器跟随目标更新;再使用Alt+ArrowLeft/Alt+ArrowRight重复验证。 4)打开+的 Tools & panels 菜单,验证 浏览器和当前范围内的插件视图各出现一次,并处于活动状态, 打开-非活动和关闭状态,并且出现转录打开的资源 仅在第二部分。使用指针和键盘打开/选择每个可用视图, 重新打开已有 URL 的浏览器并确认 URL 仍然存在,然后执行 具有 ArrowDown/ArrowUp/Home/End 的行(焦点必须跳过关闭按钮),关闭 删除非活动行并确认菜单保持打开状态并将焦点放在 邻居,按 Esc 键并确认焦点返回到触发器,然后关闭 标题中的活动标签。确认+停留在 最短和最长标签的标题右边缘。缩小窗口(或安装足够多的插件)使启动器 工具行超出面板高度:New 标题保持固定,行列表内部滚动,底部各行仍可 通过滚轮、滚动条和 Tab 焦点访问(#2026)。 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) 重新启动。 16) 打开一个插件视图, 使用 Cmd/Ctrl+R 重新加载主渲染器。确认原生视图在新渲染器出现前分离, 然后重新打开对应选项卡,确认缓存的插件页面恢复。再测试同文档导航和 子框架导航;两者都不应分离活动视图。17) 保持 Browser 标签活动并右键另一个标签, 确认仍走原生菜单且 Browser 页面保持可见;在 50% 和 150% 应用缩放下重复, 菜单锚点应保持在指针处。18) 菜单打开时关闭窗口,确认待处理请求会结束。 - 预期:启动时不显示面板、欢迎选择器、固定工具按钮或 titlebar/menu 启动器。 Cmd/Ctrl+J 打开活动会话的面板 无需创建资源选项卡即可提交宽度;重复它是幂等的, 如果没有活动会话或“设置”处于活动状态,则快捷方式不会执行任何操作 打开。每个工件自动打开停靠的第三列并创建或 激活一种资源;文件资源是路径键控且重复的资源 去重复。打开、折叠和 关闭动画面板的 width/flex 分配及其有界 opacity/slide,因此 MainChat 连续重排,无需预动画跳转。 打开面板、折叠面板或调整分隔线大小 更改操作系统窗口大小 - 仅 MainChat 在内部回流 固定客户区 (ADR 0033)。一旦面板打开,标题栏显示可滚动的标签条和固定
+;+菜单只列出 Review 与当前范围内的插件视图,不重复已打开的标签。标签 溢出时只有标签条滚动,活动标签滚动到可见范围;拖拽标签显示前后插入指示器, 靠近边缘保持拖拽会自动滚动到隐藏标签,松开后只更新标签顺序且保持当前资源激活;Alt+ArrowLeft/Alt+ArrowRight提供相同的 键盘重排路径。菜单淡入超过 ≤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 原生内缩但保留该操作带。 主渲染器完整主框架重新加载时,可见插件视图会分离但不会关闭缓存页面,因此新渲染器不会被孤儿原生层覆盖;重新打开选项卡会重新挂载同一页面。同文档和子框架导航保持视图附着。Browser 活动时右键其他标签也会使用位于原生视图上方的系统菜单;其标签、顺序和禁用状态与渲染器菜单一致,位置随应用缩放正确换算,关闭窗口会结束待处理请求。 以前的上下文面板覆盖不再存在。 - 链接规格:
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、plugin-work-panel-views.test.mjs、plugin-view-navigation.test.mjs);隔离 Electron 菜单和关闭路径已覆盖; 完整渲染器重新加载 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-CHAT-turn-changed-files-summary:已完成的回合列出它改动的文件
- 先决条件:具有可写工作空间的项目绑定 Agent 会话。
- 步骤:1) 让代理创建一个文件并编辑另一个文件;观察流式中的回合。 2) 回复完成后,检查回答下方区域。3) 请求一次跨多个目录、超过十二个文件的 改动。4) 点击一行,再点击
在「审阅」面板查看全部 N 个文件链接。 5) 在审阅中回滚一个文件的变更后回到该回合。6) 提一个不写文件的问题。 7) 切换会话后再切回来。 - 预期:回合流式期间或失败后不显示汇总。写过工作区文件的已完成回合在回答 下方、插件
entryExtra块上方显示一张无边框的本轮改动卡片,带文件数和合计+/−。每个路径只出现一次,带合并后的状态字母和计数;不超过四个文件用宽松行, 更多时用带目录数的密集行,超过 12 行折叠为+N more。点击一行会打开该会话的 审阅选项卡,滚动到该文件并展开其变更;链接只打开审阅、不展开。已回滚的文件仍在 列表中并显示删除线。每个更早的已完成回合保留自己的卡片,没有写入的回合没有卡片, 切换会话不会额外取数。逐次调用的审核行保持不变。 - 链接规格:
04-ux/08-component-spec.md§4.4,D674 - 验收:D(工作空间),质量
- 里程碑:M5
- 状态:单元覆盖(
turn-changes-card.test.mjs);完整的 UI 场景草稿
E2E-CHAT-completed-turn-scrolls-to-prompt:完成的回复从它的提示读起
- 先决条件:一个 Agent 会话,其下一条回复比转录视口更长。
- 步骤:1) 发送提示,让转录跟随回复直到结束。2) 发送一个回复短到能在屏幕内 放下的提示。3) 再发送一个长回复的提示,并在流式期间向上滚动。4) 在长回复中途 停止。5) 在长回复流式期间排队一个后续提示。
- 预期:1) 回复完成时,它的提示位于视口顶部并出现“滚动到底部”。2) 短回复 停留在底部并保持跟随。3) 视图停留在用户滚动到的位置。4) 被中止的回合停在底部。 5) 后续回合继续在底部跟随。
- 链接规格:
04-ux/09-interaction-patterns.md§2.2,04-ux/08-component-spec.md§4.4,D676 - 验收:质量
- 里程碑:M5
- 状态:已自动化(
pnpm test:e2e:transcript-turn-completion,在 Electron 中 运行真实的useTranscriptScroll;turn-completion-scroll.test.mjs)
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 工件;工作区内外均有 HTML 测试文件。
- 步骤: 1) 激活工件,依次输入不带协议的
localhost:<port>和localhost:<port>/index.html并提交;再输入带端口的点分主机名 (如example.com:8080),验证显式 HTTP(S) 地址及被拒绝的javascript:地址。 2) 导航站点链接;使用 back/forward/reload/stop。 3) 触发window.open弹出窗口和权限请求页面(例如通知 提示)。 4) 打开全局搜索,然后打开设置。返回聊天 并触发内联工具权限卡。 5) 切换到另一个面板选项卡 然后回来;关闭面板。 6) 使用“在外部打开”。7) 分别从已有页面和空白浏览器标签页, 使用平台对应的file:地址和绝对路径打开工作区内已存在的 HTML 文件;再使用平台对应的 URL 和绝对路径打开工作区外已存在文件(Unix 如file:///tmp/demo.html和/tmp/demo.html;Windows 如file:///D:/demo.html和D:\demo.html)。接着从已加载的 本地 HTML 页面点击链接,打开另一个工作区外文件;从 HTTP 页面尝试同一链接,并输入缺失的本地文件路径。 - 预期:无方案输入标准化为 http;导航状态(URL 栏, back/forward 启用、加载微调器)镜像页面。弹出窗口仅在 URL 解析为 http(s) 或 mailto 时打开默认浏览器(绝不在应用程序内); 弹出的
file:、javascript:和自定义 scheme 被拒绝。许可请求被拒绝; 顶层导航允许 HTTP(S)、工作区内文件链接,以及从本地文件页面发起的已有本地文件链接。 HTTP 页面不能导航到工作区外文件;缺失文件、目录、远程文件 authority 和其他 scheme 均被阻止。 预览隐藏在每个遮挡下 覆盖并卸载时,随后以正确的边界重新出现。 内嵌权限卡不会隐藏或重新挂载预览;resize/drag 期间原生视图保持可见, 并与占位符矩形对齐,不再闪出黑色面板底色。打开工作面板上下文下拉框时, 原生视图在不透明菜单边界下方保持可见,菜单关闭后恢复完整表面矩形。 Open-external 对 http(s) 页走系统浏览器,对任意已加载本地文件走openPath。 视图使用隔离的持久分区(应用程序外壳中没有会话流失)。工作区内外的地址都能加载; HTTP 页面发起的工作区外链接与缺失路径不会进入 guest。失败时地址栏保留输入, 浏览器用当前语言说明只能打开已存在的本地文件。 - 链接规格:
03-runtime/01-ipc-protocol.md§13a、04-ux/08-component-spec.md、05-security/01-security.md、ADR 0019、ADR 0168、ADR 0328 - 验收:质量、安全
- 里程碑:M5
- 状态:部分自动化:
scripts/e2e-browser-local-file-access.mjs覆盖原生本地文件导航;地址栏与遮挡层交互仍需手动验证。
E2E-BROWSER-capture-resize:截图期间保持预览位置并恢复最新视口
- 前提条件:隔离的 Electron 配置;本地响应式网页高于浏览器可见视口。无需配置模型服务。
- 步骤:开始整页截图并保持其等待状态,期间两次调整浏览器占位区域;通过原始
Page.captureScreenshot重复。截图等待期间依次最大化、还原、进入和退出全屏,使面板移动或改变尺寸。 同时覆盖:排队中的截图、切换资源标签、隐藏并恢复截图页、截图失败、带排队任务的标签关闭,以及销毁后的 guest 重建。 - 预期:截图等待期间,原生 guest 跟随面板当前位置,并裁剪在面板边界内,不覆盖聊天区域。 截图视口在完成前保持稳定,完成后
innerWidth/innerHeight与最新请求尺寸一致。同一页面的截图串行执行, 不阻塞其他标签。失败截图会解除尺寸锁定;隐藏或关闭页面不会意外显示页面,也不会把排队截图转发到其他页面。 重建或释放销毁的 guest 后不残留孤立原生视图。无需重复截图来修复布局。 - 超时恢复:任一截图 API 超过 30 秒仍未完成时,以
TIMEOUT失败,分离卡住的 CDP 会话并释放该页面的截图队列;后续截图可以重新连接并继续。 - 关联规范:英文源规格中的 E2E-BROWSER-capture-resize。
- 状态:原生 Electron 截图/尺寸及窗口切换期间的位置验证由
node scripts/e2e-browser-capture-resize.mjs自动执行(保留产物)。apps/desktop/test/browser-capture-resize.test.mjs覆盖生产 Host/Pane/CDP 路径、失败、排队、标签关闭、guest 重建及面板裁剪。
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 秒主框架等待上限后保持隐藏并显示错误,需重试。慢图片或子框架不阻塞已提交页面的显示; 页面尚未加载完成就导航时,旧请求的 ERR_ABORTED 不得阻止新地址、标题和加载状态更新。
- 关联规范:
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-responsive-resource-tabs
- 前提:浏览器已启用;隔离配置;本地页面包含延迟 17 秒的图片、延迟主响应及普通/新窗口链接。
- 步骤:输入慢图片页面地址,检查加载反馈及图片完成前的显示;打开两个聊天链接和网页新窗口链接, 切换标签,在其中一个标签内导航后再切走、切回;检查慢主响应、停止、失败、重试及外部打开设置。
- 预期:主框架导航提交后显示页面;新链接保留原标签,各标签保留表单、滚动位置、JS 状态和独立历史,切换不重新请求页面; 普通导航只更新原标签。失败/停止后地址栏仍可用;外部模式不新建工作面板标签。 会话切换、非法协议的隔离和可见性规则继续生效。
- 补充路径:通过 ToolSearch/BrowserPreview 预览文件,原标签的未提交表单和地址不被改写; 后台导航恢复到对应会话的标签,旧加载完成不能覆盖新意图;新空白标签地址和状态为空; 关闭后台标签销毁对应 WebContents,不影响其他标签,删除会话或销毁插件/窗口释放所属页面。
- 状态:隔离 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-radial-background-menu:对话空白处的环形菜单
- 前提条件:设置 → 外观 → 环形右键菜单可用;会话中有一条已完成的用户提示、一条已完成的助手回答,且历史足以滚动。
- 步骤:1)关闭开关,右键最后一回合下方的空白处,确认弹出纵向对话菜单。2)打开开关,右键同一位置,检查四个扇区及其静止时显示的图标与动作名。3)让指针经过各扇区,点击复制整段对话;重新打开后,在某扇区上按下右键并原地松开,确认动作执行。4)把「上」设为复制最后一条回复、「右」设为留空,重新打开环形菜单。5)不移动指针直接点击中心圆;再重新打开后依次按 Escape、点击外部、滚动、调整窗口尺寸;再次打开后在扇区上按下右键、移到中心圆松开,确认不执行任何动作。6)用 Shift+F10 打开,用方向键遍历扇区,按 Enter 执行一项。7)重启应用,确认开关和四个方向都保留;再把某个方向的未知动作写入设置文件,确认该方向回退到默认值。
- 预期:开关关闭时纵向菜单原样打开。开关开启后,以指针为圆心的四扇区环形菜单取代它,靠近窗口边缘时仍完整可见。每个扇区在悬停前就显示该动作的图标与名称;指针所在扇区以整块填充高亮,不出现聚焦方框;点击后执行该动作、关闭菜单,复制类动作照常弹出提示。菜单打开时按下右键,在哪个扇区松开就执行哪个动作;在中心圆上松开只关闭、不执行动作,打开菜单那次右键尾随的松开不触发任何动作。「留空」方向渲染为禁用扇区且不执行任何动作;尚无回答文本时复制最后一条回复为禁用。打开浏览器会让工作面板显示一个空白浏览器标签页;工作面板方向是切换:面板收起时显示「打开工作面板」,打开后改为「关闭工作面板」,再点一次收起;打开项目文件夹会在系统文件管理器中打开会话所属项目,没有项目路径的会话为禁用。点击中心圆、外部按下、Escape、Tab、滚动和窗口尺寸变化都只关闭菜单。方向键移动高亮,Enter 执行当前动作。开关和四个方向在重启后保留,存储中的未知动作回退到该方向的默认值。消息行、链接、文件引用、侧边栏和工作面板仍使用各自的纵向菜单。
- 关联规范:
04-ux/09-interaction-patterns.md§1.11、04-ux/06-settings-ia.md - 验收:C(聊天流)、质量
- 里程碑:M5
- 状态:Chromium 组件检查(
node scripts/e2e-radial-menu.mjs:静止扇区文案、指针高亮、点击/中心圆/禁用/Escape 路径、右键标记手势、方向键焦点和视口限制)加单元覆盖(radial-menu.test.mjs、settings-roundtrip.test.mjs);完整桌面旅程仍为草稿
E2E-CHAT-delete-turn-preserves-other-live-outputs:删除回合后保留其他实时输出
- 前提条件:会话有三个已完成的回合。渲染器包含这三个回合的助手和工具输出,但由于 outbox 追加仍在等待,持久化内容至少缺少一条输出。
- 步骤:删除中间的用户回合;转录替换完成后重新加载会话。
- 预期:被删除的用户回合及其输出仍然不存在;第一个和第三个回合的用户、助手和工具消息在删除前后及重新加载后都保留。待处理的追加不能在转录替换后执行并恢复被删除的回合。
- 关联规范:
03-runtime/04-data-storage.md、03-runtime/06-host-rpc-protocol.md、ADR 0060 - 验收:C(聊天流)、F(持久化)、质量
- 里程碑:M6+
- 状态:单元已覆盖(
delete-message-preserves-transcript.test.mjs、persistence-outbox.test.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) 确认已完成回合答案下方只有模型徽章、没有 上下文检查器。确认回合用量胶囊紧跟模型徽章,显示平均速度和紧凑总量; 悬停或用键盘聚焦胶囊,检查缓存读取/输入/输出精确数值、回合合计,以及 仅在非零时出现的缓存写入。将鼠标移入浮层确认其保持打开,然后用 Escape 和外部指针按下关闭。3) 将鼠标悬停在输入框工具栏的检查器触发器上,确认面板 不会打开,然后点击它。4) 检查剩余令牌 + 百分比标题、已用/窗口计数、 无卡片的本轮/速度数值、内联模型用量摘要和聚合工具摘要,确认标题下 没有双线、弹层内部没有分隔线。5) 面板打开时滚动记录、调整窗口大小。 6) 将鼠标移出面板,然后点击触发器、面板外部,并使用 Escape 关闭。 7) 在浏览器预览产物上打开工作面板,打开上下文检查器,确认该摘要仍留在 会话面板内:完全可见、不进入面板列,且在较窄面板上收窄而不是被裁剪。 8) 空闲时点击该回合的重试。9) 确认没有用量的会话仍提供重试但不显示 输入框检查器。
- 预期:有模型 id 时答案下方显示模型徽章;该回合有用量时,回合用量 胶囊紧跟其后。胶囊速度等于总输出令牌除以总响应时长,合计为视觉回合内 输入/输出/缓存读取/缓存写入之和,推理令牌计入输出,子代理开销不计入。 浮层在指针停留其上时保持打开,并在指针离开的宽限后(触发器仍保持键盘焦点 时除外)、Escape 或外部按下时关闭;浮层传送到 body、限制在会话面板内, 流式回合不显示胶囊。有用量后, 紧凑上下文检查器出现在输入框右侧工具栏、模型选择器左侧,并始终对应最新 一条已报告用量的助手回合。触发器显示圆环和剩余容量百分比(无 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) 使用“标记全部已读”,然后“清除”。 11) 当本机任务横幅和渲染器刷新仍在进行时,投递一个针对已清除/已读 durable id 的延迟
notification.changed负载,以及一个已存在 id 的重复负载。 - 预期:A 的可见当前完成不会创建任何行。正好两行 存在,最新的优先:未聚焦的 A 完成和背景 B 失败, 带有本地化标签、快照会话标题和 B 的稳定代码。 Abort/repeated 终端调用不创建行。前页脚帮助快捷方式 缺席; 32px 页脚钟形件及其向上开口的弹出框取代了它。 徽章和未读显示确切的未读计数,无需隐式打开 读取行。读取状态和两者 记录在重新启动后仍然有效。行选择标记其已读取并激活其绑定 project/session。该装置准确地保留了最新的 200 行。全部标记 保留零个未读行。选择其他会话会清除其 终端侧边栏标记并标记其任务通知已读;都没有标记 刷新或重启后返回。清除仅清空收件箱并离开 会议、回合和成绩单完好无损。清除/已读也会关闭所有待处理的任务本机通知;重复 durable id 不会产生第二个横幅或行,清除前的延迟事件不能复活已清除项目;清除后真正的新回合仍只产生一行和一个横幅。
- 链接规格:
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 上,检查本机通知属性, 通知设置条目、任务栏组、已安装的可执行文件和开始 菜单快捷方式。 9) 将同一 durable id 投递两次,然后在本机对象仍由操作系统持有时标记已读并清除收件箱。
- 预期:当前焦点 A 既不创建收件箱行,也不创建本机横幅。 聚焦背景 B 创建一个没有本机横幅的收件箱行。不专心 当前 A 和最小化故障分别创建一个持久行和一个 本地化的本机通知。单击可恢复、显示并聚焦 激活匹配会话之前的主窗口,即使通知已经进入 Windows 操作中心; 没有事件打开错误当前选定的会话。中止不显示两个表面。操作系统抑制确实 不会丢失持久行或出现误导性应用程序错误。每检查一次 Windows系统表面识别
PI-Desktop;无库存 Electron 应用程序 姓名或身份被暴露。重复投递必须幂等:一个 durable id 最多拥有一个活动的本机对象,标记已读/清除会关闭该对象,迟到的激活或渲染器事件不能再次显示旧任务。 - 链接规格:
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)在偏好未设置时点击关闭按钮(或按关闭窗口快捷键)并回答提示: 确认显示主题化应用内对话框且初始焦点为“取消”;按 Escape、点击遮罩或取消都会保持窗口打开且偏好仍未设置;关闭到托盘隐藏窗口、显示托盘图标,应用继续运行(进行中的一轮保持存活);退出则退出应用。renderer 不可用时回退到原生对话框。2)逐个重跑每种选择, 验证它在完整重启后仍被记住,并且一旦存在选择就再也不会出现第二次提示。 3)在设为
tray时点击托盘图标:窗口恢复、显示并获得焦点;托盘右键菜单 提供打开和退出,退出会退出应用。4)在设置 → 通用中在关闭到托盘 / 退出应用 之间切换,验证下一次关闭遵循新的选择、两种选择下 D216 的托盘图标都常驻、 未设置的偏好不显示任何选中项,并且搜索能命中该行。5)在存有quit时重启 并关闭窗口:即使托盘图标存在,应用也会退出。6)在任意设置下最小化,验证 窗口隐藏进托盘(D216),并且点击托盘可以把它带回来。7)向pi-desktop/window/closeBehavior/set传入未知值和"ask",验证它们失败 关闭,并验证该通道在 macOS 上直接被拒绝。 - 预期:每个未设置状态下首次关闭恰好显示一次主题化弹窗;Escape、点击遮罩或取消都不会持久化选择。renderer 不可用时限时回退到原生弹窗,原生弹窗也失败时不会卡住关闭。 托盘模式让应用带着本地化的提示与菜单继续存活且不丢数据;切换到退出应用 不会移除托盘图标,因为最小化到托盘仍然把它当作回到隐藏窗口的唯一途径。 偏好在重启后仍然有效,窗口控制的关闭按钮和关闭快捷键都遵循它,存有
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(发布资格)
- 状态:
lifecycle-confirmation-dialog-user-path.test.mjs、lifecycle-dialog-broker.test.mjs与lifecycle-close-behavior-runtime.test.mjs覆盖主题化弹窗、兜底和主进程关闭/退出选择; 完整持久化关闭偏好旅程仍为草稿。
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、ko和pt-BR稳定历史记录;可以切换产品语言。紧凑更新路径使用打包或固定更新器状态,并提供已收录的availableVersion。 - 步骤:1) 没有可用更新时,打开“设置”→“信息”→“发行说明”。2) 检查完整历史、当前版本标记、滚动和关闭方式(关闭按钮、Escape、点击遮罩层)。3) 触发或等待更新检查进入手动
available、应用内downloading或downloaded状态;检查浮动横幅和设置中的“更新”行,然后重新打开发行说明。4) 将 UI 语言依次切换为 zh-CN、zh-TW、ko 和 pt-BR 并复核,无需重新检查更新。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 场景草稿长消息回归:将超过 65,536 字符的日志作为正文发送,重新打开已保存的聊天, 编辑并添加一句说明后重发。编辑框和发出的请求必须保留日志末尾,不得带入展示截断提示。 完整历史读取失败时显示错误,不打开截断编辑框;读取期间切换聊天(包括保留相同消息数组的 A→B→A 切换)后不得打开过期编辑框或写入过期历史。返回后重新点击编辑仍可加载完整正文并重发。 自动化覆盖还验证附件和原始斜杠命令得到保留。
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)在浅色主题中,打开仍存在的本机选择(计划任务表单),并确认设置里的外观、行为和技能导入模式选择器都是应用内菜单而不是平台
<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 交叉淡入;startup-animation.test.mjs与packages/shared/src/startup-animation.test.ts覆盖 D679 动画的边界、状态机、选片、界面与启动界面优先级;pnpm test:e2e:startup-animation用生成的视频启动真实应用,断言pi-startup:解码、播放推进、渐显,以及不可播放文件仍会交出主界面;全窗口计时仍为草稿) - 优先级: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)。 7. 把一个可播放的短视频放进<dataDir>/startup/后重新启动(D679)。 8. 换成不可播放的文件(例如改名的文本文件)后重新启动。 9. 保留该动画,分别在prefers-reduced-motion: reduce下启动、重载渲染器、以及关闭窗口后从托盘或 Dock 重新打开。 - 预期:
- 准备之前:带有品牌标志、外壳名称、标语和可访问的启动状态 (
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。 <dataDir>/startup/中有视频时(D679):首个绘制帧里data-testid="startup-animation"取代启动画面,在不透明黑底上播放,无控件无声音,播完后同一层黑色继续遮住窗口直到引导完成,然后在约一秒内淡出,淡出期间主界面已可点击。启动比视频慢时,黑幕满 3 秒后出现不确定进度条。Escape直接跳到该淡出。- 不可播放的文件绝不会劫持启动:动画在 2 秒内(无首帧)或
error时结束,渐显与正常播完完全一致。 - 减少运动、渲染器重载,以及从托盘或 Dock 重新打开的窗口都不播放动画,改用品牌启动画面。设置 → 外观 → 界面 会显示选中的文件、提供打开文件夹的动作,开关改动只在下次启动生效。
- 启动被搁置超过看门狗边界时,恢复界面取代动画——绝不会被留在不透明遮罩后面。
- 没有简单的无品牌“开始...”居中文本作为唯一的启动 UI。
- Overlay/dialog 输入动作使用共享令牌;减少的运动可以保持状态变化,而无需装饰持续时间。
- 链接规格:
04-ux/07-ui-design-system.md§8、04-ux/02-i18n-english-first.md、04-ux/09-interaction-patterns.md第 1.12 节、ADR 0338、决策日志 D146 / D304 / D348 / D679 - 验收: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 提供商、项目、市场、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 整个流中的镀铬。
- 导航至插件或设置并返回,然后以减少的动作重复。
- 预期:
- 当前助手行逐步显示内容并固定关注 保持在最晚,没有明显的振荡。
- 在模拟的 120Hz 显示器上,平滑文本最多以 60Hz 更新;追上后动画帧循环停止。
- 可替换的 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 终态及耗时更新,以及使用 确定性 120Hz 动画帧验证生产平滑文本 hook。 测试不加载样式;完整提供商流式响应与 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。
- 预期:
- 每个检查点的摘要请求都携带会话自己的对话身份:在 Responses 形状的提供商 (
openai-responses、openai-codex-responses)上,出站载荷会像会话的普通 回合一样把会话 id 作为prompt_cache_key发出,因此对接 Codex 后端的网关会 接受该请求,而不是返回 400invalid_responses_request。其他线协议的提供商, 以及位于已发布厂商主机上的行,保持不变;未识别端点在 Chat Completions 上会补 上该键,理由见E2E-PROVIDER-custom-endpoint-session-identity。 - 每个
turn_end在另一个提供商请求之前都会被评估,并且永远不会 标记整体任务空闲; composer/config 控件保持阻塞状态,直到agent_end、error或仅手动的compaction_end。 - 每次成功的压缩都会在记录中添加一个分隔行, 位于检查点覆盖的最后一条消息之后,并且 恰好引发一个警告Toast。两个检查点产生两行, 顺序,并且两行都不会替换或隐藏消息。
- 检查点安装后(包括第 9 步的手动
/compact),输入框的上下文圆环立即降到 检查点估计的下一次请求大小,不用等下一轮;面板里的供应商用量仍是最后一次真实 请求的数据。下一次请求报告的用量会替换这个估计值,重启后重新打开会话也显示 同一个估计值(#2015;单元测试见apps/desktop/test/compaction-context-usage.test.mjs)。 new_context调用显示为正常工具活动行,返回 立即,并在下一个回合边界处创建检查点 而不是中途回合。
- 每个检查点的摘要请求都携带会话自己的对话身份:在 Responses 形状的提供商 (
- 在检查点后打开上下文使用检查器显示一行 压缩计数和最新摘要的令牌估计;在任何之前 检查点该线不存在。
- 在硬边界,在下一个模型之前创建持久检查点 请求。完整的可见记录没有改变,并且继续 任务保持低于模型感知的安全预算。
- 在检查点之后,下一个提供商请求不包含助手或工具 来自边界之前的消息——仅包含摘要;活动回合继续时, 另外包含最新的用户消息,该消息可能带有检查点截断标记。 已完成回合检查点的保留尾部为空,并且请求不包含没有结果的工具调用。 扩展原来的转录本行仍然显示其完整的持久结果。
- 重新启动可恢复摘要和记录的活动/已完成保留模式,并且每个更早的 压缩行仍被绘制。检查点之前的 regenerate/fork 具体记录的边界被丢弃; 以幸存消息为锚点的记录被保留/重新映射。
- 确切的提供程序溢出仅从模型中删除失败的助手 上下文;压缩/重试等待期间,界面保留同一个助手气泡并保持运行态; 压缩后只重试一次,第二次溢出不循环。第一次可恢复溢出不发终态错误消息或
error事件;只有重试/压缩失败时才以CONTEXT_TOO_LARGE或真实终态错误收口。 - 如果压缩已经用摘要或保留的用户消息替换了失败的助手,而之后发生可重试的提供程序流失败, 重试会保留当前压缩后的转录本并调用
continue();不会因为缺少失败助手而触发守卫错误(issue #1768)。 - 如果自动摘要生成失败,则持久保留尾部回退 附加检查点,运行保持活动状态,并有一个警告解释 旧模型上下文被减少;该检查点的转录行显示 「摘要生成失败 · 已保留近期上下文」,绝不显示
摘要 ≈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-PROVIDER-custom-endpoint-session-identity:自建端点收到会话 id
- 前提:两个互相独立的会话,各进行若干回合,都绑定到 API 风格为
chat_completions、base URL 指向本地抓包端点(不是已发布厂商主机)的提供商行。 另有一行位于已发布厂商主机、一行使用anthropic_messages,以及一行使用openai-responses。 - 步骤:1) 在两个会话各发一个回合,抓取两次请求的完整标头与完整请求体。 2) 逐字段比对这两个请求。3) 在第一个会话触发一次上下文压缩摘要并抓取。 4) 运行一次子智能体回合与一次带会话 id 的插件一次性调用并抓取。 5) 在位于已发布厂商主机的那一行上重复第 1 步。6) 在 API 风格为
opencode_go的行上重复第 1 步。 - 预期:
- 两次抓取的请求都携带稳定且逐会话不同的标识:
x-session-affinity与x-client-request-id,在格式为openai时还含session_id,请求体中含prompt_cache_key,取值都是该会话自己的对话 id,并在该会话的多个回合之间保持 不变。 - 自建 Responses 请求也携带
x-session-affinity,同时保留适配器原有的session_id、x-client-request-id与prompt_cache_key,因此丢弃下划线标头的反向 代理仍能路由会话。 - 压缩摘要、子智能体回合与一次性调用携带其所属会话的同一个 id,因此网关会把整个 会话固定到同一后端。
- 位于已发布厂商主机的行与 OpenCode 行的出网请求与改动前逐字节一致(OpenCode 行仍带
x-opencode-session)。 - 没有会话 id 的一次性调用不会得到
prompt_cache_key或x-session-affinity标头。
- 两次抓取的请求都携带稳定且逐会话不同的标识:
- 关联规格:
03-runtime/02-agent-runtime.md(§6.2、§6.3)、03-runtime/11-provider-model-system.md、issue #1634 - 验收:C (chat/stream) + 质量
- 里程碑:M5
- 状态:单元测试覆盖(
session-affinity.test.ts);完整网关抓包旅程草稿
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是后备名称。闲着的 后续使用更改的根内容而不是重用之前的运行时。 空的、不可读的、过大的和超出根的指令文件不会阻塞 转牌圈;全局文件与项目链各有独立的 32 KiB UTF-8 预算,过大的全局文件不会挤掉 项目条目,被截断的文件末尾带有注明来源及已加载/总字节数的提示。目标路径位于项目根之外, 或目标就是项目根本身时,文件工具会保留根链,而不是清空项目指令; 指令文件仍然只从项目根内部读取。fixture sidecar 场景验证嵌套读取应用 嵌套规则,而后续附件读取会恢复根规则且不会载入根目录外的AGENTS.md。 如果路径特定 分辨率超过其两秒期限或主机不可用,则 文件工具继续使用基础链并且不保留同级链 目录的规则。同一目录中的重复文件工具一次 提示重用一种路径解析声明;下一个提示再次解决,所以 观察到更改的指令文件。解析器使用会话绑定 项目根在运行时启动时传递,并且不发出每个文件session.getRPC。 - 链接规格:
03-runtime/02-agent-runtime.md - 接受:C (chat/stream),F(持久)
- 里程碑:M5
- 状态:解析器与运行时由
project-instructions.test.ts和runtime.test.ts自动化;fixture sidecar 场景通过pnpm test:e2e:hosted-search运行。完整 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 工件。
- 步骤:1) 检查 DMG,确认窗口里只有应用与 Applications 链接。2) 不解压应用内容, 检查 ZIP 根目录,确认既没有
PI-Desktop-macOS-opening-help.txt,也没有PI-Desktop-macOS-open.command。3) 确认 ZIP 中的应用仍可正常安装。 - 预期:DMG 使用带品牌的 720×440 背景,只包含应用与 Applications 链接;ZIP 包含 应用,但两种首次启动指引文件都不存在。签名和未签名 macOS 工件均遵循此规则;签名、 公证和更新程序行为保持不变。
- 关联规格:
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) 确认 app stapler shim 只在 app 公证提交被接受后重试xcrun stapler staple,最多六次;再确认工作流的 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-height值检查.composer-dock-docked的计算背景(应完全透明)与.thread-scroll的遮罩,覆盖两套内置主题与一套自定义主题。 - 滚动长会话,让一行正文越过 Composer 边界。
- 检查 Composer 停靠栏样式中的
.plan-approval-bar。 - 检查记录样式中的
.run-activity-error-popover.message-error。 - 在实时会话中悬停或聚焦正在重试的活动行。
- 用两个不同的
- 预期:
- 停靠区不绘制任何底衬。正文在 Composer 边界处经
.thread-scroll的遮罩 淡出,不会留在输入框下方或圆角外侧;遮罩的两个色标分别位于滚动容器底边 之上--composer-dock-height + 16px与--composer-dock-height - 2px, 因此滚到底时最后一行保持完全不透明,会话面板自己的表面(含主题铺的背景) 在停靠区后面保持可见。 - Plan/Goal 审批条使用
--ds-bg-composer加--ds-shadow-composer,而不是正文流里的--ds-tile薄洗,因此在透明停靠栏上仍可读。 - 重试 hover tooltip 把错误色混在
--ds-bg-elevated-opaque上,记录正文不会透出。 - 重试 tooltip 的高度被限制在尾部状态行上方的可用空间内,其余部分可滚动, 因此较长的 provider 消息会保留开头几行,不会被记录滚动容器或会话顶栏遮住。
- 停靠区不绘制任何底衬。正文在 Composer 边界处经
- 链接规格:
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;错误会注明实际timeoutMs预算,并建议延长预算或拆分命令。 范围内的值在 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,工作空间为 不会自动回滚。在 Windows 上,隐藏 runner 仅当宿主所在的 job 授予JOB_OBJECT_LIMIT_BREAKAWAY_OK时才请求CREATE_BREAKAWAY_FROM_JOB, 然后进入自己的 kill-on-close job;若宿主 job 未授予 breakaway,runner 改以嵌套 job 运行,而不是让工具安全失败;终止范围仍限定在该 job, 因此 Desktop 进程树不会成为终止范围。 - 链接规格:
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-265:后台 Bash 任务在回合结束后继续运行
- 前置条件:项目会话,其目录 shell 支持后台执行;测试服务可绑定的端口。
- 步骤:1) 让 agent 用
runInBackground启动长驻进程(本地 Web 服务),观察 输入框上方的运行中任务栏。2) 中止本轮,在同一会话发送第二条提示,然后重新加载 渲染进程。3) 展开该行的“输出”。4) 点击“停止”。5) 启动十个任务,每个流 输出十万行、每行一百字符,检查最终输出。6) 在宽窄窗口同时展示 Todo、Ask 和 任务列表,滚动到第十个任务,在旧读取未完成时切换日志。7) 模拟停止失败后重试。 8) 再启动两个任务,在输出读取期间退出应用。 - 预期:工具结果携带
backgroundTaskId且不等待命令结束;任务栏显示运行中的 任务与递增的耗时,并展示流式输出;进程在中止后、Bash 60 秒超时后、渲染进程重载后 仍然存活。“停止”把该行落定为“已停止”并杀掉整个进程树;退出应用后不残留孤儿进程。 十任务列表在限高区域内滚动,不挤占 Todo、Ask 和输入框;仅刷新当前展开的日志, 过期响应不能清空新预览。每个流保持 256KiB 上限并保留最终标记。停止失败可见、 无未处理的 Promise 拒绝,且可以重试。 - 关联规范:
03-runtime/03-tools-and-permissions.md(§5)、03-runtime/06-host-rpc-protocol.md(§4) - 验收标准:C、E、F
- 里程碑:M6+
- 状态:部分自动化(host-core
background_tasks::tests、渲染层契约测试apps/desktop/test/background-tasks.test.mjs;运行node scripts/e2e-background-tasks-host.mjs验证真实宿主、进程和管道压力路径,node scripts/e2e-background-tasks.mjs在隔离 Electron 中以 1280px、700px、 390px 宽度验证生产 React 组件。宿主回归测试覆盖退出前输出排空和输出锁竞争时关闭。 完整 agent 到桌面流程及约 83 分钟的十万行定速长测仍为人工场景; 自动化的大输出测试采用加速模式。)
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-output-token-limit-is-a-visible-failure:输出 token 上限必须可见地失败
- 先决条件:Agent 会话使用确定性的本地提供程序;提供程序在产生非空的助手文本后,以
stopReason: "length"或"max_tokens"结束。委派有有效报告且没有待处理的工具调用。 - 步骤:1)委派任务并让提供程序在输出 token 上限处结束。2)读取 Task 结果、生命周期 details 和委派卡片。3)用一次正常结束的提供程序响应恢复或重试同一工作。
- 预期:第一次运行以
failed结算,而不是completed,并带有SUBAGENT_OUTPUT_TRUNCATED和outputTruncated: true。父级收到明确解释,且部分报告保留在Its last output was:下面。后续以stop正常结束的回合会清除标记并以completed结算; 部分报告的运行绝不会伪装成已完成报告。 - 链接规格:
03-runtime/02-agent-runtime.md§5f、03-runtime/08-error-codes.md§3.2 - 验收:C(对话)、H(诊断)、品质
- 里程碑:M6+
- 状态:由
packages/agent-runtime/src/subagent.test.ts单元覆盖;完整桌面旅程仍需运行: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以内;第二条已结算的 链远远落在预算之内。恢复场景使用vendorKey: "deepseek",且转录中有一条 同时含有回答文本与思考内容的助手消息。 - 步骤:
- 对超出预算的那条链执行
Task.resume,完整捕获它的第一次提供商请求。 - 对落在预算之内的那条链执行
Task.resume,捕获同样的请求。 - 向恢复后的运行询问该链在最近一轮得出的结论,再询问它在第一轮得出的结论。
- 重启应用,从转录重建链索引,再次恢复那条超出预算的链。
- 在一条链里累积超过
MAX_RESUMABLE_READ_LINES的只读输出,读取下一条提示给出 的可复用清单。 - 检查捕获请求中的恢复助手历史,确认同模型思考仍位于
reasoning_content。
- 对超出预算的那条链执行
- 预期:恢复后运行的第一次请求低于硬边界。它以原始任务简报开头,并保有最近的 若干轮;最旧的工具结果优先被丢弃,而丢弃一条助手消息会连同它的工具调用一起丢弃, 因此没有孤立的工具调用会到达提供商。落在预算之内的链仍按原样整条播种。最近一轮的 结论能从播种的上下文里答出;第一轮的结论可能已经不在,此时该次运行会照实说明,而 不是凭空编造。一次恢复绝不会在它的第一次请求上以
CONTEXT_TOO_LARGE或SUBAGENT_CONTEXT_OVERFLOW失败。使用vendorKey绑定时,恢复助手历史使用与实时请求 相同的model.provider/model.id身份,思考内容仍在原生reasoning_content字段中。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 重复;以及在恢复前禁用或改出作用域。 也试一次断开后的并发调用,以及恢复握手失败的服务器。
- 连接 HTTP 服务器后将其停止,刷新 MCP 设置页;重启服务器后再次测试连接。
- 扩展 → MCP → 从 JSON 导入。粘贴
- 预期:
- 传输重启后,已激活的工具无需再次搜索即可使用;并发调用共享一次握手。 新的服务器列表仍须公布该工具。已移除的工具和未激活的服务器在不执行 调用的情况下被拒绝;未激活的服务器不会被重新连接。恢复失败返回
UNAVAILABLE,在编辑或测试连接之前不会反复握手。失败的工具执行 从不重放。 - 导入两台服务器;第三个被列为跳过“stdio 服务器 需要指挥”。局域网 HTTP 条目以
http身份登陆,其 url 完好无损,编辑器 显示未加密连接警告。 - 与其找到的工具名称以及行的字形相关的测试报告 从连接状态变为就绪状态。
- 虽然是全局的,但两个会话都会看到
mcp_<serverId>_<tool>名称。 - 缩小范围后,只有
~/work/api会话才能看到它们;总结芯片 读取“1 个项目”并为其命名。 - 第 6 步中的过时调用失败,并显示
TOOL_NOT_FOUND和“not active for 本次会议”——范围在调度时有效,而不仅仅是在目录中。 env编辑会断开连接:下一次组装或调用会重新握手,并且 该工具的行为反映了新的值。第 8 步中的重命名不会 重新连接任何东西。- 损坏的命令记录
failed并带有一条消息,不提供任何工具, 并且不会在下一次会议上重拨;压制测试 重试。 - HTTP 服务器停止后,设置页刷新显示
failed;重启后“测试连接”恢复为ready。
- 传输重启后,已激活的工具无需再次搜索即可使用;并发调用共享一次握手。 新的服务器列表仍须公布该工具。已移除的工具和未激活的服务器在不执行 调用的情况下被拒绝;未激活的服务器不会被重新连接。恢复失败返回
- 链接规格:
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 下。
- 无 DCR 的情况:如果授权服务器元数据缺少
registration_endpoint,且没有可复用的客户端,点击“授权”不会打开浏览器。错误事件包含MCP_OAUTH_DCR_UNAVAILABLE,设置页会显示本地化恢复指引,建议向服务器运营方索取客户端 ID,或改用支持 DCR 的服务器。 - 链接规格:
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-261:清除单服务器 MCP 超时覆盖
- 前提:隔离 Electron 配置及一个超时覆盖为 45 秒的已保存 MCP 服务器;不连接真实服务器或提供商。
- 步骤:打开生产 MCP 编辑器,输入超出范围的值并确认保存按钮禁用,然后清空超时字段并保存。
- 预期:无效超时无法保存。清空已有覆盖是有效操作,并序列化为
timeoutSeconds: null,使 host-core 删除覆盖并使用默认值。 - 验收:
node scripts/e2e-mcp-timeout-clear.mjs使用生产编辑器验证表单和保存序列化。apps/desktop/test/mcp-editor-timeout.test.mjs覆盖值转换;host-core MCP 测试覆盖 null 清除及默认行为。不连接真实 MCP 服务器。 - 里程碑:M6+
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 张图片,确认输入框内有 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);prompt-inline-attachments.test.mjs覆盖内联图片记录@path与重放副本仍随行,session-message-presentation.test.mjs渲染消息行顺序,packages/agent-runtime/src/runtime.test.ts断言提示内容块;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) 发送完整的引用并检查持久化的用户 消息。 - 预期:
- 每个结果持久地呈现其叶名称,并在其后以弱化样式显示条目完整的 工作区相对路径;目录叶名保留尾随
/,该 span 携带原始entry.path,并先于叶名截断,行永不溢出菜单。 - 工具提示和可访问名称保留完整的相对路径,以便 重复的叶子名称仍然可以区分。
- 对文件按 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、D660)、ADR 0024、ADR 0070 - 接受:C(对话和直播),质量
- 里程碑:M5
- 状态:单位覆盖 (
apps/desktop/test/composer-file-reference-display.test.mjs);文件模式行 布局由scripts/e2e-composer-autocomplete.mjs覆盖;完整的用户界面 旅程草稿(适用变更合入前需在具备条件的环境中运行 E2E)
E2E-COMPOSER-deep-file-mentions:@ 能找到大型工作区里的深层文件
- 先决条件:应用程序正在一个索引被截断(超过 8000 个文件和目录)的工作区中使用 Agent 会话运行,其中有一个脚本位于数千个浅层资源之下的几层目录里——例如 Unity 项目的
Assets/_Game/Scripts/Gameplay/Player/FeatureController.cs。工作区在 git 管理下和不在 git 管理下各运行一次。 - 步骤:1) 输入
@并检查脚注。2) 输入脚本名称后停顿。3) 用 Enter 接受该脚本的行。 4) 快速把查询改成几个不同的前缀。5) 在小工作区和远程会话中重复。 - 预期:
- 只输入
@时列出索引并显示截断脚注。 - 停顿后深层脚本作为一行出现,排序与其他匹配一致;这次搜索返回后脚注消失;被
.gitignore或忽略集排除的文件从不出现。 - 接受后插入与其他引用相同的文件芯片。
- 行永远不属于更早的查询。搜索进行中菜单保留本地匹配。
- 小工作区不发出搜索请求;远程会话保持使用快照并保留脚注。
- 只输入
- 链接规格:
04-ux/08-component-spec.md§11.8、03-runtime/01-ipc-protocol.md§13c、08-meta/decisions-log.md(D124、D669)、ADR 0024 - 接受:C(对话和直播),质量
- 里程碑:M5
- 状态:单元覆盖(
packages/host-runtime/src/fs-index.test.ts覆盖 git 与遍历两种 清单、忽略规则、目录以及结果上限);渲染层旅程——停顿后搜索、合并、接受、过期 结果、脚注以及完整索引路径——由apps/desktop/test/composer-deep-file-search-user-path.test.mjs在真实 Electron 渲染器中覆盖。针对大型真实目录树的完整桌面旅程为草稿(适用变更合入前需在具备 条件的环境中运行 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 文本的固定输入。
- 步骤:
- 打开设置 → 对话 → 输入与链接,确认大段文本粘贴阈值为 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.4–§3.5、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) 在 导航测试中分别从定时任务和设置进入,并测试插件前同时存在 定时任务与设置的历史项。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;另有一个带模型绑定的合成 OpenAI Codex OAuth 账户。使用合成模型,不使用真实凭据。
- 步骤:1) 新建自定义服务,检查接口格式选项。2) 编辑两种历史行并原样保存, 再明确选择 Responses 保存。3) 复制原历史行,检查当前格式及说明,等待超过 发现防抖时间后取消。4) 再次复制,选择 Anthropic Messages 后保存。5) 检查 保存请求及原行未被改变。6) 编辑合成 Codex OAuth 账户,确认原生联网搜索 可用但默认未选中,勾选并保存,检查模型绑定载荷。
- 预期:新建只提供四种通用协议;Codex 与 Pi 通过厂商账户使用,不能作为 新 API key 服务格式。历史行在未主动修改时保留协议、名称、URL 和认证。复制时 显示原账户格式但不可重新选择;必须主动选支持的格式后才能保存和发现模型, 并提供本地化说明。取消不创建行;有效副本不复用原行 ID 或凭据。对于 Codex OAuth,原生联网搜索仅对支持的 Responses wire 启用、默认关闭,且只写入该模型 的
nativeWebSearch: true。具名 OpenCode Go 与 OAuth 账户流程保持不变。 - 自动化:
pnpm test:e2e:provider-api-style在 Electron/Chromium 中渲染 实际 React 表单和VendorAccountDialog,以模拟 API 边界检查创建、更新、发现请求 及 Codex 模型绑定保存。不验证 Host 存储或真实 OAuth/模型请求。 - 链接规格:
03-runtime/11-provider-model-system.md、03-runtime/12-provider-config-schema.md、ADR 0095、ADR 0297。 - 验收:B(模型配置)、Security。
- 状态:Electron/React 场景在英文、简体中文下均通过,共八组;模拟 API 收到 四次创建、八次更新,两种语言均验证 Codex OAuth 搜索 opt-in 保存。未验证 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 分支。
E2E-CHAT-session-todo-checklist:TodoWrite 到按会话显示的 TodoDock
- 前提: 隔离的本地 Electron 配置、确定性的 Agent/Host fixture、两个 Desktop 会话,不使用真实 Provider 或付费 API。
- 步骤: 启动调用
TodoWrite的多步骤 Agent 回合,观察 Composer 上方的 TodoDock;确认右侧按钮至少为 36px 方形点击区域,箭头图标独立设为 18px,点击标题文本不会展开。点击向上箭头展开并确认箭头转向下方;切换会话并确认清单隔离。展开一个超过 dock 高度上限的清单,确认渲染全部条目、列表在 dock 内滚动而不撑高 Composer 栈;完成和取消条目,确认全部取消状态;清空清单后重载/重启 Host。发送乱序旧todos.changed事件,确认它不能覆盖新快照;再覆盖非法参数、Plan/Goal、委托和远程会话路径。 - 预期: 切换会话时收起、收起状态只占标题行高度、列表渲染全部条目并以自身为滚动容器、拒绝旧事件。展开后是纯文本清单,状态用字形标记而非带边框的方块;进行中的那一步文字带一道扫光,且任一时刻只有一处文字带扫光(收起时在标题行,展开时在对应行)。
- 链接规格:
03-runtime/03-tools-and-permissions.md、03-runtime/04-data-storage.md、03-runtime/06-host-rpc-protocol.md、04-ux/08-component-spec.md、ADR 0312。 - 验收: C / E / F / Quality / Security。
- 里程碑: M6+。
- 自动化:
pnpm test:e2e:todos从生产 Agent 的 ToolSearch/TodoWrite 路径进入,使用生产渲染器、真实 Host/SQLite 和隔离 Electron 配置验证清单旅程;仅替换外部模型流和 preload 传输,不使用真实 Provider 或用户配置。覆盖 Unicode 截断及警告重放、单一活动项归一化、全量清单渲染与 dock 内滚动、首次读取失败后不切换会话的主机恢复、已缓存快照重新同步及旧事件拒绝。运行时runtime-todos.test.ts使用确定性 Provider 验证 Agent 工具校验、超长内容归一化和续跑。 - 状态: 构建 Desktop 和 Host 后,在确切的请求候选中执行。Host-core 和渲染器定向测试是辅助检查,不能替代 Electron 用户旅程。
8. 可追溯性矩阵
| 验收 | 应用场景 |
|---|---|
| C / E / Security / Quality — Gemini work setup | E2E-LIVE-VOICE-gemini-work-schema |
| C / Quality — Continue after malformed tool output | E2E-RUNTIME-malformed-tool-call-continue-recovery |
| B / Security / Quality — Optional provider client certificate requests | E2E-PROVIDER-optional-client-certificate |
| C / E / F / Quality / Security — Session Todo checklist | E2E-CHAT-session-todo-checklist |
| C / E / Security / Quality — Live Voice 四阶段交互 | E2E-LIVE-VOICE-four-stage-ui |
| 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 — Portable configuration sync | E2E-CONFIG-SYNC-s3-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 |
| E / Quality — 浏览器截图超时恢复 | E2E-BROWSER-capture-resize |
| A / C / Quality — 侧栏材质与设置返回 | E2E-LAYOUT-sidebar-settings |
| C / Quality — 目的页加载与焦点 | E2E-087b |
| A / H / Quality — 渲染器进程崩溃恢复 | E2E-RUNTIME-renderer-crash-recovery |
| C / F / Quality — 含聊天的项目文件夹安全分离并删除 | E2E-048c |
| B / F / Security — 提供商复制 | E2E-PROVIDER-copy-config-without-credentials |
| C / Quality — 自建端点收到会话 id | E2E-PROVIDER-custom-endpoint-session-identity |
| B / F / Quality — 已选模型顺序 | E2E-MODEL-selected-order-persists |
| E / F / Quality — MCP 服务器超时覆盖 | E2E-261 |
| C / D / Quality — 折叠项目后重置会话历史 | E2E-263 |
| 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-048c、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 |
| C — 对话和直播(输入框草稿) | E2E-011c、E2E-011c-1、E2E-011c-2 |
| 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-038、E2E-SETTINGS-inline-capability-imports、E2E-040、E2E-042、E2E-047、E2E-048、E2E-048c、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 / Security — Fetch redirect policy | E2E-PLUGIN-fetch-redirect-policy |
| 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-SETTINGS-inline-capability-imports、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-SETTINGS-inline-capability-imports、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-048c、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-UPDATE-preference-and-once-only-reminder、E2E-250、E2E-PLUGIN-imported-pi-package-skills、E2E-SUBAGENT-resume-a-settled-delegation |
| 品质(侧边栏菜单定位) | E2E-021c |
| 品质(项目排序) | E2E-253 |
| 品质(工作面板标签关闭按钮可见性) | E2E-WORK-PANEL-active-tab-close-visibility |
| Quality (turn changed-files disclosure) | E2E-CHAT-turn-changed-files-summary |
| C / E / G / Quality — Pi kernel upgrade | E2E-PI-130-kernel-upgrade |
| C / E / G / Quality — Pi kernel upgrade | E2E-PI-140-kernel-upgrade |
| C — 对话和直播(输入法斜杠别名) | E2E-255 |
| E——工具和权限(Skill 常驻) | E2E-254 |
| 品质(Skill 常驻与输入法斜杠别名) | E2E-254、E2E-255 |
| C / E / F / 品质——内置技能启用状态 | E2E-SKILL-builtin-activation |
| 品质——市场目录查询重试恢复 | E2E-MARKET-CATALOG-RETRY |
| 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 |
| C / 质量 / 安全(对话 MP4 附件) | E2E-CHAT-mp4-attachment-opens-in-system-player |
| 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 |
| C / 品质(Composer 深层文件引用) | E2E-COMPOSER-deep-file-mentions |
| 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 |
| 品质(Windows 更新缓存) | E2E-260 |
| 安全性(导入扩展依赖) | 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 |
| D(界面表面)/ 品质 — 远程主机添加与配对表单折叠 | E2E-REMOTE-HOST-settings-compact-inventory |
| E — 工具与权限(能力跨级别迁移) | E2E-CAPABILITY-move-across-levels |
| F — 持久化(能力跨级别迁移) | E2E-CAPABILITY-move-across-levels |
| 品质(能力跨级别迁移) | E2E-CAPABILITY-move-across-levels |
| 里程碑 | 应用场景 |
|---|---|
| MVP 后实验性交互维护 | E2E-LIVE-VOICE-four-stage-ui |
| 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-042、E2E-087、E2E-088、E2E-088b、E2E-089、E2E-090、E2E-144、E2E-005J、E2E-201 |
| M2(侧边栏菜单定位) | E2E-021c |
| 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-048c、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-UPDATE-preference-and-once-only-reminder、E2E-212、E2E-213、E2E-214、E2E-215、E2E-216、E2E-217、E2E-257、E2E-SETTINGS-inline-capability-imports、E2E-166、E2E-SUBAGENT-resume-a-settled-delegation |
| M6+(Session Orchestrator) | E2E-PLUGIN-session-orchestrator-real-workers |
| M6+(已选模型顺序) | E2E-MODEL-selected-order-persists |
| M6+(MCP 服务器超时覆盖) | E2E-261 |
| M6+(目的页加载与焦点) | E2E-087b |
| M6+(会话列表响应性) | E2E-SESSION-list-refresh-keeps-desktop-responsive |
| M6+(Windows 更新缓存) | E2E-260 |
| 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 |
| M5(Composer 深层文件引用) | E2E-COMPOSER-deep-file-mentions |
| M5(对话 MP4 附件) | E2E-CHAT-mp4-attachment-opens-in-system-player |
| 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 |
基线后本地自动化(MCP pi_session_get 超大 compaction) | E2E-MCP-session-get-projects-large-compaction |
| 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、E2E-REMOTE-HOST-settings-compact-inventory |
| 受信任扩展(R7 v1) | E2E-DIALOG-long-text-boundaries、E2E-241、E2E-242、E2E-HOOKS-cancel-and-dispose、E2E-243、E2E-TRUSTED-EXTENSION-temporary-session-cwd-is-scratch、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-system-update-during-tool-call-restore |
| 品质(工具执行期间的系统更新恢复) | E2E-RUNTIME-system-update-during-tool-call-restore |
| C — 对话与流式(循环上下文归属) | E2E-RUNTIME-loop-context-ownership |
| 品质(循环上下文归属) | E2E-RUNTIME-loop-context-ownership |
| C / F / 质量 — 转录非法 UTF-8 恢复 | E2E-TRANSCRIPT-invalid-utf8-window-remains-readable |
| G — 插件宿主生命周期(崩溃上报) | E2E-PLUGIN-crash-report-names-the-exit-code |
| 品质(崩溃上报) | E2E-PLUGIN-crash-report-names-the-exit-code |
| C / E / F / 品质 —— 后台 Bash 任务在回合结束后继续运行 | E2E-265 |
| 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 标准;他们的黄金来源是捕获套件。
发布工件路径由 E2E-192、E2E-192a、E2E-196a、E2E-196b、E2E-196c 和 E2E-200 覆盖 (质量,M6+)。
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 会话标题:首条提示兜底 + 插件标题
- 创建一个新任务并发送第一个提示,例如“同步代码”。
- 发送后侧栏与顶栏立即显示折叠空白、截断为 48 字符的提示兜底标题,且该标题 仍是可被替换的自动标题。启用并授权 Session Titles 插件后,完成首轮可由插件 按配置的提示词、模型和思考级别生成标题并替换该兜底标题。
- 未安装插件时,会话保持提示兜底标题;再发送第二条提示也不会改写它。
- 手动重命名后再完成一轮,插件不得覆盖用户设置的标题。
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 窗口,直到宽度 < 800 或高度 < 560。
- 预计外壳会重新声明类似 Codex 的足迹(~1200×800,最小 800×560,按显示器工作区裁剪)并在仍然折叠的情况下继续恢复(不仅在发射后的前 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
- 打开设置(页脚配置文件 → 设置)。
- 预计全页设置(无应用程序 sidebar/nav)。左导轨有返回应用、搜索,以及外观、对话、快捷键、语音、行为、模型、指令、技能、MCP、子智能体、项目、系统、云同步、远程主机、信息, 按该顺序排列(云同步和远程主机在打包版均显示实验性标签);内容窗格显示所选设置页或 项目存档。
- 返回应用程序外壳并期望插件保持独立 侧边栏页脚目的地。
- 将空的 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;搜索药丸位于导轨顶部;返回应用程序 固定在导轨底部,并与主外壳侧边栏底部图标行垂直居中于同一条线;选中的「外观」 行带强调色底片与强调色字形。 - 导轨顺序为外观、对话、快捷键、语音、行为、模型、指令、技能、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,「外观」为唯一选中项,并显示返回和搜索。 - 主题选择器可正常使用,且没有无效开关或打开目标行。
- “外观”页显示界面卡片,“行为”页显示会话默认、执行与电脑操作卡,“对话”页显示「显示」与「输入与链接」,“系统”页显示网络、存储、电源(Windows/Linux 另有关闭行为)。核心侧栏依次包含外观、对话、快捷键、语音、行为、模型、指令、技能、MCP、子智能体、项目、系统、云同步、远程主机、信息;云同步与远程主机在所有构建中都显示实验性标签,语音在所有构建中都可见;插件扩展目的地位于核心分组之后。侧栏不再有“导入”入口。
- 在 800、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 选择“删除项目”,确认在任何内容移除前会打开确认对话框。点击 X 并确认 A 仍存在,再打开对话框并明确确认删除。然后从侧边栏项目菜单对活动项目 B 重复:选择删除、取消并确认 B 仍存在,再重新打开对话框并确认删除。随后尝试删除 C、宿主已不再知晓的路径,以及仍有任务运行的第四个项目 D。
- 预期:两个界面的空闲项目在选择“删除项目”后都会立即打开确认对话框。点击 X、“取消”、按 Escape 或点击遮罩层只会关闭对话框,不会移除项目、会话或转录本。对话框会说明项目、所属会话及转录本将被永久移除,并说明磁盘文件夹不会被删除。明确确认后,持久项目行、所属会话、转录本、scratch、review 文件及项目记忆都会移除。项目会立即从设置 → 项目存档和侧边栏消失,重新加载后也不会重新出现:没有保留的选项卡、最近项目条目、会话推导行或残留的 pin/archive/order 偏好。其他项目的会话和转录本不受影响。若删除的是活动项目,工作区会回退到另一个已打开项目或 Temporary,下次启动也不会重新打开该路径。即使项目文件夹已被移动或删除,仍可移除项目。删除 C 会被拒绝并显示提示,项目组保持不变;宿主已无持久行的路径仍会从项目存档和侧边栏移除,不会报缺少项目错误。删除仍有运行任务的 D 时会打开同一个确认对话框并列出运行中的会话,确认按钮会明确表示停止并删除;取消会保留项目和任务,确认则先停止对应轮次再删除 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
- 前提条件:一个项目包含一个空闲会话和一个仍在运行中的会话,两者都可从侧边栏会话菜单访问。
- 步骤:打开空闲会话菜单并按一次“删除”,确认该项进入武装状态;按 Escape 或等待武装超时后,确认会话仍存在。重新打开菜单,连续点击两次“删除”,确认会话被移除。待运行中的会话结束后,对它重复武装、取消和确认流程。
- 预期:第一次点击不会移除内容,并把标签改为
nav.deleteTaskConfirm(“Delete?” / “确认删除?”),且带data-armed="true";菜单保持打开,点击外部、按 Escape 或等待超时都会解除武装且不移除内容。只有第二次点击才会删除会话及其转录本。项目删除则必须通过明确的确认对话框(见 E2E-PROJECT-delete-removes-project-and-owned-sessions)。 - 链接规格:
04-ux/09-interaction-patterns.md§1.6、D421、D431 - 验收:品质
- 里程碑: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 在进行过程中显示橙色圆点,先在 3.2 秒内呼吸两次,再保持常亮;绿色勾号 完成,失败时出现红色圆圈警报。开始新回合清除 A 较早的终止标记;中止不会留下已完成或失败的标记。
预计选定的空闲 B 将显示静态重音蓝色轮廓环和活动环 行背景。如果选择的 B 开始工作,其橙色的进行中点将显示 优先,直到回合结束;其最新的最终结果仍然隐藏 选择时位于所选环后面。
每个指标都公开本地化的“进行中”/“已选择”/“已完成”/“失败” 通过其可访问的名称和工具提示来显示文本。减少运动使橙色 点静态而不改变其颜色或含义。行高、标题截断、 图钉图标、悬停操作和对焦环在两个主题中保持稳定。
打开带有已完成或失败标记的对话并期望该终端 当其持久任务通知被读取时,标记为立即清除。 刷新通知并重新启动应用程序;认可的商标不得 返回。标记为从收件箱中读取的终端通知同样会产生 无侧边栏端子标记。
对悬浮卡中的关联运行会话圆点重复检查。3.2 秒后,两处运行标记均无活动动画; 对其余内容空闲的原生 macOS 窗口录制时,标记不得持续产生绘制帧。 开始新一轮任务并重新打开悬浮卡,有限动画可以再次播放。 GPU 测量须区分应用提交帧与整机负载。
对三处空闲可挂载的指示分别用一个空闲窗口重复:让一个会话停留在工具 审批请求上、开启计划模式、显示后端告警横幅。3.2 秒后,权限圆点、计划 模式芯片和告警圆点均无活动动画,对其余内容空闲的原生 macOS 窗口录制时 不得出现来自它们的持续绘制帧。启用减少动态效果时,三者自首帧起即为静态。
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 模式选择器具有稳定的宽度
- 以英语和中文打开空的主页和停靠的线程。
- 打开 Composer 模式芯片,在 Agent/Plan/Goal 之间多次切换。
- 期望芯片以带选中标记的菜单项列出三个模式、只显示当前模式,并为最长内置标签 (英文“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 批准使用的停靠位置相同。请求没有 倒计时。过长的问题正文在卡片内滚动(
min(208px, 24dvh),并有24vh回退),可通过键盘聚焦且显示焦点环;标题栏上的 Decline、Skip 和 Next/Submit 留在该滚动区域之外。切换问题后正文从顶部开始。工具输出排序为question:answer,使用、多个答案之间和问题之间的\n---\n之间,并保持question:用于跳过的问题。拒绝所有会产生空的 每个问题占位符,但仍然完成工具调用。右侧工作面板打开时,问题 Toast 在 主内容安全区内水平居中,不与面板标签栏、浏览器导航控件或地址栏相交;安全区 缩放或同宽平移时 Toast 同步跟随。链接规格:
03-runtime/17-asktool-questions.md,04-ux/11-asktool-question-card.md、ADR 0077验收:E(交互式工具输出)、C(内联卡)
里程碑:M5
自动化覆盖:
pnpm test:e2e:asktool-card在 Electron 中挂载生产组件,覆盖长问题滚动、键盘可达性、标题栏动作可见性和点击 Next 后的滚动重置。pnpm test:e2e:question-notification覆盖安全区缩放与水平平移、Toast 创建、会话/项目导航、答案提交和原生键盘输入。状态:部分自动化(组件路径、长问题行为和通知安全区已覆盖;完整应用桌面旅程仍待完成)
请求切换回归:在聊天 B 留下一个单题请求。在聊天 A 将双题请求推进到第二题, 再切换到 B。B 应显示自己的第一题、空白答案和可用输入框,不应出现应用错误页。 在同一会话排队两个请求,提交第一个后为第二个选择不同答案,确认两次提交分别包含 自己的请求 ID 和所选答案。
E2E-124:窗口控件最小化到任务栏并关闭到所选界面
- 先决条件:在 macOS、Windows 和 Linux 上构建桌面应用程序;英语 和 zh-CN 语言环境可用;正常的主窗口打开。
- 步骤:1)在 Windows 上最大化主窗口,然后单击其任务栏按钮;确认窗口最小化, PI-Desktop 的任务栏项仍然存在。再次单击同一任务栏按钮;还原过渡结束后,确认 窗口仍在最大化状态、边界符合显示器工作区,且任务栏仍可见。用其他应用覆盖窗口, 再单击 PI-Desktop 的任务栏项,确认窗口回到前台且没有隐藏到托盘。2)在 macOS 上 单击交通灯最小化控件,确认窗口隐藏到托盘。3)在 Windows/Linux 上使用渲染器最小化控件, 确认原生任务栏项仍然存在;再次单击后确认同一窗口还原并聚焦。选择“关闭到托盘”并关闭 Windows/Linux 窗口,确认任务栏项消失且可从托盘恢复;选择“退出”并重复,确认进程退出。 4)打开托盘菜单并选择“显示”,再重复“退出”。5)在 zh-CN 中重复,并在窗口隐藏时 调用 macOS 应用程序激活。
- 预期:Windows 原生任务栏切换和 Windows/Linux 渲染器最小化控件保留任务栏项, 并通过原生最小化/还原完成往返。还原最大化的无边框 Windows 窗口后,窗口仍符合显示器 工作区,任务栏保持可见。单击被覆盖窗口的任务栏项会将窗口带到前台。Windows/Linux 上 只有“关闭到托盘”会隐藏窗口;“退出”会结束进程。macOS 菜单栏图标是清晰的透明单色 PI 标记,不带圆角应用磁贴,原生最小化仍隐藏到托盘。“显示”、托盘图标点击/双击及应用激活 会恢复现有窗口;本地化菜单包含“显示 PI-Desktop”和“退出 PI-Desktop”。退出按正常顺序关闭, 不留下孤立的主机、sidecar 或托盘进程。
- 链接规格:
03-runtime/01-ipc-protocol.md、03-runtime/07-process-model.md、04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、08-meta/decisions-log.md(D216、D230、D252、D256)、ADR 0078、ADR 0090、ADR 0117、ADR 0123 - 验收: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 图标。窗口放大/缩小/重置保持独立。停靠的插件工作面板视图(自带的「浏览器」和「文件浏览器」页)不导入应用令牌,而是把该倍率乘以窗口缩放作为自身页面缩放,因此在任何设置下都与应用保持比例,pi.browser的访客矩形也按同一倍率换算。独立插件窗口与挂件保持自己的基础尺寸。无效值被拒绝或钳制。无协议/存储版本升级。 - 关联规格:
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、apps/desktop/test/plugin-view-scale.test.mjs、apps/desktop/test/browser-guest-hole-scale.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 → 定义固定模型 → 会话模型;未配置或未授权的显式覆盖返回 工具错误,且不向子模型发送请求。覆盖目录为空时,错误、系统摘要与 Task 说明 指明「设置 → 模型 → 模型高级设置 → 可供 AI 自动调度 → 保存」的操作路径。 猜测键被拒绝后,省略
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 上以普通、未最大化 窗口打开。工作面板关闭时执行一次,以确定宽度打开时再执行一次。
- 步骤:
- 缓慢拖动每个可用窗口边缘和一个角落,中途短暂停顿后松开鼠标。
- 确认指针按下期间窗口持续跟随,没有跳回默认尺寸或显示器边缘。
- 工作面板打开时从外边缘调整大小,确认对话窗格回流,而面板宽度和 分隔线首选项不变。
- 在 Windows 上检查默认 12 DIP 圆角在调整大小前后保持一致;选择获授权 的
cornerRadius: 0主题,再切回内置主题,确认恢复为全局--radius-md; 输入超过 24 DIP 的半径应被拒绝。 - 在 Windows 上最小化并恢复窗口,然后确认四个原生圆角仍与所选半径一致。
- 等待调整大小稳定后关闭并重新启动应用。
- 预期:无边框外壳仍提供原生边缘和角落命中区域;Windows 默认圆角使用 全局 12 DIP
--radius-md,最大化/全屏时为矩形。最小尺寸保持 800×560(按显示器工作区裁剪),恢复看门狗不会与慢速调整大小流竞争。最后稳定的基础边界会 在重新启动后恢复;临时工作面板预留宽度不会被保存为用户的聊天窗口尺寸。 - 链接规格:
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 / ADR 0317 / D637 - 验收:A(应用外壳)、F(坚持)、质量
- 里程碑:M6+
- 状态:
test:e2e:window-controls覆盖最小化/恢复前后的圆角裁切、主题半径变化、全屏、最大化和窗口控件; Windows 原生拖动需在专用交互式桌面上运行test:e2e:window-resize-native。
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 列表来自账户令牌的GET {base}/codex/models, 所以 pin 里还没有的 id 只要响应里有就能选;请求失败才回退到 pi-ai。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-OAUTH-anthropic-copy-code:Anthropic 复制代码登录复用现有提示桥接
- 前置条件:本地 token 端点夹具只拦截 Anthropic OAuth token URL。生产 pi-ai Anthropic 流程与 Desktop
VendorOAuth使用内存 Host RPC 夹具;不使用真实账户或远程端点。 - 步骤:发起 Anthropic 厂商登录;在
select提示中选择copy_code;检查授权 URL; 为 manual-code 提示输入合成的code#state;通过夹具完成 token 交换;解析请求认证并检查 Host 中存储的凭据。 - 预期:选择项保留
browser和copy_code;授权 URL 使用https://platform.claude.com/oauth/code/callback;token 交换成功;refresh 凭据保存在 provider 作用域的 Host OAuth 引用中,运行时只获得短期 access token;不创建 API-key secret。 - 链接规格:
03-runtime/14-secrets-storage.md§10;07-plugins/16-trusted-extensions.md§4。 - 验收:B(厂商账户)、安全、质量。
- 状态:本地 provider-flow 集成夹具;未验证真实账户或渲染器视觉流程。
E2E-164:上下文压缩保留活动任务边界
- 先决条件:提供商夹具可以在一个会话中完成多个连续任务,在终止边界触发自动 检查点,在工具循环期间触发活动回合检查点,并且可以重启会话。桌面 transcript runner 还会将确定性的本地 provider 溢出恢复事件经过生产运行时事件处理、转录投影和 assistant-turn renderer 回放。
- 步骤:
- 在同一会话中完成任务 A 和任务 B,使用不同指令并产生可见的完成回复。
- 在已完成回合后触发检查点,然后发送任务 C,捕获下一次提供商请求的上下文。
- 在任务 D 仍有工具结果或
toolUse待处理时触发压缩,捕获下一次请求。 - 重启并重新打开会话,然后再发送一条提示。
- 在隔离的溢出夹具中,第一次 provider 响应超出上下文窗口后暂停压缩并检查渲染的助手回合, 然后让重试成功。
- 预期:已完成回合检查点的保留尾部为空;下一次请求包含其摘要和任务 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);隔离溢出恢复夹具通过pnpm test:e2e:transcript;更完整的 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/索引丢失)。队首可能带有缺失或属于另一会话的 turn id;旧版重放时 JSONL 也可能已有该消息 id 的重复行。 - 步骤:1)确认侧边栏不再列出该会话,且
session.appendMessage会因session not found失败。2)重启应用(或完成一次刷新 outbox 的主机握手)。3)在索引缺失、JSONL 已重复写入同一消息且后面还有已索引消息时,用过期 turn id 重放该条消息。4)可选:删除会话,并确认其 outbox 条目被丢弃而非复活。 - 预期:主机从 JSONL 重新插入 sessions 行并重建索引。失效 turn 关联会被省略,但消息内容保留;outbox 不会卡在队首,后续消息顺序不变。重放会原位更新现有行,不再追加副本;
last_seq和搜索索引与去重后的转录顺序一致。用户删除的会话不会被残留 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
- 状态:单元已覆盖(
sessions.rsstale-turn/replay 测试、persistence-outbox.test.mjs满载拒绝与排空测试);完整桌面旅程仍为草稿(除非用户明确要求,否则不要在本地跑 E2E)
E2E-180:已发送的文件引用保持芯片并可点击打开
- 前提条件:Agent 会话所在工作区含有嵌套源文件、HTML 文件,以及文件名带空格的文件;该项目组还有一个第二文件夹,里面有一个属于它自己的源文件。输入框也可以把操作系统文件粘贴到会话临时目录。内置的文件管理器插件已加载。
- 步骤:1)通过输入框芯片引用工作区源文件、工作区 HTML、带空格的文件名,以及粘贴的临时文件,然后发送。2)查看用户气泡。3)点击 HTML 芯片,再点击工作区源文件芯片,最后点击临时目录芯片。4)附加上项目第二个文件夹里的文件,并点击它的芯片。5)右键那条已发送的
@path芯片、助手 Markdown 里的行内代码引用、文件链接、本地图片、工具行自己的文件路径、工具结果的文件列表,以及附件缩略图,再右键一个什么都没匹配到的引用。6)在那条芯片上依次复制完整地址与相对地址,再对临时目录里的引用做同样两步。 - 预期:
- 每条已发送引用画成紧凑的叶子名芯片(图标 + 名称),而不是完整
@path。工具提示和无障碍名称保留规范路径。带引号路径和临时绝对路径也包括在内。 - 芯片加上短提示时,用户气泡按内容收缩,而不是撑到
min(82%, 600px)上限。 - 点击 HTML 芯片在工作面板浏览器打开该文件。
- 点击工作区源文件芯片后,该文件在文件管理器工作面板视图中打开;芯片点击不再打开宿主的
file:选项卡,也不再交给系统默认应用。 - 点击临时目录芯片时,文件在宿主的
file:选项卡中按其绝对路径打开——它位于文件管理器项目根之外。 - 点击项目第二个文件夹里那个文件的芯片时,该文件在文件管理器视图中打开:补全会搜索整个项目组、主文件夹优先,同级文件夹里的文件用绝对路径寻址,因为相对路径永远指主文件夹(ADR 0263)。
- 右键文件引用会打开渲染器自己的菜单:在文件夹中显示该文件,并复制它的完整地址与相对地址;助手 Markdown 里的行内代码引用、文件链接与本地图片、工具行自己的文件路径、工具结果的文件列表或匹配列表中的路径、图片附件芯片都提供这几项,而什么都没匹配到的引用会自己报告出来,而不是去显示别处同名的那份文件。复制写出的就是它说的地址:完整地址即绝对路径,相对地址即项目内相对写法;临时目录或附件文件没有相对地址,会直接说明,而不是把绝对路径当成相对地址写出去。
- 持久化用户消息仍包含给模型用的规范
@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-CHAT-mp4-attachment-opens-in-system-player
- 前提条件:对话中有一个粘贴到会话临时目录的 MP4,以及两个没有后缀、按内容哈希存储的 MP4 附件;后两者分别大于 512 KiB 和小于该上限,三者均保留
video/mp4元数据。 - 步骤:点击每个对话附件,再在宿主文件选项卡选择“用系统默认应用打开”。对工作区内的
.mp4文件重复操作。 - 预期:大附件提示体积超过内嵌预览上限,小附件提示二进制内容;二者均有系统打开操作。宿主校验真实路径包含范围,并为无后缀 blob 提供指向原始字节的
.mp4别名。允许范围外的文件和符号链接逃逸均被拒绝;系统打开失败会提示用户。 - 链接规格:
03-runtime/01-ipc-protocol.md§ fs、04-ux/09-interaction-patterns.md§8a.2 - 验收:C(对话和直播)、质量、安全
- 里程碑:M5
- 状态:Electron 隔离测试已覆盖粘贴到临时目录的 MP4 及两种大小的 blob(
test:e2e:composer-paste);安装版与系统播放器的完整旅程仍待验收。
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中mode为system,且 URL 与绕过列表仍保留。再选回“自定义”,确认两个值恢复到输入框,且mode为custom。 - 对正在监听的代理点测试,确认已连接;对关闭的端口点测试,确认失败且不改已保存 URL。对需要认证的 HTTP / SOCKS5 代理填入
user:pass@后再测,确认是已连接而不是net::ERR_NO_SUPPORTED_PROXIES(issue #490)。 - 保存自定义代理后,通过已配置供应商发送一条简短提示。确认供应商请求和响应经过代理;即使 SOCKS5 代理将完整 bind 响应合并在一个 TCP 数据块中,请求仍能完成。再确认扩展市场刷新和 models.dev 刷新走代理;绕过列表中的回环地址不走代理。
- 切到直连再切回系统。确认无需重启应用即可生效。再选回自定义,确认第 3 步的 URL 与绕过列表原样回来(系统/直连期间它们保留在
AppSettings.networkProxy里)。
- 预期:自定义覆盖模型请求、市场、更新、插件
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) 确认设置 → 关于 提供发布页而不是“重启以更新”。8) 退出并再次启动解压后的可执行文件。 - 预期:两个 Windows 工件都无空格并已上传。
latest.yml只指向 NSIS 安装程序。 ZIP 解压后的应用无需安装向导或管理员提示即可启动,保持正常的 PI-Desktop 任务栏 标识和图标,使用现有应用数据目录,并默认选择更新偏好manual、报告有效更新模式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-UPDATE-preference-and-once-only-reminder:更新偏好与单次提醒
- 前提条件:有隔离的打包应用配置(支持自动安装的 Windows NSIS 安装版和 Windows 便携 ZIP),并可在不运行生产安装程序的情况下让更新发现路径在多次检查中 返回同一个稳定版。
- 步骤:1) 在已安装包中打开设置 → 关于,确认默认选择自动。2) 选择手动,关闭并重新 打开设置,再重启应用,确认手动偏好仍保留。3) 让检查发现一个稳定版;确认没有自动下载 或安装,只显示一次提醒。关闭提醒、离开再返回,重复检查后重启并再次检查。4) 确认同一 版本不会再次提醒,但设置行仍显示版本并可打开发布页。5) 选择自动,确认受支持平台恢复 现有应用内下载/安装行为。6) 启动便携 ZIP,确认默认手动;明确选择自动前先查看风险警告。
- 预期:偏好按安装实例持久化。手动模式只检查,并保存最近提醒版本,避免重复检查和 重启后重复提示;关于页中的状态与发布页入口始终可用。自动模式在支持的平台维持现有行为。 ZIP/便携包默认手动,只有经过带警告的明确选择才可改为自动。
- 链接规格:
03-runtime/07-process-model.md、04-ux/09-interaction-patterns.md、 ADR 0022 / D628 - 验收:质量(设置交互与更新安全)
- 里程碑:M6+
- 状态:
pnpm test:e2e:settings-scroll覆盖设置选择与持久化;update-preference.test.mjs覆盖模式/提醒策略。打包 Windows 安装器旅程仍需运行器验证。
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) 确认显示主题化应用内弹窗、标题与正文分开且初始焦点为“取消”;弹窗打开时再次触发退出,确认仍只等待同一个决定,既不重复弹窗也不绕过确认。分别按 Escape、点击遮罩取消,确认窗口、托盘、host-core 和 sidecar 仍在。3) 再次操作并确认退出,验证有序关机。4) Windows/Linux 在关闭行为未设置时关窗,确认 D230 主题弹窗提供取消 / 关闭到托盘 / 退出;取消保持未设置,选择退出不会二次询问。5) 以
PI_DESKTOP_BOOT_PROBE=1(及 supervision/capture 等价项)启动,确认app.quit()不弹窗。6) 在打包的 Windows NSIS 或 Linux AppImage 安装上选择“重启以更新”,确认退出警告不出现且安装器完成升级和重启。 - 预期:显式退出可取消;待确认期间的重复退出不会重复弹窗或绕过确认,取消后后续退出会再次询问。托盘退出即使窗口隐藏也能呈现弹窗。已在 D230 选择退出的关窗路径不会再次确认。renderer 不可用、崩溃或超时无响应时限时回退到原生弹窗;原生弹窗也不可用时不得阻塞退出。自动化探针和应用内更新重启均跳过警告,后者直接进入有序关机。
- 链接规格:
04-ux/08-component-spec.md、04-ux/09-interaction-patterns.md、08-meta/decisions-log.md(D216、D230、D363)、ADR 0022、ADR 0090 - 验收:A、质量
- 里程碑:M5
- 状态:
lifecycle-dialog-broker.test.mjs、lifecycle-confirmation-dialog-user-path.test.mjs、lifecycle-close-behavior-runtime.test.mjs、close-behavior-tray.test.mjs和shutdown-regenerate-persistence.test.mjs覆盖 broker、renderer 用户旅程、主进程选择及待处理退出;完整应用关机和打包更新旅程仍为草稿,生命周期集成变更时需在可用发布环境运行。
E2E-209:从本地智能体存储导入模型配置
- 前提条件:至少有一个受支持的本地配置,来源包括
~/.claude/settings.json、~/.codex/config.toml的[model_providers.*]、~/.config/opencode/opencode.json、~/.pi/agent/models.json或~/.cc-switch/cc-switch.db;其中包含同一端点、不同 API key 的两个配置档案,并可选包含一个仅 OAuth 的厂商。PI-Desktop 中可以已有等价提供商。 - 步骤:1) 打开设置 → 模型并展开“从其他工具导入”,确认扫描前工作台为空闲状态。2) 扫描模型配置,确认来源组默认展开、候选行显示名称/模型数/主机/API key 或 No API key 徽章,且界面和扫描 IPC payload 都没有秘密值。3) 导入选中的提供商,确认同端点的不同密钥成为独立记录并可在 Composer 模型菜单中选择;再次导入同一选择,确认未变化的凭据被跳过。4) 若应用此前没有默认模型,确认首个导入提供商成为默认;若已有默认值,确认其不变。5) 确认仅 OAuth 的来源账户不会出现在候选列表中。
- 预期:只有显式扫描(D007)。已存储的 API key 写入宿主密钥库。只有等价提供商(归一化 URL + API 风格 + 相同凭据)会跳过;同端点的不同凭据保持独立。协议与 schema 版本不升级。
- 链接规格:
04-ux/06-settings-ia.md、04-ux/08-component-spec.md§18.5、03-runtime/01-ipc-protocol.md、03-runtime/11-provider-model-system.md、ADR 0179、D342 - 接受:B(模型配置)
- 里程碑:M4
- 状态:单元覆盖(
packages/shared/src/model-config-import.test.ts、apps/desktop/test/model-config-import.test.mjs);完整 UI 旅程为草稿(该表面变更时需在具备条件的环境中运行)
E2E-SETTINGS-inline-capability-imports:在所属设置页导入技能和 MCP
- 前提条件:可扫描受支持的外部技能与 MCP 配置;至少有两个项目;一个来源 MCP 服务器处于禁用状态,URL 包含 userinfo 与查询凭据。
- 步骤:1) 打开设置 → 技能,选择“项目”和一个项目,再展开“扫描其他工具”。确认打开面板不会扫描;启动扫描,选择技能,选择复制或链接模式并导入。2) 确认新技能属于所选项目。切换项目后确认旧候选与选择已清除。3) 打开设置 → MCP,选择“项目”和同一项目,展开“扫描其他工具”,扫描并导入已禁用的 MCP 候选。4) 确认服务器属于该项目、仍为禁用状态,候选行只显示 URL 主机,不显示 userinfo、路径或查询参数。5) 改变项目作用域后确认旧 MCP 候选与选择已清除。
- 预期:技能和 MCP 扫描仅在用户明确启动后执行。项目扫描和写入携带所选项目路径;作用域变化会清除旧候选。MCP 禁用状态保持不变,候选元数据不会渲染 URL 凭据。
- 链接规格:
04-ux/06-settings-ia.md、04-ux/08-component-spec.md§18.3 / §18.6 / §18.7、ADR 0319 / D645 - 验收:F(持久化)、Security、Quality
- 里程碑:M6+
- 状态:源契约与作用域 IPC 测试覆盖(
settings-import-page.test.mjs、agent-capability-settings.test.mjs、agent-import-ipc.test.mjs、import-format.test.mjs)。隔离 Electron 用例pnpm test:e2e:settings-inline-import覆盖项目级 MCP 扫描、选择、导入、可见结果和作用域载荷。
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 旅程已记录,仍按策略延后
E2E-MCP-session-get-projects-large-compaction:超大 compaction 记录不再让 pi_session_get 整包截断
- 前提条件:使用
PI_DESKTOP_MCP_CONTROL=1启动 PI-Desktop。存在一个持久会话,其session.compaction记录(summary/retainedTail/details.modifiedFiles)单独 序列化即超过 512 KiB 的 MCP 结果上限。 - 步骤:1)读取
mcp-control.json,用其 URL 和 bearer token 完成 MCP 握手。2)对该会话 调用pi_session_get,messageLimit/contentLimit/messageBefore取任意值。3)检查structuredContent。4)对一个普通小会话重复。 - 预期:答复不是
{truncated: true, reason: "MCP_RESULT_LIMIT", preview}信封;session.messages带回请求的转写页;session.compaction保留createdAt与details.generation,而summary、retainedTail、details.modifiedFiles不存在。 小会话的答复不变。 - 链接规格:
03-runtime/01-ipc-protocol.md§13d - 验收:C(会话)、质量
- 里程碑:M6+
- 状态:
apps/desktop/test/mcp-control.test.mjs中的本地 MCP Server 合约测试会运行带认证的 JSON-RPCtools/call,分别验证超限和未超限的pi_session_get答复。完整 Electron 到 Host 旅程仍属于发布验收。
受信任扩展场景(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-TRUSTED-EXTENSION-temporary-session-cwd-is-scratch:临时会话里的扩展在本会话 scratch 中工作
- 前置条件:一个已启用的夹具扩展,注册命令
where,报告ctx.cwd、ctx.sessionManager.getCwd(),以及不带cwd选项用pi.exec启动的子进程的 工作目录。 - 步骤:1)开一个临时会话(无项目),在任何工具调用之前运行
/where。 2)在项目会话中运行/where。 - 预期:临时会话中三个值都是该会话的
scratch/<sessionId>目录,该目录存在且 子进程在其中启动;没有一个是 sidecar 的进程目录。项目会话中三个值都是项目根, 且不会为扩展创建 scratch 目录。 - 链接规格:
07-plugins/16-trusted-extensions.md§7;D114 - 验收:A(应用控制)、质量
- 里程碑:MVP 后(R7 v1)
- 状态:单元测试覆盖(
packages/agent-runtime/src/extensions/runtime-lifecycle.test.ts);Electron 流程已记录
E2E-244:不支持的 API、加载错误与处理器超时降级为诊断
- 前置条件:四个已启用的夹具扩展:一个在顶层导入
@earendil-works/pi-tui并 调用ui.setWidget;一个从@earendil-works/pi-coding-agent导入不支持的命名导出; 一个模块在加载时抛出;一个context处理器永不返回。 - 步骤:1)开始一个回合。2)打开每个条目的诊断抽屉。3)确认不支持的导出仍为 undefined。4)等待超过 30 秒处理器限制。5)禁用抛出的扩展并开始另一个回合。
- 预期:pi-tui 导入成功,
setWidget返回惰性dispose,每个成员记录一条诊断; 不支持的 coding-agent 导出保持不可用并记录unsupported_api;抛出的扩展显示error及消息和堆栈,composer 显示一行提示,其余扩展仍加载; 停滞的处理器在 30 秒后被放弃并记诊断,回合以未修改的上下文完成;禁用后提示在 下一回合边界消失,且没有运行中的回合被打断。 - 链接规格:
07-plugins/16-trusted-extensions.md§4.2、§4.4、§5、§6 - 验收:质量
- 里程碑:MVP 后(R7 v1)
- 状态:部分自动化(
pnpm test:e2e:trusted-extensions);runner.test.ts通过真实 Jiti loader 覆盖缺失的静态命名导出;该导入对应的诊断抽屉渲染、停滞处理器超时和边界禁用旅程仍需额外验证
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 依赖is-number@7.0.0和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 会话已完成至少一个报告了令牌用量的回合。可打开设置 → 对话 → 显示。
- 步骤:
- 确认输入框工具栏上下文环显示剩余容量(环近乎满圆,百分比 ≈ 剩余 %,tooltip 与 aria-label 使用剩余词汇)。
- 打开设置 → 对话 → 显示,将上下文用量显示分段控件从「剩余」切到「已用」。
- 回到聊天,检查上下文环:圆弧按
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 并在输入框发送
/skill:<skill-id>,检查随后的回合以及工具收到的原始 Skill ID。 - 把会话切换到 Plan 模式,再次检查工具列表。
- 禁用或移除全部 Skill,再发起一个 Agent 回合。
- 预期:只要技能目录非空,
Skill就随第一个请求下发,且绝不出现在# On-demand tools中,因此匹配任务与/skill:<skill-id>调用都能直接加载正文,不再多一次发现往返。未加前缀的同名命令或模板保持原行为,不作为 Skill 别名。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-SKILL-builtin-activation:在设置中管理内置技能
- 前提条件:使用隔离的桌面配置文件和普通项目工作区。确保另有一项已启用的用户或插件 Skill,使 imagegen 关闭后
Skill工具仍可用。 - 步骤:
- 打开「设置 → Agent → Skills」,检查「内置技能」分组,确认
pi-desktop/imagegen和pi-desktop/plugin-development默认开启。 - 关闭
pi-desktop/imagegen,在普通工作区打开新会话,检查技能目录和 Composer 的/菜单。 - 使用旧的 imagegen id 调用本地
Skill工具。 - 重新开启 imagegen 并打开另一个会话。重启应用后检查设置和新会话目录。
- 打开「设置 → Agent → Skills」,检查「内置技能」分组,确认
- 预期:关闭后的 imagegen 不会出现在提示词技能目录和
skill:pi-desktop/imagegen斜杠条目中。使用旧 id 直接加载时会提示该内置技能已关闭。重新开启后,新会话可再次看到两项入口;设置在重启后仍保留。plugin-development 在设置中始终可见,但只在插件工作区进入会话目录。关联的 Agent 工具保持独立可用。 - 链接规格:
07-plugins/01-plugin-system.md(§12.3)、03-runtime/01-ipc-protocol.md(§12b)、03-runtime/02-agent-runtime.md(§7.0.0)、04-ux/04-builtin-commands.md(§8) - 验收:C(对话和直播)、E(工具和权限)、F(持久化)、品质
- 覆盖范围:
settings-inline-import-user-path.test.mjs在隔离 Electron 中验证设置开关、持久化 IPC 请求和页面关闭状态;image-generation.test.mjs、composer-mcp.test.mjs及 host-core 技能/RPC 测试覆盖目录过滤、旧 id 加载拒绝、持久化和激活协议。重启和完整新会话旅程仍由桌面验收覆盖。 - 里程碑:M6+
- 状态:桌面交互旅程草稿
E2E-255:顿号打开斜杠菜单
- 前提条件:可用中文输入法;输入框草稿为空;至少存在一条斜杠条目(内置别名、模板、插件命令或 Skill)。
- 步骤:
- 保持草稿为空,开始组字「、」,观察临时文本,再提交组合输入。
- 继续输入命令名并确认高亮行。
- 输入一段在其他字符之间包含「、」的草稿。
- 发送一段首字符为「、」且未确认任何行的草稿。
- 取消组字后再次输入
/;另模拟丢失compositionend后删除文本,确认斜杠菜单仍能打开。
- 预期:提交的「、」被就地改写为
/,普通斜杠菜单以与直接输入/相同的过滤和键盘行为打开,光标停留在替换后的字符之后。出现在草稿其他位置的「、」保持原样,@文件菜单不会对该标点作出反应。组字期间「、」保持原样,提交后结果为/,不会出现/、;普通中文组字和已有草稿中的标点不变。取消组字留下空白草稿,替换草稿会清除原有的空草稿组字起始状态。 - 链接规格:
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。node scripts/e2e-composer-ime-stack.mjs挂载生产输入框、草稿和补全组件,通过 CDP 驱动 Chromium 组合输入,覆盖别名提交、普通中文、取消组字、草稿替换、补全确认及丢失结束事件后的恢复。发送流程和真实操作系统输入法候选窗口仍需手动验证。
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和git@192.168.1.5: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和git@github.com: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 会话刷新事件。
- 使用插件的
- 确认项目和新导入的会话出现在默认侧边栏,然后再次归档项目。
- 在不导入内容时刷新会话,再通过
session.importBatch导入无路径会话以及重复或跳过的会话。
- 在不导入内容时刷新会话,再通过
- 预期:成功新增且绑定到项目的插件导入会清除对应规范化项目路径的归档状态,并使项目/会话可发现。普通刷新、无路径会话、跳过的导入和重复导入都保持归档状态不变;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>),且至少一个会话的存储时间戳损坏或越界。 - 步骤:
- 使用
importer-codex-scan.test.mjs对该归档运行会话扫描器。 - 检查候选会话的标题与每条会话展示的 createdAt/updatedAt。
- 通过旧导入器测试契约检查一个真实用户消息位于合成注入之后的会话;不再启动设置 UI 导入流程。
- 使用
- 预期:候选标题取自第一条真实用户消息——合成注入绝不作为标题出现,而以
#开头的真实粘贴内容(例如# Role: …)予以保留。用户消息全为合成的会话不作为候选出现。损坏或越界的存储时间戳回退到源文件的 mtime,绝不回退到导入时刻。 - 链接规格:
03-runtime/01-ipc-protocol.md、D320 - 验收:C(对话与流)、F(持久化)、品质
- 里程碑:M6+
- 状态:由
importer-codex-scan.test.mjs单元测试覆盖;不再有设置 UI 旅程
E2E-LAYOUT-three-column-width-priority
- 前置条件:在非设置路由中打开一个桌面会话,存在已持久化的首选工作面板宽度,窗口足够宽以容纳三栏。
- 步骤:
- 打开工作面板并请求用户的首选宽度。
- 将内部分隔线向 MainChat 左边缘拖动,包含指针预览阶段,然后释放。
- 在布局收起左栏后手动重开左栏。在 866px 视口中重复一次,此时重开会让面板窄于 244px;然后再次收起左栏,并朝面板方向拖动分隔线。
- 关闭工作面板并确认左栏恢复;再在手动收起左栏后重复一次。
- 用
ArrowLeft、ArrowRight、Home、End重复调整分隔线。 - Navigate to the real Plugins 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 的重开目标。重开借出的宽度不会被存储:已存储的首选宽度不变,再次收起左栏即恢复该宽度,分隔线拖动仍停在 244px。关闭面板只恢复由布局机制收起的左栏。分隔线的 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-WORK-PANEL-overflow-tab-drag-exclusion
- 前提条件:在 Windows 上打开桌面会话,并创建足够多的工作面板标签,使横向标签栏发生溢出。
- 步骤:将标签栏分别滚到两端,切换到另一个对话再切回,用键盘重排标签,然后逐个关闭标签,直到只剩一个和没有标签。使用原生
WM_NCHITTEST检查相邻侧边栏与对话标题栏、可见标签、标题栏操作按钮和多余空白。 - 预期:视口外的标签不会阻挡可见滚动视口以外的原生拖动。相邻标题栏和未使用的标题栏空白返回
HTCAPTION;可见标签和操作返回HTCLIENT。切换、重排和关闭标签后,这些命中区域保持正确。 - 链接规格:
04-ux/07-ui-design-system.md§10 - 验收:品质
- 状态:Windows 原生回归由
scripts/e2e-work-panel-drag.mjs(pnpm test:e2e:work-panel-drag)覆盖;macOS/Linux 原生拖动尚未验证。
E2E-WORK-PANEL-active-tab-close-visibility
- 前提条件:渲染工作面板,并创建足够多的长标题标签使横向标签栏溢出。
- 步骤:在窄布局中激活溢出标签,检查关闭按钮矩形是否落入标签栏视口,并确认
elementFromPoint命中关闭按钮。保持同一标签激活并缩窄布局后重复检查。随后将标签栏滚到一半,对部分可见的非激活标签执行悬停,再检查其关闭按钮。 - 预期:激活标签及布局缩窄后,关闭按钮都完整位于标签栏内;悬停部分可见的非激活标签时,其关闭按钮也会滚入视口。三个状态下按钮都可见且可命中,无需中键、键盘快捷键或扩大面板。
- 链接规格:
04-ux/07-ui-design-system.md§10,issue #1762 - 验收:品质
- 里程碑:M6 之后桌面壳维护
- 状态:
scripts/e2e-work-panel-tab-close.mjs在 Electron 中覆盖真实几何位置和命中目标;apps/desktop/test/work-panel.test.mjs也覆盖滚动契约。Windows 原生命中测试由scripts/e2e-work-panel-drag.mjs单独覆盖。
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 也不再列出它,输入框得到完全相同的文本与原始文件引用 芯片——而不是被剥离标注的内联内容。重新发送产生的提示与排队时一致。重启后即使内存 草稿已不存在,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-explicit-project-owner | 在 MCP 页选择 Project,打开市场并安装内置条目;请求携带所选项目,返回后显示项目记录。All 和 Global 保持全局安装,其他层级中的同名条目不阻止安装 | 隔离 Electron 中的真实 React 页面和市场;IPC 边界模拟(apps/desktop/test/mcp-market-owner-user-path.test.mjs) |
| 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-MARKET-CATALOG-RETRY | 技能市场和 MCP 市场查询全部失败后、以及部分来源失败时都显示重试;点击后播报加载、重跑当前查询,并在来源恢复后清除失败状态 | Electron 中的真实 React 组件使用确定性的模拟市场 API 响应;不访问公网 |
E2E-MCP-MARKET-explicit-project-owner:市场安装遵循明确选择的 Project 筛选
- 前提条件:MCP 设置已选择项目;使用隔离 Electron 配置和确定性 IPC 响应,不访问真实市场或调用 MCP。
- 步骤:选择 Project,打开市场,安装内置条目后重新打开市场。分别验证 All、Global、All 下未选择项目,以及另一层级已存在同名条目的情况。未选择项目时选择 Project,再通过面板公开接口尝试安装。
- 预期:Project 请求携带
level: "project"和所选projectPath;All、Global 请求携带level: "global",不传项目归属。返回后显示已安装记录,重新打开市场时仅按目标层级标记已安装。其他层级中的记录不阻止安装。未选择项目时,Project 市场入口禁用,面板保护逻辑提示缺少项目且不写入。 - 关联规格:
04-ux/06-settings-ia.md、ADR 0112。 - 验收:质量。
- 里程碑:M6+。
- 状态:已自动化(
node --test apps/desktop/test/mcp-market-owner-user-path.test.mjs)。
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-MARKET-CATALOG-RETRY:重试失败或部分可用的市场查询
- 前提条件:渲染技能市场和 MCP 市场面板,并在 IPC 边界模拟市场 API。每个 API 依次返回全部失败、一个来源失败的结果、完整成功结果。第二组先加载成功、随后失败、重试后恢复;第三组在成功后切换搜索词再失败,使快照属于另一个查询;第四组在从未成功过的情况下失败。
- 步骤:打开两个市场;全部失败后点击重试;确认加载播报和恢复的目录条目;部分失败后再次点击重试。随后先加载出列表、令下一次查询失败、重试,再切换搜索词后失败一次。
- 预期:两个市场都按当前查询重新请求,期间显示加载状态;部分来源失败时仍有重试入口;来源恢复后失败项清除。成功加载过后再遇失败,列表保留上次成功加载的结果并标注时间,MCP 条目的来源角标也保留,重试入口保留;重试成功后保留列表消失,且该列表不回答其他查询、也不在从未成功过时出现。测试不访问公网,也不改变主进程网络策略。
- 链接规格:
04-ux/06-settings-ia.md、ADR 0177、ADR 0272 - 验收:Quality
- 里程碑:M6+
- 状态:已自动化(
pnpm test:e2e:market-retry、apps/desktop/test/skill-market-panel.test.mjs、apps/desktop/test/mcp-market-panel.test.mjs)
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,使用确定性消息/状态转换,无需模型凭据。 - 步骤:发送一轮,输出部分文字后停止增量,开始并完成工具,切换和清空具体阶段,恢复文字后完成、停止或失败。分别在
Show thinking开启与关闭时重复;验证规划、权限、提问与提案等待、历史阅读及会话切换。 - 预期:输出暂停和工具完成后仍有且仅有一行本地化、可访问的底部状态。具体阶段优先于规划/目标或工作中。等待用户操作时隐藏;回合结束与历史阅读时移除。会话切换只使用对应会话的状态;已有内容与权限操作保持可用。运行中与含助手错误的回合不挂载空操作栏;默认字号下,部分回答的片段盒底部到状态文字顶部为 24 CSS px。结束后的无错误正文保留复制、分支与重新生成。需分别在
Show thinking开启与关闭时验证。历史阅读和权限卡保留正常消息间距;回合结束后贴底模式继续跟随底部,恢复正常操作栏高度;向上阅读时正文位置和滚动偏移保持不变。 - 链接规格:
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种子,一次使用任意其它已存值。 - 对存的是通用
8192输出上限的行、以及用户手改过上限的行重复步骤 4, 读取设置行里的上限与新会话实际请求里的上限。
- 配置一个提供商,勾选 models.dev 已发布
- 预期:步骤 1 显示发布值并带「跟随 models.dev」提示。步骤 2 在所有使用 effective window 的地方(设置行、上下文检查器、会话启动)都显示修正后的值,无需删除重建。 步骤 3 在设置行、检查器和实际请求中都保留用户输入的值,包括在目录窗口更大时手输的
128000,且提示消失。步骤 4 表现确定:128000种子跟随目录,其它值保持原样。 步骤 5 对输出上限套用同一套来源规则:8192种子跟随已发布的limit.output, 用户输入的上限保持不变。每一步中标记都能在提供商行的保存/读取往返后保留, 早于该标记写出的配置仍可读。 - 关联规范:
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:resolve 及时应答时官方渠道安装使用平台镜像
- 先决条件:全新配置,目录中存在一个插件,并可记录平台与两个镜像主机的请求(各项都可由本地存根代替)。
- 步骤:1) 打开扩展 → 市场并确认目录已加载。 2) 捕获目录请求,确认使用
plugins.aiuo.net。 3) 安装插件。 4) 捕获平台收到的请求。 5) 检查哪个镜像提供了安装包。 6) 再安装第二个插件,然后重新安装第一个插件的同一版本。 - 预期:市场页面没有来源选择器;每次安装或更新只发出一次带
deviceId、pluginId以及可选版本的POST /api/v1/download/resolve;resolve 在三秒内应答时,按顺序尝试downloads并在解压前校验平台返回的sha256与sizeBytes;某个镜像不可达或失败时无需用户操作即可尝试下一个;resolve 超时后从目录 URL 下载并校验目录摘要;重新安装同一版本会发起新的 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:平台 resolve 缓慢或不可用时快速回退到目录地址
- 先决条件:官方目录已刷新;本地存根可让
POST /api/v1/download/resolve延迟超过三秒,或返回429、503 NO_DOWNLOAD_SOURCE与发布拒绝。 - 步骤:1) 让 resolve 存根延迟超过三秒并安装插件。 2) 确认界面离开 Resolve 阶段,开始从目录 URL 下载。 3) 分别用
429和503 NO_DOWNLOAD_SOURCE重复。 4) 对所选版本分别返回403 NOT_PUBLISHED、403 PLUGIN_ARCHIVED和404。 - 预期:慢 resolve 三秒后被放弃;超时、
429和503 NO_DOWNLOAD_SOURCE都从目录的artifactBaseUrl与相对url回退,且在安装前通过目录摘要校验;限流不等待、不重试;403 NOT_PUBLISHED、403 PLUGIN_ARCHIVED和404仍明确报错,不会安装回退包;已归档插件仍从安装与更新选择中隐藏;所选渠道保持不变。 - 链接规格:
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 和外部点击关闭; 验证每小时无时间输入且首次等待一小时;设置每天任务在下一个真实分钟执行; 选择间隔周期并填入 30 分钟,确认任务行显示该跨度而不是时钟,再切到每周周期后切回,确认该值仍然保留;打开编辑表单,确认任务列与任务页面让位; 让一个任务积累超过共享窗口的运行记录、另一个任务保持空闲,确认空闲任务仍然显示自己的最近一次结果; 观察自动完成;删除已结束的任务。普通 Agent 对话经模型工具调用发现、创建、查询、 修改任务到 15:30,再删除;验证页面显示具体时间,改名保存不覆盖。模型为本地确定性夹具。
预期: 项目、权限和精确 provider/model 只保存在该任务,重新打开仍显示相同值并实际传到 sidecar,其他任务不受影响;项目、权限和模型控件保持嵌在“指令”框的 Composer 风格底栏中,窄窗口也不产生横向溢出;缺少新增字段的旧记录保持原默认行为。配置持久化并显示下次时间;暂停后不触发;手动与自动入口均调用真实 Agent sidecar;本任务的运行记录列出每次运行的状态与耗时,「打开会话」到达持久化会话,而会话列表与会话搜索不会列出它,该会话顶部栏可返回同一个任务与同一次运行;间隔任务按表单给出的跨度排程并在任务行中显示,表单打开时独占本页;让一个任务积累超过共享窗口的运行记录、另一个保持空闲时,空闲任务仍显示自己的最近一次结果;自动执行不依赖渲染器发送提示词。 宿主测试补充验证重复准入、错过时段、无效输入和重启恢复。
规格: 04-ux/01-ui-ia §3.3;04-ux/08-component-spec §6、§20A;03-runtime/04-data-storage §4.11; ADR scheduled-desktop-automations;ADR 0305。
验收: 定时执行与可恢复的运行历史。
里程碑: MVP 后的桌面自动化。
状态:
node scripts/e2e-scheduled.mjs自动化覆盖;按 AGENTS.md 在任务候选 版本上运行,并记录已测提交与基线。
<a id="e2e-config-sync-webdav-portable-configuration"></a>
E2E-CONFIG-SYNC-s3-portable-configuration
Follow the current S3 acceptance scenario.
- Use separate Host processes, data directories, HOME and agent directories. Local S3 fixtures exercise conditional writes, authentication, merge, conflict choices and repeated convergence, deletions, credential opt-out, approval and skill resources.
- 项目和项目组列表保留在本机,不提供 projects 同步类别。发布项目/项目组元数据 不会在接收设备创建或修改项目/项目组;接收设备自行注册 workspace 后,映射并 审批项目级技能,验证资源成功到达而本机项目组名称、根目录及排序保持不变。 同步/恢复忽略旧 vault 的
projects实体,保留本机项目和云端历史。 - Backup only uploads local snapshots; histories are mode-specific. Sync restore publishes with explicit acknowledgment; backup restore stays local.
- The real settings-page test covers secret-free drafts, saved credential reuse, explicit token clearing, mode selection, restore confirmation, stale history responses and disconnect. Development-build-only visibility remains unchanged.
- Live S3 is explicitly opt-in and authorized. Read credentials without logging or copying them; use a fresh random prefix, verify initial emptiness, clean only test-created objects and verify final emptiness. Never use the running desktop or real model providers. Record the tested candidate/base.
- A 30-entry history index does not imply physical garbage collection. Only the new S3 format is supported, without legacy detection or migration; unrelated data is not deleted. Recovery is not whole-filesystem atomicity.
- Automation:
pnpm test:e2e:config-sync, Rustconfig_sync::anduser_skills::,config-sync-user-path.test.mjs,config-sync-ipc.test.mjs. Full checkpoint-recovery fault injection is not claimed by these user paths. - Start a held manual sync, leave Settings, then return. Verify Host still reports
syncing, duplicate actions remain disabled, and releasing the local fixture finishes the operation and refreshes state. The aggregate RPC has no deadline; individual S3 requests remain bounded. - Acceptance: F (persistence), Security, Quality. Candidate validation required.
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、多条命令及 思考和最终回答;
Show thinking偏好开启与关闭两种状态;过程显示偏好分别为 「平铺显示」「命令工具优先」「全折叠显示」;重启后保留已保存的设置;旧版消息级 转录搜索目标。 - 步骤: 确认活动组默认展开,让同一回合在无人操作时完成,并确认过程收起,只剩最终 回答(以及错误或待处理操作面)。另一个回合中,明确展开已完成组,并确认后续更新仍保留 该选择;随后收起回合披露并确认组随之收起,再重新展开并确认组选择恢复。在「全折叠显示」 中,确认命令行留在所属活动组内,且只有普通助手进度文字会拆分活动组。在「命令工具优先」 中,确认
Bash/run 行作为组旁的独立行渲染,普通助手进度消息或命令会结束当前组, 相邻调用保持分离,思考行默认展开。在「平铺显示」中,确认不渲染嵌套活动组,外层披露按 转录顺序列出工具、命令、思考和进度行,工具详情保持收起。在每种模式中确认回合只渲染 一个外层披露,收起它会隐藏、重新展开会恢复其下的行。关闭Show thinking,确认不再 渲染任何已完成的推理行,仅在推理流式输出期间保留整段运行的 TextShimmerThinking指示器,不使用转圈、脉冲圆点或逐字符动画,减少动态效果时保持静态;重新打开后确认 已完成的推理行恢复。分别选择每个过程显示值并重启,确认设置保留;移除已保存的值, 确认默认使用「全折叠显示」。确认标题分别显示执行中、已完成、已停止或失败,执行期间 压缩上下文与恢复会各自命名,未记录时长时不显示时长,且绝不回显当前命令。确认顶层标题 只显示状态、已记录的耗时与直接工具/搜索数;折叠指示紧跟工具数;标题行下方有一条细线; 悬停只改变文字颜色、不绘制背景;失败回合不追加问题计数。检查嵌套路径, 包括相互独立的组/条目切换、父级收起与恢复、单项片段、字面最后一项选择、失败/被拒/ 恢复、保留窗格重新挂载、命令行详情在打开前保持收起,以及旧版搜索定位。 - 预期: 一个回合只渲染一个过程披露,包含全部回答前的行,最终回答、助手错误、中止后 的末尾文字和待处理操作留在披露之外;没有最终回答文字时,过程标题和这些错误或待处理 操作面仍然保留。未被操作的披露在完成时收起。「平铺显示」中没有嵌套活动组,活动工具、 命令、思考和进度行按转录顺序排列,工具详情保持收起。「命令工具优先」中,
Bash/run 行 作为组旁的独立行,相邻调用保持分离,普通助手进度消息或命令会结束当前组,因此中间进度 段落和每条命令都会开启新组。「全折叠显示」(默认)中,命令行留在所属组内,只有普通 助手进度文字会拆分活动组,因此中间进度段落会结束前一组,后续活动开启新组。只有在至少 两个可见项时才显示组;单项没有组。思考行在「平铺显示」与「命令工具优先」中默认展开; 「全折叠显示」保留已发布的例外:较早的行保持收起,只有最后一个活动行在其为当前步骤时 展开,完成后收起。用户明确选择的状态继续保留,并在外层披露重新展开时恢复;这些组默认值 不依赖Show thinking偏好。回合标题按执行中、已完成、已停止、失败的顺序报告,失败与 停止优先于正常完成,当前步骤为推理时显示Thinking,执行期间压缩上下文和恢复会各自 命名,未记录时长时不显示时长,且绝不回显当前命令。标题只包含状态、耗时与直接工具/ 搜索数,折叠指示紧跟工具数,标题行下方有细线,悬停只改变文字颜色、不绘制背景,且不 显示问题计数。嵌套组标题与命令行共用回合标题的左边缘。Show thinking关闭时,不再 渲染任何已完成的推理行,仅在推理流式输出期间保留 整段运行的 TextShimmerThinking指示器;重新打开时恢复。除显示模式的思考默认值外, 没有任何条目载荷会自动展开:工具/搜索行在用户打开之前保持收起。父级、子级和同级状态 相互独立;窗格拥有的用户选择跨更新、偏好切换和重新挂载保留,渲染器重启后重新应用默认值。 搜索只展开拥有目标消息的回合过程,以及存在时的活动组,每个请求应用一次;条目级精确定位 不在本次范围内,被偏好隐藏的推理会保持隐藏,直到用户打开Show thinking。已保存的transcriptDisplayMode跨重启保留,缺失或未知值解析为「全折叠显示」;已保存的showThinking跨重启保留,缺失值回退到旧版thinkingDisplayMode(compact隐藏,detailed/未知/缺失显示)。 - 2026-09-20 变更的验证范围: 本次变更只包含多级折叠与活动组呈现;精确转录搜索 定位不在本次范围内,保持原有搜索行为。
- 自动化:
pnpm test:e2e:transcript覆盖「命令工具优先」与「全折叠显示」布局 以及运行中到完成时的过程披露行为;pnpm test:e2e:transcript-disclosure验证手动 展开状态保留和视口锚定;apps/desktop/test/turn-process.test.mjs覆盖模式分段,apps/desktop/test/transcript-display-modes-ui.test.mjs覆盖渲染层模式接线、 思考默认值与 TextShimmer,apps/desktop/test/turn-process-header.test.mjs覆盖 顶层标题的状态/耗时/工具数组合及不显示问题计数,apps/desktop/test/turn-process-header-style.test.mjs覆盖整行细线与仅文字变色的 悬停,apps/desktop/test/transcript-display-mode-settings.test.mjs覆盖设置行与搜索索引,apps/desktop/test/settings-roundtrip.test.mjs覆盖规范化、 写入校验与全折叠默认值。 - 规格: 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用真实的运行时覆盖两半:重复历史(丢弃 + 一行日志)与唯一历史 (同一对象、无日志)。状态: 单元测试覆盖;没有端到端驱动对重复转录发出真实提供商请求。
Reused-id regression (#1815): Run two actual tool executions whose provider responses reuse the same id, then inspect the next request. Both results must remain, with distinct paired ids and no synthetic placeholder. Repeat after context trimming and restored history, and verify cancellation, metadata and replayed pending snapshots retain their semantics.
runtime.test.tscovers the parent prompt path;subagent-fallback.test.tsdrives the real Pi adapter over local HTTP with two executions and three requests;tool-call-identity.test.tscovers retained identities after trim.
E2E-RUNTIME-system-update-during-tool-call-restore
先决条件: 一份会话转录,其中一条模型系统记录在工具调用执行期间保存:位于发出该调用的助手行之后、该调用的工具行之前 (
beforeMessageId为工具行 id)。在启用对话中途系统消息的确定性 Anthropic Messages 夹具下加载进重新创建的运行时;不使用真实凭据。步骤: 发一条提示,使运行时组装并发出请求。读取夹具收到的出站请求体。
预期: 工具调用仍挂在发出它的助手消息上、紧随该消息的文本,不合成只含调用的助手消息。每条对话中途的
system消息都跟在user消息之后,因此提供商不会回role 'system' must follow a 'user' message or an 'assistant' message ending in a server tool result。规格: 03-runtime/02-agent-runtime §5c 与 §7.0.0、08-meta/decisions-log D127。验收: C(对话与流)、品质。 里程碑: Post-MVP 回归覆盖。
自动化:
packages/agent-runtime/src/system-transcript-runtime.test.ts把这些行恢复进真实运行时,并把捕获的请求交给 Pi 的 Anthropic 适配器。状态: 单元测试覆盖;没有端到端驱动对 Anthropic 提供商夹具恢复这类转录。
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-OUTPUT-CAP-716:保持模型请求在发布上下文窗口内
- 先决条件: 确定性 provider 夹具公布上下文窗口,支持父级和子代理请求并记录请求负载;不使用真实凭据。
- 步骤: 配置大于已发布窗口的用户上下文覆盖;发送包含 CJK 文本的父级提示、调用子代理并运行一次性补全,再以
thinkingLevel: "omit"重复父级请求。随后恢复一个包含许多大型系统指令更新的长会话,并通过不支持对话中途系统消息的 Responses 网关继续;再运行一个最后一次用量报告之后才追加多次系统指令更新的子代理。 - 预期: 每个请求都有不超过估算剩余窗口的明确输出预算;运行时按已发布安全上限压缩,且
input + output不超过上限。能容纳时,小型 ASCII 请求保留配置的预算;恢复会话和子代理仅发送一条折叠后的指令消息,并保留配置预算,而不是降至 provider 最小值。 - 关联规格:
03-runtime/02-agent-runtime.md、03-runtime/13-model-catalog-and-selection.md。 - 自动化:
packages/agent-runtime/src/output-cap-request.test.ts通过 loopback Responses 网关驱动恢复会话和子代理请求。 - 状态: 折叠指令步骤由 loopback 网关覆盖;其余确定性 provider 夹具待补充。
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-MCP-HTTP-SSE-held-open — A streamable HTTP reply lands before the server ends the stream (issue #1188)
- Preconditions: A local mock Streamable HTTP server writes its JSON-RPC reply immediately and keeps the
text/event-streambody open well past the client's handshake budget — the shapehttps://gitmcp.io/docsshows, where initialize is answered in about two seconds and the stream only ends about twelve seconds later. No provider credentials needed. - Steps: Configure that server with the
httptransport and a handshake budget shorter than the stream lifetime; connect, discover the tools, and call one. Repeat with a server that keeps the stream open and never answers. - Expected: Handshake, discovery, and the call complete as soon as their reply event arrives, so the connection reports
readywith the discovered tools instead ofmcp initialize timed out after <budget>ms. A server that never replies still fails withTIMEOUTinside the same budget, and a stream left open past its request is aborted rather than kept open. - Specs: 07-plugins/01-plugin-system §12.2; ADR 0038.
- Acceptance: HTTP transport integration and the main-process MCP handshake.
- Milestone: Maintenance.
- Status: Automated by
apps/desktop/test/plugin-mcp.test.mjs(held-open reply case); no live app 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-IMAGES-result-download | 下载与定位生成图片 | 03-runtime/21-image-generation | scripts/e2e-image-chat.mjs |
E2E-IMAGES-result-download
- 前提: Agent 会话有两张已生成的 PNG,使用本地图片夹具。
- 步骤: 确认一张主图和右侧两张缩略图,下载第一张结果,选中并下载第二张,在窄窗口检查聊天布局;打开选中图片的全窗口预览,双向切换并放大,用 Escape 关闭,检查“在文件夹中显示”操作。
- 预期: 主图和下载目标随缩略图切换;选中缩略图显示浅灰色外框,切换已加载图片时卡片和预览不会短暂空白,预览保持适配尺寸;快速反向操作会取消尚未完成的图片解码;两张下载文件分别与原图字节相同,文件名安全。窄窗口中的聊天和预览缩略图仍可操作;预览切换、缩放和焦点恢复可用,关闭后对话保留选中项,原图仍可访问;定位操作使用受限文件接口。
- 验收: 结果操作和图片浏览不改变图片存储和预览权限。
- 状态:
scripts/e2e-image-chat.mjs自动验证下载和预览交互;定位复用现有受限 IPC。
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.3.
- 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.3.
- 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-SETTINGS-destination-scroll-reset
- 打开设置 → 对话,滚动到中间,再选择快捷键:标题和首项从顶部显示。滚动后 返回对话,该页也从顶部显示。
- 再次选择当前分类,或不离开当前页更新设置,保留内容区滚动位置。
- 覆盖内置分类 → 插件、插件 → 插件、插件 → 先前选择的内置分类;再次 选择当前插件分类时保留位置。
- 从其他分类、当前打开的插件分类,以及对话当前页通过全局搜索设置锚点进入 对话:插件页关闭,目标项在下一次绘制前可见,消费锚点后保持定位。没有锚点的 外部切页也会离开插件,并从顶部开始。
- 在明暗两种主题下运行。
- 自动化覆盖:
pnpm test:e2e:settings-scroll在隔离 Electron 中挂载真实的 SettingsPage、store、翻译和构建后的 CSS,探针运行的是开发构建。仅 preload 数据使用 fixture;搜索导航调用 SearchDialog 使用的公开 store 入口。该测试还 检查开发版中云同步与远程主机的实验性标签;settings-developer-only-destinations.test.mjs另行验证打包版可见性、搜索结果及仅开发者目的地的回落行为。该测试覆盖渲染层交互, 不覆盖 host 持久化或 完整全局搜索弹窗。
E2E-SETTINGS-one-destination-one-subject
- 场景:D684 的目录拆分。每一项设置都能从负责它那个主题的目的地找到,且不会 出现在两个目的地里。
- 步骤:
- 打开设置。导轨按四个带标题的分组列出外观、对话、快捷键、语音、行为、模型、 指令、技能、MCP、子智能体、项目、系统、云同步、远程主机、信息。
- 进入外观:界面(主题、语言、字体、字号,macOS 另有毛玻璃)、侧边栏及其两个 缩进子开关、轮盘右键菜单、启动。没有网络、存储、电源或关闭行为卡。
- 进入对话:显示(显示思考 + 子行、过程显示 + 子行、上下文用量、平滑流式、 内联可视化)在一行可见副标题之下,该副标题说明这里只改变呈现;然后是输入与 链接(回车发送、粘贴阈值、链接打开目标)。
- 进入行为:会话默认(默认模式、权限模式、命令 Shell)、执行(无尽重试、定时 任务)、电脑操作在最后。
- 进入系统:网络、存储、电源,以及 Windows/Linux 上的关闭行为。
- 在导轨搜索
theme、主题、proxy、scheduled、paste、permission。 每个查询只命中真正渲染该行的目的地,点击命中后滚动到该行并闪烁提示。 - 在这四页各改一项设置,关闭设置再打开,读回取值。
- 预期:没有设置同时出现在两个目的地,也没有设置无处可去。每页以导轨字形的 页面尺度版本、标题和一行副标题开场;能力页、模型页和项目页改为显示自己的描述、 不带副标题。行的解释仍只在它的问号之后,唯一常驻的说明文案是卡的副标题。 迁移不改变持久化取值——拆分之前写入的配置读回来完全一致。
- 自动化:
settings-general.test.mjs钉住目的地索引、各页归属与语言包键;pnpm test:e2e:settings-scroll在 Electron 中检验导轨、搜索锚点与各目的地滚动。
E2E-SETTINGS-retired-destination-ids-resolve
- 场景:拆分之前写下的目的地 id 仍能打开页面。
- 步骤:让配置或深链选中
general后打开设置;再用ai重复。随后触发原生 设置菜单项与命令面板的设置入口。 - 预期:
general打开外观,ai打开行为;两个 id 都不出现在导轨和设置搜索中。 未知 id 打开外观。内容区绝不空白。 - 自动化:
settings-general.test.mjs断言解析表,settings-developer-only-destinations.test.mjs断言隐藏目的地的回落使用索引的 默认目的地而不是写死的 id。
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 配置,不向真实提供商发送推理请求。
E2E-MARKDOWN-table-actions
- Setup: Render a conversation containing two Markdown tables, including aligned columns, formatted text, Chinese text, commas, quotes, and
<br>cells. - Steps: Copy the first table; download its CSV; expand it; use copy inside the modal; close with Escape and with Close. Append a streamed row while the preview is open. Repeat the preview at a narrow width in light/dark themes and English/Chinese. Deny clipboard writes at the browser boundary.
- Expected: Actions operate only on their own table. Markdown retains inline syntax and alignment. CSV decodes as UTF-8 and preserves fields and line breaks. The modal fits the viewport, traps focus, updates streamed rows, blocks native work-panel surfaces, and returns focus on dismissal. Narrow previews keep short headers on one line and scroll horizontally; sticky headers fully cover the rows behind them. Failed copies report an error, not success. Existing table wrapping remains intact.
- Automated coverage:
node --test apps/desktop/test/markdown-table.test.mjsandnode scripts/test-markdown-table.mjsafter building the desktop. The latter mounts the production Markdown component in an isolated Electron window and exercises real clipboard/download boundaries. It does not call a model or use the user's app profile.
原生搜索续跑契约(离线 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-CHAT-fork-completed-reply-while-running
- Preconditions: Isolated real desktop profile, configured model, two turns in one Desktop conversation; the first assistant reply has completed.
- Steps: Send the second prompt. While it is still running, click Branch from this reply on the first reply. Continue chatting in the child, then return to the parent.
- Expected: The child contains only history through the first reply, can continue independently, and starts with no running turn. The parent keeps running and retains its second prompt and reply. No live tail is overwritten. A whole-session fork and a fork within the active tool loop remain rejected. A navigation during the fork response records the child without stealing focus.
- Coverage: Host fork regression, renderer session-fork-running tests, session IPC contract tests, and real-model desktop acceptance. A local model fixture or mocked component result is not real-model acceptance evidence.
E2E-WORKTREE-isolated-session-lifecycle
- 前置条件: 隔离的桌面配置和数据目录、git 2.29+、一个处于分支上且至少有一次提交的 git 项目、已配置的模型,以及可选的
.worktreeinclude(列出一个被忽略的文件)。 - 步骤: 从项目菜单创建两个隔离会话,同时在两者中运行修改文件的提示。运行打印
PI_PORT/PI_WORKTREE_ROOT的 Bash 命令。把第一个会话合并到项目;在项目和第二个 会话中修改同一行,再合并第二个会话。在第二个会话中解决冲突后再次合并,然后在第一个会话中使用从项目同步。在一个隔离会话 中并行启动两个写文件的子代理。最后删除一个含未合并工作的隔离会话,并在会话存在时重启 一次应用。打开一个非 git 项目的菜单。 - 预期: 每个会话的修改只落在自己的 worktree 中;在合并前项目检出和
git status保持干净,每个 worktree 中都有被 include 的忽略文件。两个会话获得不同端口。第一次合并 快进项目,成功提示中给出分支名。冲突的合并会列出文件、不改动项目,并把该行标为Conflict;解决后徽标消失、合并成功。第一个会话行显示↓N;从项目同步会显示新提交数并清除该标记,并把冲突解决结果带入第一个会话,不改动项目。写文件的子代理在各自的 worktree 中编辑,完成的运行合回会话。 删除时提示未合并工作将丢失,并删除 worktree 文件夹和pi-desktop/<id>分支,不触碰其中 链接指向的文件。重启后会话在其 worktree 中重新打开;被手动删除的 worktree 显示Folder missing,且绝不回退到项目目录。在非 git 项目中 新建隔离会话 置灰,并在下方 显示原因。 - Windows 检查: 含目录 junction 的 worktree 删除时不跟随 junction;超过 260 字符的深 路径能创建和删除;worktree 中有文件被锁定时删除会明确失败并可重试。
- 关联规范:
03-runtime/03-tools-and-permissions.md§4e、§10.2;03-runtime/06-host-rpc-protocol.md;04-ux/01-ui-ia.md§3.2;ADR 0329。 - 覆盖: host-core
session_worktrees测试(创建、合并、冲突、删除、链接安全、启动 对账、子代理)、worktree IPC 契约测试、渲染层worktree-ui测试和 agent-runtime 子代理 测试。Windows 检查在 Windows 主机上以 host-core 测试运行:junction 删除(remove::tests)、 根目录被替换为链接(lifecycle::tests),以及经 RPC 用户路径的深路径和文件占用时删除 (rpc::worktree_rpc::tests)。pnpm test:e2e:worktree-live(scripts/e2e-worktree-live.mjs)在协议层用真实模型执行上述步骤,不经过渲染层。 - 状态: Partially automated
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。
E2E-PROVIDER-endpoint-guidance-and-search
- 前提: 隔离 Electron/Chromium、真实配置表单、合成响应,无真实密钥或服务。
- 步骤: 打开已有 DeepSeek、xAI、旧 OpenAI 服务,直接勾选搜索、取消、重开、 勾选并保存、重开后取消勾选并保存;应用中转站完整请求地址的格式建议。
- 预期: 一个服务入口、一个搜索开关;没有额外搜索预设或切换接口按钮。 只保存模型搜索选项,地址、协议、名称、密钥及其他模型配置保持不变;取消不写入。 已保存格式优先于域名预设,中转站建议不改变来源。
- 规格: 03-runtime/12、03-runtime/11、ADR 0297 官方路由修订。
- 验收: 中英文及原有自定义/OAuth 路径通过;真实适配器验证开关控制的请求路径、 凭据和工具。DeepSeek 同一会话关闭→开启→关闭搜索后仍能继续,保留文本, 不向 Completions 重放搜索加密数据。
- 里程碑: 提供商配置维护。
- 状态:
pnpm test:e2e:provider-api-style、official-native-search.test.ts; 共享路由测试覆盖伪装域名、不安全地址和未知中转站。未验证线上服务或 Host/SQLite 保存。
E2E-260:Windows 更新缓存可安全迁移和回收
- 前提: 已打包的 Windows x64 NSIS 安装、隔离用户配置,以及非系统卷上的可写缓存目录。使用本地更新源 fixture;不得访问 GitHub 或付费服务。
- 步骤: 1)在旧
%LOCALAPPDATA%更新缓存中准备installer.exe、current.blockmap和待安装更新。2)设置PI_DESKTOP_UPDATE_CACHE_DIR为隔离缓存根目录后启动。3)确认 Main 将差分基线和待安装更新迁移至配置目录,并清理旧目录。4)完成更新并启动新版本。5)fixture 报告没有更新后检查配置目录。 - 预期: 缓存子目录名以
app-update.yml为准;迁移保留待安装更新及差分基线。确认当前版本已是最新版本后,只移除pending/,installer.exe和current.blockmap仍供下次增量更新使用。未设置覆盖变量时默认路径保持不变;应用不会将下载写入Program Files。 - 规格:
03-runtime/07-process-model.md、ADR 0022。 - 验收: 缓存路径、迁移和清理单测通过;Windows task-candidate 验证应覆盖更新源传输、安装器交接和文件系统行为,且不连接真实发布源。
- 里程碑: M6+
- 状态: 单测和源码契约覆盖(
update-cache.test.mjs、auto-update.test.mjs);仍需 Windows 安装器/E2E 验证。
E2E-FIXED-TOOL-DECLARATIONS: Stable Flash schemas with independent activation
Fixture: production AgentSidecar and isolated Host, official Pi Flash binding, and a child-process fetch boundary redirected to local HTTP/SSE. Credentials are dummy values and synthetic tools have no external side effects.
Prompt, search Alpha, execute Alpha, search Beta and execute Beta. HTTP payload tests assert identical ordered
toolsand unchanged prior message prefixes. Directly calling a declared but inactive tool must fail before the Host.Restart both processes, then compact and restart again: Alpha stays activated, Beta remains inactive despite its declaration. Activate Beta, then remove it from the catalog and verify it cannot execute. New schema/route epochs must not restore old grants. Host rejection and Plan guards remain effective.
Runtime contracts additionally cover legacy migration, interrupted activation, malformed metadata, temporary prompt replacement, deterministic catalog order, the 128-tool boundary and insufficient-context fallback.
Automated:
node scripts/e2e-fixed-tool-declarations.mjswith built shared/host-runtime/agent-runtime andPI_DESKTOP_HOST_BINset to the candidate Host binary;fixed-tool-runtime.test.tsandfixed-tool-declarations.test.tscover HTTP payload and policy contracts. Electron UI/outbox is outside this fixture's scope.Separately authorized official Flash experiments compare several independent on-demand/fixed sessions with the same synthetic catalog and call sequence. Record cold requests, both activations, follow-up cache hits/misses and cumulative input cost. Report the larger first request and possible short-chat cost increase, alongside any longer-conversation benefit. Offline test success alone is not evidence of provider cache behavior.
Overflow/system-update integration: recover a provider context overflow with a system delta after the failed assistant message. Keep that delta, remove the failed assistant before compaction, and reuse one visible assistant message through successful recovery. Terminal failure and Stop retain their existing closure behavior. Covered by the parameterized runtime overflow user-path test.
E2E-262:聊天 path:line 引用打开文件并滚动到目标行
- 前提: 隔离 Electron/Chromium、活动工作区和会话、可用的随应用打包文件管理器视图,以及确定性的文件系统 IPC fixture。
- 步骤: 渲染真实聊天中的
path:line:column引用并在末尾加句末标点;点击已验证的文件芯片,等待宿主文件查看器加载。 - 预期: 即使文件管理器视图可用,带位置的引用仍打开宿主只读文件选项卡。文件请求保留行列号,查看器滚动到视口中间的目标行;打开路径和文件内容与引用目标一致。
- 规格:
04-ux/08§11.8;ADR 0262。 - 状态:
node scripts/e2e-file-ref-line-scroll.mjs在隔离 Electron 中挂载生产LinkifiedText与FilesTab,并通过文件系统 IPC fixture 提供文件内容;不访问真实项目文件或模型服务。
E2E-263:折叠项目后重置已加载的会话历史
- 前提: 侧边栏中保留一个至少有 12 条未置顶会话的项目,并可展开该项目组。
- 步骤: 1. 展开项目,确认最新的 10 条会话和“加载更多”控件可见。2. 点击“加载更多”,确认 12 条会话全部显示。3. 折叠项目后再次展开。4. 检查显示的会话和“加载更多”数量。
- 预期: 重新展开项目后只显示最新的 10 条未置顶会话,“加载更多”显示剩余的 2 条。折叠只清除该项目已加载更多的状态;会话选择和其他项目组不变。
- 规格:
04-ux/08-component-spec.md§3.9;04-ux/09-interaction-patterns.md§1.6。 - 验收: C / D / Quality。
- 里程碑: MVP 后维护。
- 覆盖:
scripts/e2e/sidebar-row-states.mjs使用 Host fixture 项目和真实 CDP 指针点击验证完整用户流程。sidebar-project-sessions.test.mjs覆盖初始页面、加载更多、折叠重置和项目隔离;app-store-sidebar.test.mjs验证侧边栏折叠与加载更多使用对应状态转换。
导入扩展时发现 GUI 环境下的可执行文件回归(#1173)
使用无法解析 Node/npm 的 GUI 风格 PATH 启动依赖安装,并在 ~/.local/bin 准备可信 fixture 安装。 校验和 npm 两个安装阶段都必须使用该安装,同时保留应用自身的 PATH,且不探测 shell 启动文件。 显式选择的 npm 仍优先使用其所在目录。缺少安装时仍返回 npm-unavailable 并保留原生选择器恢复路径。 可执行集成 fixture 使用真实隔离子进程,并检查仅注册表安装和凭据隔离。
E2E-CHAT-subagent-parent-error-resume
- 前置条件:编译后的运行时与本地 provider 模拟服务;使用真实 Agent、SDK 传输、 Task/TaskStop 和委托历史,仅模拟 Host Read 边界。
- 步骤:启动两个委托,各自完成一次 Read,再让两条 provider 流保持活动。 使父级请求失败;在后续提示中恢复两个 id,并检查恢复请求。分别重复用户 Stop 和 TaskStop 场景。
- 预期:父级失败后会话回到空闲;两个委托以 failed 和 SUBAGENT_PARENT_FAILED 结算,并各自带着原来的 Read 上下文恢复。显式 Stop/TaskStop 产生 aborted/stopped; 两个恢复请求均被拒绝,不再启动委托。不会自动重启。
- 关联规格:03-runtime/02-agent-runtime.md §5f;ADR 0279。
- 验收:C、E、Quality。里程碑:维护。
- 状态:通过
node scripts/e2e-subagent-parent-error.mjs自动验证无头运行时与 provider 集成;不使用真实账户或桌面数据。运行时测试另行覆盖父级 429 耗尽与 Stop 和结算之间的竞态。
E2E-PLAN-HISTORY: Immutable submissions retain current status in chat
Run node scripts/e2e-plan-history.mjs against the committed candidate host. Use an isolated Electron profile/workspace and the production session IPC, preload, API normalization, store events, ToolRow, and Markdown renderer.
- Submit Plan and read a capped history page: expand the full Markdown and open its exact artifact path in the owning session's work panel.
- Approve and deliver a stale pending tool echo: history still says approved. Cover approval-before-message and inactive retained-session events.
- Complete execution, submit/reject a revision, and read both newest/older pages: retain both exact bodies and statuses, mark only the older Plan superseded, and leave unrelated sessions/Goal untouched.
- Submit/reject Goal and check localized Chinese status.
- Continue chat, append a compaction checkpoint, restart host and renderer: both versions remain readable; no historical card has approval controls.
No provider credentials or paid model calls are required. The fixture ends at work-panel file-request routing; artifact bytes are verified from the real host-created files. The full app's file-preview viewer is covered separately.
E2E-PLUGIN-fetch-redirect-policy
- Preconditions: Current-main task candidate, built workspace packages, real Node plugin child process and two loopback HTTP fixtures; no real user profile, provider or credentials.
- Steps: Load the example probe plugin and invoke its
probe.fetchoperation through the production panel bridge. Query capabilities, request error/manual/ follow, and repeat against the injected single-hop transport. Exercise invalid modes, missing grants, disallowed targets, relative/missing Location, loops, and timeout. Test 301/302/303/307/308 and default compatibility. - Expected: Error returns REDIRECT_DISALLOWED and manual returns the original response, both with zero target requests. Follow reaches the target only after egress authorization. Error codes survive child IPC; refusals are audited.
- Specs linked:
07-plugins/03-plugin-api.md,07-plugins/04-plugin-security.md - Acceptance: G + Security
- Milestone: Post-MVP plugin API
- Status: Automated protocol E2E via
pnpm test:e2e:plugin-fetch-redirect. The sample panel's native-window gesture remains manual; no desktop is attached.
E2E-COMPOSER-configured-context-window
- **步骤:**为目录发布 1M 的模型保存 500K 用户额度。打开 Composer 模型列表,然后发送一条简短消息并检查上下文用量。对目录管理的额度以及模型发现不可用的情况重复验证。
- **预期:**用户配置的模型行和上下文检查器显示 500K;目录管理的额度跟随已发布值;没有发现信息的已配置模型仍显示保存的额度。模型选择保持不变。
- 状态:
apps/desktop/test/composer-models.test.mjs自动覆盖用户覆盖值、目录继承值及发现不可用时保留额度;完整的“保存 → 模型列表 → 发送消息 → 上下文检查器”路径仍需手动验证。
E2E-264:重新打开归档项目后恢复侧栏显示
- 前提条件:本地文件夹已有持久项目组,且可通过渲染层侧边栏偏好归档。
- 步骤:
- 归档项目,确认它从默认侧边栏隐藏,但仍可在“设置”→“项目归档”中找到。
- 点击项目归档页中的“打开”。
- 再次归档,打开“添加项目”,选择同一目录并创建。
- 再次归档,从项目归档页打开其会话。
- 预期:每次激活成功后都会清除该路径的归档展示状态、返回聊天并使项目重新显示在侧栏。仅选中详情行而不激活、普通会话刷新都不会更改归档状态。重新选择同一目录会复用现有项目组,不会复制项目或会话数据。
- 关联规格:
04-ux/06-settings-ia.md、04-ux/09-interaction-patterns.md、ADR 0294、D659、issue #1653。 - 验收:仅在 Host 项目激活成功后,渲染层才恢复该规范化路径的本地归档元数据。
- 状态:
apps/desktop/test/app-store-sidebar.test.mjs与apps/desktop/test/project-create-dialog.test.mjs有源码契约覆盖;完整 UI 场景仍为草稿,仅在明确请求且环境可用时运行。
E2E-266:项目折叠时标题显示运行活动
- 前提条件:至少两个带会话的本地项目组,其中一个有运行中的回合(也可另有等待权限的会话);另有一个已连接且存在运行会话的远程主机项目组。设置 → 外观 → 侧边栏使用默认值。
- 步骤:
- 在一个项目中启动回合并折叠该项目组。
- 检查折叠标题,再展开项目组。
- 在另一个项目中启动并行回合,并在远程主机项目中启动回合。
- 打开设置 → 外观 → 侧边栏,关闭“在项目上显示运行状态”,再检查侧边栏。
- 重新开启总开关,并开启“活动流光”。
- 只关闭“数量徽标”并检查效果,然后重新开启徽标。
- 只关闭“活动流光”并检查效果,然后重新开启活动流光。
- 开启操作系统的“减少动态效果”并检查活动流光。
- 预期:默认时有运行活动的项目标题只显示数量徽标,活动流光关闭;运行中与等待权限的数量分开呈现。远程主机项目也遵循相同默认值。启用活动流光后,它与徽标同时显示,但计数仍只出现在徽标里;关闭数量徽标后计数改由流光承担,因此同一组不会把同一个数字显示两遍。关闭总开关会隐藏两种提示,子开关置灰但保留已存值;重新开启后恢复此前选择。每个子开关只隐藏自身提示。减少动态效果时两种提示仍可见,但不播放动画。设置在渲染进程重载后保留。
- 关联规格:
04-ux/01-ui-ia.md§3.2、04-ux/06-settings-ia.md(常规 → 侧边栏卡片)。 - 验收:项目折叠时仍能回答“这里是否有正在运行的工作”,且两种提示可从持久化设置独立控制。
- 状态:草稿。现有
apps/desktop/test/sidebar-project-runs.test.mjs覆盖数量分类、总开关与子开关门控、两种渲染入口,以及主题令牌和减少动态效果保证;它不覆盖设置 API 缺省值规范化或完整交互,相关场景仍需在可用环境手动验证。
E2E-FAST-MODE:/fast 只在允许的模型上请求优先档位
- 前置条件:一个 ChatGPT 订阅(Codex)账号和一个 Responses API key 提供商,各配置两个模型。 自动化路径不需要真实提供商;手动路径使用可捕获请求的本地端点或用户授权的账号。
- 步骤:
- 在「设置 → 模型」中为每个提供商的一个模型勾选 Fast 模式;确认 Chat Completions 提供商上该勾选框不可用。
- 在允许的 Codex 模型上新建任务,输入
/fast,发送一条提示。 - 再次发送
/fast,然后再发送一条提示。 - 把会话切换到未允许的模型,发送
/fast。 - 在 Fast 开启时 fork 会话;重启应用后重新打开两个会话。
- 预期:第 1 步只在 binding 上保存
fastModeAllowed。第 2 步模型芯片显示 Fast 标识,请求体带有service_tier: "priority";Responses API key 模型改为提示按 token 计费的价格。第 3 步清除标识和该字段。 第 4 步给出模型设置提示并拒绝,不保存任何状态。第 5 步 fork 和重启后开关保持开启。子代理和压缩请求 从不携带该字段。远程会话拒绝/fast。可信扩展把会话切到它自己的 Responses 模型后,下一条请求不携带该字段。 - 关联规格:
03-runtime/11-provider-model-system.md、03-runtime/04-data-storage.md(v27)、04-ux/04-builtin-commands.md、ADR 0334、issue #1657。 - 状态:自动化覆盖见英文版。完整的设置 →
/fast→ 捕获请求路径为手动验证场景。
E2E-REMOTE-FILE-MANAGER:受限的插件远程读取
运行 node scripts/e2e-remote-file-manager.mjs,验证实际内置的 apps/desktop/resources/plugins/pi.file-manager。这是发布验收路径:单独的开发插件 通过不代表内置插件已修复。脚本会输出实际加载的目录。 可选参数 <built-file-manager-directory> 可选择配套插件重新构建的 views/ 或官方 PluginPack 产物的解包目录,仅用于开发验证。验收 #1711 前需更新内置正式版本、 上游标签与提交及各文件校验值,再对更新后的 PR 集成候选运行默认路径。
开发集成可在实际内置目录验证 Desktop 热修复,并在 UPSTREAM.md 中如实记录固定 源码提交、本地补丁和产物校验值。热修复路径通过,不代表配套上游正式发布或维护者 最终验收已经完成。 PI_FILE_MANAGER_QA_DIR 可指向已有、提供 Playwright 的插件 tests/electron 环境; PI_ELECTRON_BINARY 选择兼容的已有 Electron;PI_E2E_ARTIFACTS 可保留截图。
隔离的真实插件进程与生产 file:// 视图经过 Desktop SDK 网关、持久绑定读取器、 本机回环 RACP 传输和真实 Host 文件服务。仅项目记录查询和认证启动由夹具提供。 远程夹具根会显式注册在 Host 文件夹选择器浏览根之外。检查嵌套目录、文本/图片、 凭据隐藏、编辑器/保存/菜单只读、断线错误和刷新恢复, 以及切换项目后拒绝延迟预览和祖先打开请求,并恢复本地编辑、Browser 和编辑器探测。 不使用模型、用户 profile 或外部 SSH;真实 SSH 及 Windows/Linux 原生行为需另行平台验收。
E2E-PROVIDER-reasoning-effort-override: Preserve an explicit gateway reasoning parameter
- Setup: A custom Chat Completions provider with a DeepSeek-family model; start with a legacy binding that omits
supportsReasoningEffort. - Steps: In English, Chinese and German at 850/1000/1400px widths, open model settings, confirm Auto, select Send, save and reopen; repeat for Do not send and Auto. Switch to Responses and Anthropic and confirm the Completions-only control is hidden.
- Expected: Each saved binding retains true, false, or null respectively. With High selected, the real Completions adapter sends
reasoning_effort: highonly when enabled; Auto keeps the existing DeepSeek compatibility default. Responses continues using its own reasoning contract. Provider creation, update/read, copying, and sync preserve the explicit override. - Automation:
node scripts/e2e-provider-api-style.mjs --reasoning-effortexercises real React/Chromium controls and captures the Host API payload (the IPC boundary is stubbed). Rust provider tests independently verify real SQLite persistence;provider-reasoning-effort.test.tscaptures requests from the real pi-ai adapter with fixture SSE responses. No live gateway credentials are required.
E2E-RUNTIME-malformed-tool-call-continue-recovery: Continue after malformed tool output
- Preconditions: An isolated sidecar and loopback Responses fixture can emit an empty tool name, then a corrected Read call. Unit fixtures also cover missing names/IDs, empty Responses wire IDs, non-object arguments, retained malformed context, compaction tails and resumed delegates.
- Steps: Return one malformed response, then invoke the same
agent.promptentry used by Continue three times. Repeat with three consecutive malformed responses followed by a corrected response. Keep the runtime alive throughout. - Expected: Each malformed batch settles with one local
context-validationerror before tool execution, with no transport retry. All three later prompts complete; the corrected Read executes once, and the runtime is reused rather than restarted. Model context retains valid history and system updates while excluding malformed calls and associated results. Optional usage estimation does not lose terminal tool events. The durable transcript and Host permissions are unchanged; unrelated malformed hosted search still fails strict validation. - Specs linked:
03-runtime/02-agent-runtime.md§5d. - Acceptance: Conversation, runtime recovery and quality.
- Milestone: M5 hardening.
- Automation:
pnpm test:e2e:hosted-searchruns the production sidecar and real Responses adapter against synthetic SSE, including one-error and three-error recovery cases.malformed-tool-recovery.test.ts,tool-call-identity.test.tsandsubagent.test.tscover retained state, event/result agreement, batch rejection and delegate restoration. - Status: Automated offline; native UI clicking and live providers are outside this fixture's coverage.
电脑操作(ADR 0342、ADR 0344)
原生应用操作仍以 03-runtime/computer-use 为落地闸门。宿主一侧现已有 macOS 真机证据,来自 PI_DESKTOP_COMPUTER_USE_DEVICE=1 node scripts/e2e-computer-use-device.mjs (需显式开启:它会读取操作者的屏幕,带 --act-app 时还会往其中一个窗口里输入)。 Electron 一侧——工具自动签发、指示器、"停止"中止轮次、绘制光标、对真实模型的回路—— 仍为 NOT RUN,需要屏幕录制权限的部分同样未跑。下述权限申请界面已有隔离 fixture 自动化。
E2E-COMPUTER-USE-request-accessibility-from-settings
- 前置条件:隔离 Electron profile;在 IPC 与 Electron 边界模拟系统调用;功能关闭,不调用 Provider 或真实系统权限提示。
- 步骤:打开设置,键盘聚焦并连续点击未授权状态;完成申请但保持未授权。在外部开启权限后返回应用。覆盖权限拒绝、申请失败及重试、过期查询、申请中返回焦点、不支持平台与申请中卸载页面。
- 预期:打开设置或开启功能不会申请系统权限。只有显式点击才在 Main 申请主应用 TCC 权限,后端 API 保持独立。等待中禁用按钮并显示忙碌,重复点击不重复申请。未授权时仅打开固定的 macOS 辅助功能设置页,并提示用户开启权限。错误可见且可重试。焦点/可见性恢复刷新状态;申请后每秒轮询一次,最多 60 次计时,不再次提示。主应用授权后停止轮询并显示已授权;即使子进程仍未授权,也不会继续要求重复授权,而是单独说明后端权限异常。后端恢复后清除异常。过期结果和卸载后的监听、计时器不得更新界面。已授权或不支持状态仅显示标签。非主窗口来源在系统调用前被拒绝,系统设置打开失败传回;不会交接窗口、截屏或派发输入。
- 自动化:
node --test apps/desktop/test/computer-use-permission-user-path.test.mjs apps/desktop/test/computer-use-permission-ipc.test.mjs;shared 能力筛选测试与 host-core 不支持后端测试覆盖平台门禁。 - 规格:Computer Use 的设置与 UX 界面。
- 状态:fixture 自动化 PASS;真实 macOS TCC 提示/开关 NOT RUN。系统权限仍由用户手动确认。
E2E-COMPUTER-USE-desktop-grant-is-automatic-and-stoppable
- 前置条件:隔离的桌面 profile,电脑操作已开启且已绑定模型,被测平台的操作系统 权限已授予,两个 fixture 目标应用,最前面是一个 PI-Desktop 窗口,无需 Provider 账号。
- 步骤:在没有任何授权时请求 Agent 操作某个应用。观察指示器,执行
inspect与 一次act。任务进行中按下"停止",并让 Agent 立刻再次调用Computer;等冷却结束后 再调用一次。在第一个授权仍活动时从另一个会话发起请求。让授权空闲超过过期时间,并 耗尽动作预算。随后重启应用。 - 预期:不出现挑选器,也从不询问用户选哪个窗口:首次调用即签发桌面授权并绑定到 最前面那个非 PI-Desktop 的窗口,不触发操作系统提示,
a11y-only下也不截取画面。 指示器标明该窗口、剩余预算与截取模式,随每次改绑更新,应用退到后台时仍可见。 "停止"撤销授权并中止持有它的轮次,紧随其后的再次调用以COMPUTER_STOPPED_BY_USER失败且不触碰屏幕;冷却结束后可正常签发新授权。第二个 会话以COMPUTER_GRANT_IN_USE被拒。空闲过期与预算耗尽各以自己的错误码关闭授权, 且没有授权能活过重启。 - 规格:电脑操作、 03-tools-and-permissions。
- 自动化:host-core 的
computer::测试、apps/desktop/test/computer-use-service.test.mjs;真机用scripts/e2e-computer-use-device.mjs。 - 状态:PARTIAL —— 真机(macOS 15,台前调度开启)已确认:自动桌面授权绑定最前 可用窗口(我们自己的窗口就在它前面且被拒绝)、设置默认值进入授权、真实无障碍树、 以及"停止"冷却。指示器、"停止"中止轮次、运行中的预算与空闲关闭、重启场景仍 NOT RUN。
E2E-COMPUTER-USE-window-switching-stays-inside-the-refusals
- 前置条件:隔离的桌面 profile 且已有活动桌面授权,两个 fixture 应用,一个 PI-Desktop 窗口,一个内置凭据拒绝名单上的窗口,以及一个可按需关闭的 fixture 窗口。
- 步骤:让 Agent 读取窗口列表,切换到第二个 fixture 应用并检查、动作。尝试切换到 PI-Desktop 窗口、拒绝名单窗口以及一个已不存在的 id。用切换前读到的
uid动作。 任务进行中关闭当前绑定窗口再动作。 - 预期:观察中列出宿主枚举到的窗口并附带拒绝原因,Renderer 侧没有任何界面能枚举 窗口。允许的切换改绑授权、推进 revision、重新框住绘制光标,消耗一个步数但不消耗 动作预算。被拒绝的切换分别以
COMPUTER_SELF_TARGET_REFUSED、COMPUTER_TARGET_DENYLISTED与COMPUTER_SCOPE_VIOLATION失败,绑定窗口保持不变, 并作为一步回报给模型而不是结束运行。切换前的uid以COMPUTER_STALE_REVISION失败。绑定窗口被关闭时回落到最前可用窗口,同时在途动作被拒绝;审计日志中每次移动 记录一条仅含应用身份的computer.grant.rebound。 - 规格:电脑操作、 05-security/01-security §13。
- 自动化:host-core 的
computer::改绑测试、packages/computer-use的切换测试; 真机用scripts/e2e-computer-use-device.mjs。 - 状态:PARTIAL —— 真机上在六个真实应用之间切换成功,每次绑定都推进 revision, 切换前的 revision 被拒绝,自我目标/拒绝名单/未知窗口三种绑定均被按码拒绝。真机还 发现并修掉两个缺陷:台前调度下无障碍窗口无法与窗口服务器窗口对应(改为按窗口 id 对应,D683),以及读取失败时授权会留在读不了的窗口上。关闭绑定窗口的回落、光标重新 框住、审计回读仍 NOT RUN。
E2E-COMPUTER-USE-self-target-and-secure-input-refused
- 前置条件:隔离的桌面 profile,已有活动授权,一个带待处理审批卡片的 PI-Desktop 窗口,以及一个包含密码字段的 fixture 窗口。
- 步骤:在包括
auto的每种权限模式下、以及在autonomous-in-scope授权下, 尝试对 PI-Desktop 窗口(含审批卡片与实时语音悬浮件)做检查、截取与动作。让密码 字段获得焦点,尝试inspect、screenshot与set_value。尝试一个位于内置凭据 拒绝名单上的目标,并尝试移除该条目。 - 预期:每次自我目标尝试都以
COMPUTER_SELF_TARGET_REFUSED失败;没有任何 合成事件到达任何 PI-Desktop 窗口,也没有审批卡片被关闭。安全输入以COMPUTER_SECURE_INPUT_REFUSED拒绝截取与合成,字段内容不出现在快照、图像、 工具结果或日志中。内置拒绝名单条目无法移除。 - 规格:电脑操作、 05-security/01-security §13。
- 状态:PARTIAL —— 真机上一个 PI-Desktop 窗口位于所有窗口最前,无论作为自动授权 的候选还是显式绑定,都以
COMPUTER_SELF_TARGET_REFUSED被拒;拒绝名单窗口以COMPUTER_TARGET_DENYLISTED被拒。审批卡片本身、安全输入与密码字段拒绝仍 NOT RUN。
E2E-COMPUTER-USE-stale-basis-and-revision-fail-closed
- 前置条件:隔离的桌面 profile,一个
a11y-and-pixels授权,以及一个可按需 移动、改变尺寸与改变层叠顺序的 fixture 窗口。 - 步骤:先
screenshot,移动目标,再基于过期basis动作。在对某个uid的inspect与act之间改变目标尺寸。用超出测得边界的坐标动作。用另一个授权签发的basis动作。在a11y-only授权下用坐标动作。 - 预期:分别返回
COMPUTER_STALE_BASIS、COMPUTER_STALE_REVISION、COMPUTER_SCOPE_VIOLATION与COMPUTER_CAPTURE_UNAVAILABLE;其中任何一种都不 派发事件,不静默夹取,目标应用观察不到任何输入。 - 规格:电脑操作、 08-error-codes §3.10。
- 状态:NOT RUN —— 需要真机运行。
E2E-COMPUTER-USE-voice-request-adds-no-authority-of-its-own
- 前置条件:隔离的桌面 profile,带绑定工作会话的实时语音 fixture 适配器,无真实 Provider 账号与麦克风。分别在电脑操作关闭与开启两种状态下各跑一次。
- 步骤:在功能关闭与开启状态下,各口述一条要求 Agent 操作某个应用的请求。在会话 处于 Plan 与 Goal 时分别重复。在一次语音运行中按下"停止",并立刻重复该口述请求。
- 预期:功能关闭时请求以
COMPUTER_DISABLED失败;语音不能开启该功能,也不发生 截取或合成。功能开启时,该请求作为普通 Agent 轮次在会话策略下运行,并拿到与键入 路径相同的自动桌面授权(ADR 0344)——语音不带来任何额外权限,也不跳过任何拒绝—— 进度通过现有工作反馈通道朗读。Plan 与 Goal 在权限评估之前拒绝该工具。冷却期内的 语音重试以COMPUTER_STOPPED_BY_USER失败。 - 规格:电脑操作、 live-voice、 live-work-session。
- 状态:NOT RUN —— 需要真机运行。
E2E-COMPUTER-USE-platform-support-is-explicit
- 前置条件:每个受支持平台各跑一次,另加一个 Wayland 会话,以及一台操作系统 权限被拒绝的机器。
- 步骤:在每个平台上开启该功能并打开体检视图。让 Agent 调用
Computer以签发 授权并动作。在 Windows 上绑定由更高完整性级别进程拥有的窗口。在 Wayland 上先尝试输入再尝试截取。在屏幕 录制或无障碍被拒绝的平台上尝试检查与截取。 - 预期:体检视图区分"未申请"、"已拒绝"与"本平台不支持"。Wayland 上的输入以
COMPUTER_PLATFORM_UNSUPPORTED失败而门户截取仍可用;提权的 Windows 目标以COMPUTER_TARGET_ELEVATED失败而不是丢弃事件;被拒绝的操作系统权限以COMPUTER_OS_PERMISSION_MISSING失败。没有路径挂起,也没有路径在未动作的情况下 回报成功。 - 规格:电脑操作。
- 状态:NOT RUN —— 需要真机运行。
E2E-TOOLS-session-background-jobs
拆分修复回归:TaskStop 在能力探测时取消不派发停止请求,派发后取消不重复停止,迟到的 RPC 错误仍被观察。
拆分修复回归:命令摘要保留 UTF-8 前缀并独立标记截断;兼容没有标记的旧快照,错误输出仍保留末尾。
构建 shared、agent-runtime、host-runtime 与 Host 后,运行
PI_DESKTOP_HOST_BIN=<candidate-host> node scripts/e2e-session-background-jobs.mjs。使用隔离 Host 数据、工作目录、生产 runtime/pi 循环与本地 HTTP 模型夹具。
后台启动测试,列举并等待失败结果;启动常驻命令,等待超时、无参停止,再显式停止并等待。
验证退出码、两路末尾输出、UTF-8 上限、跨会话列表/读/停隔离、超时和无参停止保留 Bash、显式停止终态、重复查询及桌面输出一致;拒绝 Plan 模式。
macOS 上,停止信号成功且子进程退出后,清理被拒绝但随后确认进程组消失,应得到 stopped;持续拒绝或进程组仍存在时,应保留清理失败。
runtime 测试补充新输入和父回合取消、延迟 RPC 错误、旧 Host/远程能力缺失、混合选择器拒绝、原委派控制及无子代理配置的工具注册。
Host 测试覆盖先完成后等待、重复读取、Unicode 截断、归属查找、幂等停止与停止请求待确认。Windows 子孙进程清理测试仍须执行,原生失败单独报告。
在子进程已退出、监管任务尚未排空管道时请求停止,检查原退出结果、最终输出和重复读取的耗时。延迟能力探测或第一次 Host 观察,检查等待总时限。Electron 会话记录用例渲染尚待确认的 Bash 停止请求和混合作业列表。
E2E-ISSUE-1953
MCP All 在创建、导入、安装时使用所选项目,无项目才回退全局;显式筛选保留归属,编辑器固定项目。验证不同筛选、创建、市场安装和缺少项目拒绝写入。
在 A 打开创建或编辑窗口,切换页面至 B 并等待列表加载。A/B 同名不同 ID 不阻止保存到 A;A 真实重复名称或 ID 仍阻止创建,真实重复名称仍阻止编辑。
E2E-ISSUE-1789
生图标记说明聊天和默认菜单移除效果,并区分图片生成与输入;折叠行保留生图标记。通过真实组件验证标记、帮助、折叠、取消及恢复聊天资格。
E2E-ISSUE-2162
关闭已连接浏览器标签时清除连接;会话级工具使用同一会话选中的替代页,显式已关闭目标仍失败,导航不复活不可见旧标签。验证关闭、重开、snapshot、fill、click、navigate 和最终关闭。
E2E-ISSUE-2163
末尾分页包含最后一条消息之后追加的检查点,中间页仍只读取生效及页内记录。验证消息、压缩追加后读取及 Host 重启,最新检查点与上下文估算保留,历史用量不变。
E2E-ISSUE-2164
压缩估算作为占用时使用检查点模型容量,旧检查点使用当前会话完整绑定;历史请求用量不变,未知容量标记估算。验证 20,619 / 1,000,000 为 2%、未知路由、旧记录及新请求恢复。
E2E-ISSUE-2086-fork-selection
父会话重新读取时创建侧边栏分支,完成后子会话立即激活且旧选择清除;若被新导航替代,只列出分支并保留新选择。覆盖原运行中父会话与失败路径。
User-defined mode workflow acceptance
Candidate command: PI_DESKTOP_HOST_BIN=<candidate-host> node scripts/e2e-mode-definitions.mjs with PI_SCRATCH_DIR set to an isolated test directory. No model API is called.
- Configure a session with a two-stage definition snapshot. Start a durable turn and claim the first contract stage.
- Request completion without its required artifact; observe a failed gate.
- Supply the artifact through stage completion; verify its exact disk content and the waiting-for-approval state.
- Settle the first turn, approve with the current revision and claim the next stage with approval provenance.
- Submit the stage's command gate. A changed command must fail with
MODE_GATE_MISMATCHbefore execution. - Execute the exact command through
tools.execute; consume the Host-recorded result and observe finished, no open run, and session mode Agent.
Companion tests: mode_runs::* and rpc::modes_rpc::tests::* cover migration, recovery, CAS, budget and permission caps; desktop mode-definitions-user-path, mode-definitions-renderer and mode-runs-main cover source editing, selection, run controls and Main dispatch. Runtime modes/* covers stage completion and compaction injection.
Goal run controls with a live model (opt-in, paid)
Command: PI_LIVE_BASE_URL=<url> PI_LIVE_MODEL=<model> PI_LIVE_API_KEY=<key> PI_DESKTOP_HOST_BIN=<candidate-host> node scripts/e2e-goal-live.mjs with PI_SCRATCH_DIR set (PI_LIVE_SECRETS_DIR + PI_LIVE_SECRET_REF may replace the key; only the machine key and that one secret file are copied). It calls a paid model API and needs the account owner's authorization. Real pieces: an isolated Host, the production runtime loop and Electron main's run coordinator.
- With
goalBudget { maxTurns: 6 }set, a Goal run starts with 6 turns, does the work, callsCompleteStagewith the three-heading artifact, finishes, and the session returns to Agent. - During the first turn, Edit the objective and budget, then Pause: the turn receives the closeout notice and ends within 3 more model requests; the run stays paused and no turn starts. Resume starts an approval turn with the edited objective, which the run meets before it finishes.
- During a turn, Remove: the turn is not interrupted, finishes in Agent without
CompleteStage, no Host-started turn follows, and no open run is left.
E2E-ISSUE-2173: Redundant delegation job selectors
- Entry: build shared/agent-runtime/host-runtime and Host, then run
PI_DESKTOP_HOST_BIN=<candidate-host> node scripts/e2e-task-job-selectors.mjs. - Preconditions: isolated Host data/workspace and a deterministic localhost provider; production parent and delegate loops, with no external model account.
- Steps: start a delegate; wait with both its job reference and matching delegationIds; re-read with empty delegationIds and single-job mode/minCompleted; start another delegate, hold its provider response, stop it with both selectors, and confirm its stopped state through TaskWait.
- Expected: reports remain readable, redundant selectors do not cause errors, single-job mode/minCompleted are ignored, and cancellation settles the target. Unit tests also reject conflicting/malformed IDs and mixed Bash selectors.
- Specs:
03-runtime/02-agent-runtime.md, Session job controls. - Acceptance: wait, report re-read and running-delegate cancellation complete through the real tool loop. Milestone: maintenance. Status: automated.
E2E-ISSUE-1707
生产 SSH 引导使用合成 Darwin 目标,按 IPC 形态传递错误并解析界面提示:显示 Linux 安装包范围,不下载或上传。Linux 及其他安装失败提示保持原行为。
E2E-ISSUE-2184
隔离项目加载真实内置插件,创建文件和中文目录,重命名并移动文件,核查磁盘与返回元数据;非法名称及重复目标拒绝。覆盖 file-manager-mutations.test.mjs 和原有 Electron 目录定位路径。
E2E-NATIVE-workspace-file-routing
运行 node scripts/e2e-native-workspace-files.mjs:使用隔离 Electron Main、 生产 workspace IPC、合成 sidecar 会话元数据和真实测试文件。选中无关的 Desktop 工作区后,列出、读取和解析 native 文件,再拒绝不存在的 native 会话。 断言 native 会话及 scratch 请求不会进入 Desktop 存储。 native-workspace-files.test.mjs 另验证 sidecar 更换后拒绝旧响应。 不执行模型或访问用户 native 会话。
E2E-custom-mode-prompt-admission
Select a simple custom mode with a distinctive prompt and contract policy, send a message, and refresh planning state. The runtime receives the selected definition and the session retains its mode. Remove the definition before another send: reject without persisting a user message or invoking the sidecar; the admission turn is closed. For a staged run, edit the catalog into a simple mode and then remove it; subsequent dispatch retains the run snapshot in both cases. Controlled coverage: apps/desktop/test/custom-mode-dispatch.test.mjs. Native Electron/provider acceptance is separate from these stubbed Host/sidecar boundary checks. Also create directly in Goal or a staged custom mode without switching modes: the first send starts a run. mode-session-create.test.mjs checks the production creation registrar validates input and sends its snapshot in one creation RPC. Host RPC tests cover snapshot/policy persistence, invalid snapshots and rollback after an injected session-insert failure. Approve a legacy Goal proposal: live events and pending-proposal restoration show Agent. Inactive polling with no approval preserves staged Goal/custom modes, including after older Goal history. Coverage: mode-planning-approval.test.mjs, session_mode_create_tests.rs. scripts/e2e-mode-definitions.mjs also exercises production prompt IPC and the coordinator against an isolated real Host, including edit/removal, durable claim settlement and unavailable-mode cleanup. The sidecar boundary is synthetic; this does not qualify native Electron interaction or live-provider execution.
升级仍带有 sessions.mode 内置模式枚举 CHECK 的 schema 28/29/30/31 测试库, 在已有会话切换自定义模式,并直接用自定义模式创建会话。两条路径均保存定义并 保留工具策略;内置模式切换和快照不匹配拒绝仍正常,重开后保留模式选择。 保留原数据、子记录、索引、触发器和视图;替换或外键检查失败时回滚并保留升级前 备份。覆盖:rpc/session_mode_migration_tests.rs、db/session_mode_migration_tests.rs。 这是隔离的真实 SQLite/Host RPC 验证,不代表安装版界面或真实供应商验收。
E2E-ISSUE-1926-scratch-prefix
创建配置相同但 scratch 不同的真实 runtime,基础提示段相同且尾部各保留自身路径;验证 shell 语法和自定义提示兼容,不调用真实供应商。