chore: land tree view-state, vault, Pi module split, and repo hygiene

Persist PageTree expand state via control-plane view-state and align
chevron/DOM with restored expansion; keep Sidex-style shallow page-tree
scan and drop the unused recursive scanner that only added cargo noise.

Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi
into a module package, and retire Hermes/ACP/OpenHub recycle + root
harness evidence from the index while gitignoring recycle and local
diag dumps.

Archive superseded design/bugs docs under old/, point architecture at
ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor
regressions so the working tree can stay clean.
This commit is contained in:
Agent Board
2026-07-21 05:13:05 +08:00
parent 6f9c7d3b58
commit b798f628ee
264 changed files with 17480 additions and 17314 deletions
@@ -0,0 +1,49 @@
# [recycle] 3-19 [done][bug] Reasonix ACP wrapper 调 mnote tool 缺少身份与幂等字段 v1
> 发现时间:2026-05-17
>
> 状态:`[done]`
>
> 关联主线:`03-rust-web`
## 1. 问题定义
Reasonix ACP wrapper 调用 mnote tool 时只发送 `toolName``args``workspaceId`,没有传递 `actorId``sessionId``runId``toolCallId``traceId``dryRun``idempotencyKey` 等工具合同字段。
## 2. 证据
- [reasonix-acp-wrapper.mjs](/mnt/Data1T/mnote/scripts/reasonix-acp-wrapper.mjs:230) 附近构造 `callMnoteTool` 请求体。
## 3. 影响
- Rust tool executor 难以稳定进行鉴权、审计、追踪和幂等。
- 写工具可能被拒绝,或在缺少幂等键时产生重复写入风险。
- Reasonix 与 Hermes 对同一 tool 的调用合同不一致。
## 4. 建议修复
- wrapper 必须从 ACP run/session 中透传 actor、session、run、toolCall、trace、dryRun、idempotency 字段。
- 写工具缺失必要字段时应 fail fast。
- 增加 Reasonix 端到端 smoke,断言请求字段完整。
## 5. 修复
已修复:
- [reasonix-acp-wrapper.mjs](/mnt/Data1T/mnote/scripts/reasonix-acp-wrapper.mjs:34) 新增 `buildMnoteToolPayload`,统一构造 mnote tool 请求体。
- [reasonix-acp-wrapper.mjs](/mnt/Data1T/mnote/scripts/reasonix-acp-wrapper.mjs:43) tool payload 现在包含 `actorId``sessionId``runId``toolCallId``traceId``dryRun``idempotencyKey``workspaceId``documentId`
- [reasonix-acp-wrapper.mjs](/mnt/Data1T/mnote/scripts/reasonix-acp-wrapper.mjs:303) HTTP 请求头同步携带 `x-mnote-actor-id``x-mnote-workspace-id`
- [reasonix-acp-wrapper.mjs](/mnt/Data1T/mnote/scripts/reasonix-acp-wrapper.mjs:379) ACP `session/prompt` 期间通过 `AsyncLocalStorage` 保存 mnote tool contexttool 调用继承 run/session/actor/trace。
- [acp_types.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/acp_types.rs:163) `SessionPromptParams` 扩展可选 mnote 上下文字段。
- [acp_session_manager.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/acp_session_manager.rs:193) 增加 `run_prompt_with_mnote_context`
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:754) ACP Reasonix prompt 启动时透传 mnote session/run/actor/trace/workspace/document 上下文。
## 6. 验证
```bash
node --check scripts/reasonix-acp-wrapper.mjs
MNOTE_REASONIX_ACP_SELFTEST=1 node scripts/reasonix-acp-wrapper.mjs
cargo test --manifest-path rust/Cargo.toml -p mnote-web acp_session_manager -- --nocapture
```
结果:JS 语法检查通过,wrapper selftest 通过,ACP session manager 5 个测试通过。
@@ -0,0 +1,40 @@
# [recycle] 3-20 [done][bug] ACP incoming request 只记录日志不响应 v1
> 发现时间:2026-05-17
>
> 状态:`[done]`
>
> 关联主线:`03-rust-web`
## 1. 问题定义
ACP client 收到 `session/request_permission` 等 incoming request 时当前只记录日志,没有返回协议响应。
## 2. 证据
- [acp_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/acp_client.rs:347) 附近处理 incoming request。
## 3. 影响
- ACP agent 侧可能等待到超时。
- 权限请求无法进入用户确认或自动拒绝流程。
- run 状态可能卡在进行中,SSE 输出也无法表达真实阻塞原因。
## 4. 建议修复
- 为已知 request type 实现明确响应:允许、拒绝或要求人工确认。
- 未支持的 request type 也应返回结构化 error,而不是只日志。
- 增加 ACP request/response 协议测试。
## 5. 修复
- [acp_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/acp_client.rs:157) 将 ACP 子进程 stdin writer 作为共享 `Arc<Mutex<BufWriter<_>>>` 传入后台 reader loop。
- [acp_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/acp_client.rs:335) 对带 `id + method` 的 incoming request 写回 JSON-RPC error 响应,当前无权限确认 UI 时明确拒绝,避免 agent 等待超时。
- [acp_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/acp_client.rs:551) 增加 mock ACP server 测试,覆盖 `session/request_permission` 主动请求必须收到响应。
## 6. 验证
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web test_incoming_permission_request_gets_response -- --nocapture`
- 结果:1 passed
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web acp_client -- --nocapture`
- 结果:6 passed
@@ -0,0 +1,38 @@
# [recycle] 3-21 [done][bug] ACP run payload 被第一次 stream_events 消费后移除 v1
> 发现时间:2026-05-17
>
> 状态:`[done]`
>
> 关联主线:`03-rust-web`
## 1. 问题定义
ACP run 的 payload 在 `stream_events` 路径被消费后从内存表移除。刷新页面、多个 SSE 订阅、断线重连时,后续订阅可能无法再取到同一个 run 的 payload。
## 2. 证据
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:789) 进入 `stream_events`
- ACP 分支中存在 `ACP_RUN_PAYLOADS.remove` 语义,payload 生命周期与第一次 stream 订阅绑定。
## 3. 影响
- 浏览器刷新或 SSE 重连可能导致同一 run 无法恢复。
- 多个客户端观察同一 run 时只有第一个订阅者成功。
- abort / retry / reconnect 语义不可靠。
## 4. 建议修复
- payload 应有 run 级持久生命周期,至少保留到 run completed / failed / aborted 后短 TTL。
- stream subscription 不应拥有 payload 的删除权。
- 增加 SSE 断线重连与重复订阅测试。
## 5. 修复
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:2051) `acp_run_payload_for_stream` 使用 `ACP_RUN_PAYLOADS.get(run_id).cloned()`stream subscription 只读取 payload,不再拥有删除权。
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:4116) 增加重复读取测试,锁定刷新、重连、多个观察者不会因为第一次读取而丢失 run payload。
## 6. 验证
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_client_acp_stream_payload_lookup_keeps_payload_for_reconnect -- --nocapture`
- 结果:1 passed
@@ -0,0 +1,50 @@
# [recycle] 3-22 [done][bug] ACP permission 自动拒绝后 UI 仍展示审批动作
> 发现时间:2026-05-18
>
> 状态:`[done]`
>
> 关联主线:`03-rust-web` / ACP runtime 与页面 AI 事件映射
## 1. 问题定义
当前 ACP client 收到 `session/request_permission` 后会立即返回 JSON-RPC error,并把事件转换为 `permission.denied`。这是为了避免 agent 等待权限响应直到超时。
但页面 AI UI 仍在权限卡片和权限弹窗里展示“允许 / 拒绝”按钮。即使这些按钮在 `permission.denied` 下会被禁用,用户仍会看到审批入口,误以为可以把真实协议结果改成允许。
## 2. 证据
- [acp_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/acp_client.rs:350) 对 incoming `id + method` request 直接返回 JSON-RPC error。
- [acp_session_manager.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/acp_session_manager.rs:121) 对 `session/request_permission` 生成 `decision="denied"`
- [acp_bridge.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/acp_bridge.rs:237) 将 decision 映射为 `permission.denied`
- [layout.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/ssr/pages/layout.rs:4060) 权限弹窗仍渲染“允许 / 拒绝”按钮。
- [layout.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/ssr/pages/layout.rs:5805) 权限消息卡片仍渲染“允许 / 拒绝”按钮。
## 3. 影响
- 用户看到的“允许”动作与 ACP wire contract 不一致。
- 点击动作只修改本地 UI 状态,不会回写 ACP JSON-RPC response。
- 对已经自动拒绝的权限请求展示审批按钮,会掩盖真实失败原因。
## 4. 修复
- [acp_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/acp_client.rs:350) 维持 `session/request_permission` 自动拒绝协议响应,避免 agent 等待超时。
- [layout.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/ssr/pages/layout.rs:4003) `pageAiApplyPermissionEvent()``permission.denied` / `permission.allowed` 只保留结果态,不再展示可点击审批弹窗。
- [layout.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/ssr/pages/layout.rs:4048) `pageAiShowPermissionDialog()` 对已决权限事件直接隐藏对话框。
- [layout.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/ssr/pages/layout.rs:5797) 权限消息卡片对已决事件只展示结果,不再渲染审批按钮。
## 5. 验证
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_uses_backend_acp_session_runtime_store -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web acp_permission_request_emits_frontend_decision_event -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web ssr::pages::layout::tests -- --nocapture
cargo fmt --manifest-path rust/Cargo.toml --all -- --check
git diff --check -- rust/crates/mnote-web/src/ssr/pages/layout.rs bugs/03-rust-web/done/3-22-acp-permission-auto-deny-ui-shows-approval-actions-v1.md
```
结果:
- `page_ai_uses_backend_acp_session_runtime_store``1 passed`
- `acp_permission_request_emits_frontend_decision_event``1 passed`
- 版面相关静态测试继续通过,权限消息代码路径未破坏。
@@ -0,0 +1,109 @@
# [recycle] MNote 浏览器 QA 实测发现 2026-07-08
- **状态**`process`
- **来源**Agent Board Pi Taskflow 实测(MNote 浏览器 QA → Bug 文档 20260708-130503
- **复现环境**
- MNote Web`http://127.0.0.1:3000`Rust SSR + leptos-tiptap island
- 后端:control-plane libSQL local
- 浏览器:Playwright Chromium headlessviewport 1280×800
- 登录:测试账号 `mnote.e2e@example.com`authMode=controlPlaneSession
- 工作区:`/mnt/Data1T/Mnote_data/users/mnote-e2e/workspaces/my-space`
---
## 1. 总体状态
| 检查项 | 结果 | 详情 |
|--------|------|------|
| API Gateway /health | ✅ PASS | 200 |
| Auth 页面加载 | ✅ PASS | 518ms |
| 测试账号快速登录 | ✅ PASS | 跳转到 `/` |
| Workspace 壳渲染 | ✅ PASS | sidebar / topbar / shell 均可见 |
| Page AI / OpenHub 页面 | ✅ PASS | 130ms 加载,AI 输入框可见 |
| Console 错误 | ✅ PASS | 0 errors |
| 业务网络错误 | ✅ PASS | 0 real errorsSSE abort 为预期导航行为) |
---
## 2. 发现的问题
### 2.1 `file-order.json` 页面排序索引为空
- **严重程度**medium
- **实际结果**`/mnt/Data1T/Mnote_data/users/mnote-e2e/workspaces/my-space/.mnote/file-order.json` 内容为 `{"parents": {}, "version": 1}`,无任何页面条目。
- **预期结果**:工作区中存在有效页面目录(`新页面054900``新页面145827``新页面233155``有机合成中的保护基` 等),每个目录下均有 `.md` 正文文件,file-order.json 应包含这些页面的排序条目。
- **证据**
```bash
$ cat .mnote/file-order.json
{"parents": {}, "version": 1}
$ ls workspace/
新页面054900/ 新页面145827/ 新页面233155/ 有机合成中的保护基/
```
- **影响范围**:页面树(Page Tree)排序可能不正确,新建页面可能无法正确出现在侧边栏页面列表中。
- **建议修复方向**:检查页面创建/重命名/移动流程是否正确更新 `file-order.json`;检查 Pi lab 批量创建页面的流程是否绕过了正常的 metadata 写入路径。
### 2.2 `/api/tree/page-tree` 端点返回 404
- **严重程度**low
- **实际结果**`GET /api/tree/page-tree` 返回 HTTP 404。
- **预期结果**:应返回 200 或 401(需要认证),而非 404。
- **证据**
```bash
$ curl -s -w "\nHTTP: %{http_code}" http://127.0.0.1:3000/api/tree/page-tree
HTTP: 404
```
- **影响范围**:直接调用此 API 的外部客户端或测试脚本会失败。前端使用 `GET /api/tree/view-state?treeKind=pagetree`(需要认证,已正常工作),当前不受影响。
- **建议修复方向**:确认 `/api/tree/page-tree` 是否为已废弃路由;若是,应在路由注册时返回 410 Gone 或转发到 view-state;若不是,应修复路由注册。
### 2.3 侧边栏中无 `a[href*="/document/"]` 格式的文档链接
- **严重程度**:low(待确认是否为预期行为)
- **实际结果**Playwright selector `a[href*="/document/"]` 在侧边栏中找到 0 个元素。
- **预期结果**:侧边栏页面树中的页面应可点击跳转到文档页。
- **证据**QA 脚本 `page.locator('a[href*="/document/"]').count()` 返回 0。
- **影响范围**:如果页面确实使用不同 URL 模式(如 `/page/...` 或前端路由),则无影响;否则侧边栏页面无法点击跳转。
- **建议修复方向**:确认当前侧边栏页面树使用的链接格式;如果是前端 SPA 路由(如 `data-page-id` + click handler),则更新测试 selector;如果是 bug,修复链接生成。
### 2.4 Dev 模式 hot-reload 轮询累积
- **严重程度**low(仅 dev 模式)
- **实际结果**:多个 `GET /api/dev/hot-reload` 请求处于 pending 状态,随时间累积。
- **预期结果**hot-reload 连接应复用或及时关闭,不应无限累积。
- **证据**:网络追踪显示 20+ 个 pending 的 `GET /api/dev/hot-reload` 请求。
- **影响范围**:仅影响开发模式下的浏览器性能和内存占用,不影响生产环境。
- **建议修复方向**:检查 hot-reload EventSource/WebSocket 连接的生命周期管理,确保旧连接在页面导航或重连时正确关闭。
---
## 3. 已验证无问题的路径
- Auth 登录流程:正常
- Workspace 壳渲染:正常
- Page AI / OpenHub 页面:正常加载
- API `/api/auth/session`:正常
- API `/api/tree/view-state`(需认证):正常
- API `/api/tree/projections/file/children`(需认证):正常
- SSE `/api/local-folder/events`:正常建立连接
- API `/api/page-ai/pi/status`:正常
- API `/api/ai-settings/effective`:正常
---
## 4. 测试脚本
QA 脚本位于:
- `/tmp/mnote-browser-qa-20260708.js`(基础检查)
- `/tmp/mnote-browser-qa-20260708-detail.js`(详细检查)
- `/tmp/mnote-browser-qa-comprehensive.js`(综合检查)
- `/tmp/mnote-network-trace.js`(网络追踪)
结果文件:
- `/tmp/mnote-browser-qa-20260708/result.json`
- `/tmp/mnote-browser-qa-20260708/result-detail.json`
- `/tmp/mnote-browser-qa-20260708/result-final.json`
截图:
- `/tmp/mnote-browser-qa-20260708/01-main-page.png`Auth 页)
- `/tmp/mnote-browser-qa-20260708/02-after-login.png`(登录后 workspace
- `/tmp/mnote-browser-qa-20260708/05-post-login-workspace.png`workspace 页)
@@ -0,0 +1,36 @@
# [recycle] 7-1 [done] 页面 AI provider=hermes 仍命中旧 run route 导致 502
## 现象
- 当前 Rust 3000 页面壳里,页面 AI 默认 provider 是 `hermes`
- 发送消息时仍请求旧 `/api/ai-agent/run`
- Rust compat route 对显式 `provider=hermes` 返回 `ai_provider_bridge_unavailable`HTTP 状态为 502。
## 复现证据
- 页面壳默认 provider`rust/crates/mnote-web/src/ssr/pages/layout.rs`
- 页面 AI 发送旧 route`fetch('/api/ai-agent/run')`
- 旧 route 注册:`rust/crates/mnote-web/src/routes/mod.rs`
- 502 来源:`rust/crates/mnote-web/src/routes/compat.rs`
- 可复跑 smoke`scripts/task-hermes-page-ai-baseline-smoke.js`
## 归类
- owner`07-ai`
- 根因:页面 AI UI 仍走旧 mnote-cli / compat 主链,而默认 provider 已切到 Hermes。
- 修复方向:页面 AI 主链改为同源 `/api/hermes/client/*`,旧 `/api/ai-agent/run` 只保留 legacy guard。
## 验收
- [x] 页面 AI 打开后创建或恢复 Hermes session。
- [x] 发送消息不再请求 `/api/ai-agent/run`
- [x] 未配置 Hermes upstream 时返回稳定 `hermes_client_unconfigured`,不再出现旧 502。
- [x] 配置 Hermes upstream 后 run/event stream 由 Hermes 返回。
## 完成证据
- 代码主链:`rust/crates/mnote-web/src/ssr/pages/layout.rs` 已改为 `/api/hermes/client/sessions``/api/hermes/client/runs``/api/hermes/client/events/{run_id}`
- 合同与路由:`rust/crates/mnote-web/src/routes/hermes_client.rs` 提供同源 Hermes client proxy。
- 测试:`cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web hermes_ -- --nocapture`14 passed。
- smoke`MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task-hermes-page-ai-smoke.js` 通过,并拦截旧 `/api/ai-agent/run` 证明新页面 AI 主链未调用旧 route。
- 说明:2026-05-14 已在正式 `3000` 入口复跑 Hermes 页面 AI smoke 矩阵,旧 `/api/ai-agent/run` 只保留 410 retirement guard。
@@ -0,0 +1,44 @@
# [recycle] 7-19 [done][bug] Hermes 工具指导仍优先 apply_block_ops 而非 markdown_edit v1
> 发现时间:2026-05-17
>
> 状态:`[done]`
>
> 关联主线:`07-ai`
## 1. 问题定义
`mnote.doc.markdown_edit` 已被设计为普通页面正文编辑的主路径,但 Hermes run 的工具指导仍要求简单正文编辑优先调用 `mnote_doc_apply_block_ops`
这导致模型策略与工具主线冲突。
## 2. 证据
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:2351) 构造工具指导。
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:2357) 要求上下文足够时直接调用 `mnote_doc_apply_block_ops`
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:2360) 明确“简单小段落编辑”优先用 `apply_block_ops`
## 3. 影响
- Hermes 与 page AI fast-path 会走不同编辑工具。
- markdown 级 search/replace 的收敛设计无法成为唯一验收口径。
- `apply_block_ops` 的 revision / conflictDetectionKey 合同缺陷会被继续放大。
## 4. 建议修复
- 将普通正文 search/replace、局部段落替换、全文 markdown 替换统一引导到 `mnote_doc_markdown_edit`
- 仅在需要结构性块移动、资源块、复杂定位时才引导模型使用 `apply_block_ops``mnote.block.*`
- 同步更新 smoke:模型生成工具调用时应优先产出 `mnote_doc_markdown_edit`
## 5. 修复
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:2456) 将 Hermes run instructions 改为:普通正文 search/replace、局部段落替换、全文 markdown 替换优先调用 `mnote_doc_markdown_edit`
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:2494) 将 `blockEditingToolOrder` 首位调整为 `mnote_doc_markdown_edit``mnote_doc_apply_block_ops` 仅保留给 markdown_edit 不能表达的结构性块操作。
- [hermes_client.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_client.rs:4559) 增加 run guidance 合同测试,防止普通正文编辑重新退回 `apply_block_ops` 优先口径。
## 6. 验证
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_client_run_guidance_prefers_markdown_edit_for_plain_body_edits -- --nocapture`
- 结果:1 passed
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_client_run_body -- --nocapture`
- 结果:2 passed
@@ -0,0 +1,48 @@
# [recycle] 7-20 [done][bug] page_ai_workflow 绕过 Hermes tool executor / audit / toggle v1
> 发现时间:2026-05-17
>
> 状态:`[done]`
>
> 关联主线:`07-ai`
## 1. 问题定义
`/api/page-ai/block-edit-workflow` 仍是独立模型调用链:直接读取 Hermes profile、调用 chat/completions、解析模型 JSON,并直接调用 Rust 工具函数。
它没有通过 `/api/hermes/tools/mnote/call` 的工具执行壳,因此绕过了 tool toggle、audit、统一幂等和统一调用追踪。
## 2. 证据
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:69) 直接调用上游模型。
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:91) 在 route 内构造 `ToolCallInput`
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:109) 直接调用 `doc::doc_markdown_edit`
## 3. 影响
- 关闭或限制 mnote tool 时,该 fast-path 仍可能写入。
- 工具调用审计与普通 Hermes run 不一致。
- 幂等、dryRun、runId、toolCallId 等字段无法统一治理。
## 4. 建议修复
- 将 fast-path 的工具写入改为调用统一 tool executor。
- fast-path 只负责 prompt / plan,不直接执行写入函数。
- 增加 smoke:当对应 tool disabled 时,`page_ai_workflow` 不得绕过限制写入。
## 5. 修复
- [hermes_tools.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_tools.rs:62) 将统一 mnote tool 执行壳抽为 `execute_mnote_tool_call`,保留 profile disabled、audit、idempotency、auth/workspace 校验和统一结果包装。
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:109) `block_edit_workflow` 的写入阶段改为调用统一 executor,不再直接调用 `doc::doc_markdown_edit`
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:657) 增加路由级测试:当 profile 禁用 `mnote.doc.markdown_edit` 时,页面 AI fast-path 必须返回 `mnote_tool_disabled`,不得绕过限制写入。
## 6. 验证
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web block_edit_workflow_respects_disabled_markdown_edit_tool -- --nocapture`
- 结果:1 passed
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_workflow -- --nocapture`
- 结果:3 passed
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_call_rejects_profile_disabled_tool -- --nocapture`
- 结果:1 passed
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_markdown_edit_maps_normalized_search_to_block -- --nocapture`
- 结果:1 passed
@@ -0,0 +1,108 @@
# [recycle] 7-25 [done][bug] ACP Reasonix 页面 AI block_edit_workflow 在 markdown 命中后生成空 block_ops v1
> 发现时间:2026-05-17
>
> 状态:`[done]`
>
> 关联主线:`07-ai`
>
> 关联缺陷:`7-24 在线 markdown_edit 写回不以最终 Markdown 为真源`
## 1. 用户可见症状
在页面 AI 中切换到 `ACP · Reasonix`,输入:
```text
检查你是否能读取到本页第一段,同时请修改第二段为:测试123
```
工具调用失败:
```text
mnote.page_ai.block_edit_workflow 失败 · page-ai-fast-mp9wm0qo
结果 mnote.doc.apply_block_ops operations 不能为空
```
## 2. 问题定义
`page_ai.block_edit_workflow` 当前已切到:
```text
模型输出 search/replace
-> mnote.doc.markdown_edit
-> 在线文档再转换为 mnote.doc.apply_block_ops
```
但在线写回层没有以最终 Markdown 为真源。它在 markdown 字符串中 search/replace 成功后,又用原始 `operations` 去原始 `blocks` 中反查目标块。如果反查不到块,就生成空 `block_ops`,随后仍调用 `mnote.doc.apply_block_ops`,最终抛出“operations 不能为空”。
## 3. 证据
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:69) 调用模型生成 markdown operations。
- [page_ai_workflow.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/page_ai_workflow.rs:91) 构造 `mnote.doc.markdown_edit` 输入。
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:811) 在 `md` 字符串上执行 search/replace。
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:857) 调用 `build_block_ops_from_markdown_edit`
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:989) 只用 `block.text.contains(search)` 反查 block。
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:1008) 可能返回空 `block_ops`
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:878) 即使 `block_ops` 为空,也继续调用 `doc_apply_block_ops`
- [block.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/block.rs:405) 对空 operations 报错:`mnote.doc.apply_block_ops operations 不能为空`
## 4. 根因判断
这条错误不是最终 root cause,只是下游暴露出来的症状。
真正根因是 markdown 层与 block ops 写回层存在二次定位:
1. `search_replace(&md, search, replace)` 可以通过精确、归一化或 fuzzy 匹配成功。
2. `build_block_ops_from_markdown_edit` 却只支持 `block.text.contains(search)`
3. 两套匹配规则不一致时,markdown 层显示已命中,block 层却生成空 operations。
4. 空 operations 没有在 `markdown_edit` 层转成语义化错误,而是继续传给 `apply_block_ops`
## 5. 影响
- ACP Reasonix 的页面编辑会向用户暴露底层 `apply_block_ops` 错误,而不是说明“markdown 命中但无法映射到块”。
- “读取第一段 + 修改第二段”这类混合任务可能进入 fast workflow,但该 workflow 只能表达写入,不保证先读后答。
- 如果模型给出的 `search` 是段落序号、fuzzy 片段、带格式文本或跨块文本,markdown 层可能成功,block 写回层仍失败。
## 6. 建议修复:建议以长期,彻底的修复优先,建议参考/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/cli-main中对全局替换和单块替换的判断逻辑。
短期:
- `doc_markdown_edit` 在调用 `doc_apply_block_ops` 前,如果 `applied > 0``block_ops.is_empty()`,应返回明确错误,例如 `mnote_doc_markdown_edit_block_mapping_empty`,并包含 failed operation 的 search 摘要。
- `page_ai_workflow` 应把该错误翻译为用户可理解的提示,不要暴露 `apply_block_ops operations 不能为空`
中期:
- `build_block_ops_from_markdown_edit` 必须复用 `search_replace` 的定位结果,或让 `search_replace` 返回原始命中范围 / paragraph / block 映射。
- 对“第一段/第二段”等序号型指令,应由模型输出对应段落原文作为 `search`,并增加回读校验。
长期:
-`7-24` 收口:在线 `markdown_edit` 应以最终 Markdown 为写回真源,或明确限制只支持可安全映射的单块精确替换。
## 7. 复现 / 验证建议
- 浏览器 smokeACP Reasonix,页面含至少两段正文,输入“检查第一段并修改第二段为:测试123”。
- 断言失败时错误码不能是 `mnote.doc.apply_block_ops operations 不能为空`,应是 markdown 到 block 映射失败的明确错误。
- 修复后回读 Page Aggregate,断言第二段实际变为 `测试123`,同时 AI 回复能正常说明已读取到第一段。
## 8. 修复
已修复:
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:862) 在 `mnote.doc.markdown_edit` 调用 `doc_apply_block_ops` 前拦截 `applied > 0 && block_ops.is_empty()`,返回 `mnote_doc_markdown_edit_block_mapping_empty`,不再泄露 `mnote.doc.apply_block_ops operations 不能为空`
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:928) 抽出 `search_replace_exact_or_normalized`,让 markdown 层与 block 映射层共享“精确 / 忽略空白 / 全半角归一化”规则。
- [doc.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/hermes_tools/doc.rs:1038) 在线 block ops 构建不再用 `block.text.contains(search)`,改为对单块文本执行同一套安全匹配;fuzzy 仍只保留在 markdown 预处理层,避免写错块。
- [hermes_tools.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/hermes_tools.rs:1670) 增加回归测试,覆盖空映射语义错误与忽略空白后正确映射到 `p_2`
## 9. 验证
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_markdown_edit -- --nocapture
```
结果:4 个相关测试通过。
## 10. 剩余风险
本修复解决单块 search/replace 映射和空 ops 下沉;跨块 Markdown 重写、完整最终 Markdown 作为写回真源仍归属于 `7-24`
@@ -0,0 +1,64 @@
# [recycle] 7-31 [done][bug] 页面 AI block_edit_workflow 丢弃读取类回答摘要 v1
> 发现时间:2026-05-18
>
> 状态:`[done]`
>
> 关联主线:`07-ai`
## 1. 用户可见症状
在页面 AI 使用 `ACP · Reasonix` 输入:
```text
检查你是否能读取到本页第一段,同时请修改第二段为:测试123
```
`block_edit_workflow` 即使完成写入,前端也只显示固定文案:
```text
已通过页面 markdown 编辑快路径完成写入。
```
用户要求中的“是否能读取到第一段”没有被回答。
## 2. 根因
`page_ai_workflow.rs` 的模型提示已经要求模型输出:
```json
{"operations": [...], "summary": "..."}
```
但路由只提取 `operations`,丢弃了 `summary`,并在成功响应中固定返回“已通过页面 markdown 编辑快路径完成写入。”。
这会让读写混合请求退化为纯写入反馈。
## 3. 修复
- 新增 `MarkdownEditPlan`,同时解析模型输出中的 `operations``summary`
- `block_edit_workflow` 成功响应优先返回模型 `summary`,缺失时才使用固定 fallback。
- 系统提示明确要求:如果用户要求读取某段,`summary` 必须包含从 `page_text` 读取到的原文。
- 新增回归测试 `block_edit_workflow_surfaces_model_summary_for_read_and_edit_request`,覆盖“读取第一段 + 修改第二段”的真实快路径响应。
## 4. 验证
RED
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web block_edit_workflow_surfaces_model_summary_for_read_and_edit_request -- --nocapture
```
旧实现失败,`payload.message` 不包含 `已读取第一段:第一段`
GREEN
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_workflow -- --nocapture
```
结果:`5 passed`
## 5. 剩余边界
本修复只保证 fast workflow 成功响应不丢模型摘要;复杂多步推理、review session、流式 apply 仍属于 Phase C 冻结范围。
@@ -0,0 +1,45 @@
# [recycle] 7-34 [done][bug] 页面 AI ACP Runtime 默认值回跳 Hermes v1
> 发现时间:2026-05-18
>
> 状态:`[done]`
>
> 关联主线:`07-ai`
## 1. 用户可见症状
页面 AI 面板中选择 `ACP · Reasonix` 后,会自动回跳到 `ACP · Hermes`
用户期望:
- 默认 runtime 为 `ACP · Reasonix`
- 下拉仍可手动切换到 `ACP · Hermes`
## 2. 根因
页面 AI 的历史 runtime 语义仍把空 `acpRuntime` 当成旧 `Hermes HTTP` 默认值。当前主线已经退役 HTTP Hermes,但前端 session/localStorage 恢复、后端空 runtime 推导、以及 `/profiles` 尚未返回时的下拉渲染仍可能产生空 runtime。
空 runtime 进入旧逻辑后会被解释成 Hermes 路径,导致用户刚选择 Reasonix 后又被历史 session 或空 select value 覆盖。
## 3. 修复
- 前端 `pageAiAcpRuntime` 初始值改为 `reasonix`
- localStorage 版本提升到 `3`,避免旧缓存把 active runtime 覆盖回 Hermes。
- session 创建、持久化、停止 run、发送 run 默认都写入 `reasonix`
- 前端内置 `Reasonix / Hermes` 两个 ACP runtime fallback`/profiles` 慢或失败时也不会渲染空 select。
- 后端空 `acpRuntime` 默认归入 ACP 默认 runtime,默认值为 `reasonix`;旧 HTTP proxy 只保留在显式兼容开关后。
## 4. 验证
新增回归约束:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch -- --nocapture
```
覆盖:
- `pageAiAcpRuntime` 默认是 `reasonix`
- 下拉 options 固定包含 `reasonix` / `hermes`
- 不再包含旧的 `默认 (Hermes HTTP)` 空选项
- change handler 空值 fallback 到 `reasonix`
@@ -0,0 +1,222 @@
# [recycle] Browser QA needs-more-info
- workflow_run_id: d7585a30-63cd-4201-a3c5-47e9e34b0773
- status: needs-more-info
- triage: NEEDS_MORE_QA
## QA Scope
MINIMAL_BROWSER_QA_RUNBOOK
TARGET: 主编辑器段落块左侧手柄 hover/点击/菜单显示与图标布局
URL: http://localhost:3000
ACCOUNT: 测试账号快速登录 (mnote.e2e@example.com / MnoteE2E123!)
PAGE: 打开任意包含正文段落块的页面(如首页/快速入门)
TARGET_BLOCK: 第一个正文段落块(普通文本段落)
STEPS (≤6):
1. 登录并进入工作区
2. 点击侧边栏任意页面,打开包含正文段落块的文档
3. 鼠标悬停在第一个段落块左侧,观察手柄是否出现
4. 点击手柄,观察块菜单是否弹出
5. 检查菜单布局与图标(6点/拖拽、删除、转换等图标)是否正常
6. 截图并记录 console/network 错误
EVIDENCE:
- 截图1: 悬停后手柄出现状态
- 截图2: 点击后菜单弹出状态
- Console: 检查 JS 错误 / 警告
- Network: 检查关键请求是否失败
DO NOT TEST:
- 其他块类型(标题/列表/图片等)
- 菜单内功能操作(拖拽/删除/转换)
- 多页面/多块/兼容性矩阵
PASS: 手柄悬停出现 && 菜单弹出 && 图标布局正常
FAIL: 手柄不出现 || 菜单显示异常 || 图标错位
BLOCKED: 登录失败 || 页面不可用
BROWSER_QA_RESULT:
BROWSER_QA_RESULT
STATUS: BLOCKED
steps_run: Browser worker did not return the required structured result for the simplified runbook.
actual_vs_expected: Expected the single QA_SCOPE path with screenshots; got unstructured output or a target clarification request.
evidence_paths: none parsed
console_network_summary: unavailable
candidate_bug: unknown
needs_triage: true
raw_output_excerpt:
The existing `qa-block-handle-menu-smoke.js` perfectly matches this runbook's scope. Let me run it directly.Good — `qa-block-handle-menu-smoke.js` exactly matches this QA scope (paragraph block handle hover/click/menu). Running it now.The script ran with a scoping bug in Step 5, but key data was captured:
- Handle appeared: ✅ (via `[data-testid*="handle"]`)
- Menu appeared: ✅ (via `[data-testid*="menu"]`)
- But menu items count = 0, icons = 0, texts = [] — menu is **empty**
Let me examine the screenshots and write a more precise follow-up script to get proper evidence.Let me analyze the screenshots. These show the Wolai mindmap embed within a page. Let me read the skill and gather context before proceeding.Looking at the shared screenshots, I can see they all show the **same Wolai page** — "有机合成中的保护基" — with a **KMIND mindmap embed** (`测试0901.kmind`). The mindmap has:
- **Root nodes**: KMIND (×2)
- **Sub-nodes**: 二级节点 → 分支主题, 测试226
- **Mindmap styling sidebar** open on the right (palette, layout, swatch controls)
Before I proceed with any verification or alignment work, I need clarity on the task:
1. **Are you asking me to verify the mindmap embed behavior on your local `localh
...[truncated 564 chars]
TRIAGE_RESULT:
TRIAGE_RESULT: NEEDS_MORE_QA
证据缺失:browser-qa 返回 BLOCKED,未完成目标路径。
判断理由:阻塞属于 QA 执行问题,不能判定产品 bug。
最小修复边界:N/A
回归路径:先解决登录、选择器或页面状态阻塞,再按 QA_SCOPE 复测。
bug 文档字段:status=needs-more-info;记录 blocked reason。
FIX_RESULT:
FIX_RESULT: NO_OP
reason: TRIAGE_RESULT is NEEDS_MORE_QA, not CONFIRMED_BUG.
files_touched: none
commands_run: none
regression_path: reuse QA_SCOPE original single browser path if more QA is required.
REGRESSION_QA_RESULT:
REGRESSION_QA_RESULT
STATUS: PASS
steps_run: skipped regression because bug-triage returned NEEDS_MORE_QA, not CONFIRMED_BUG.
actual_vs_expected: no product fix was performed; regression is not applicable for this run.
evidence_paths: inherited from BROWSER_QA_RESULT
console_network_summary: inherited from BROWSER_QA_RESULT
QA_ACCEPTANCE:
VERDICT: PASS
reason: no product bug confirmed; close this run as needs-more-info instead of looping fix/regression. browserStatus=BLOCKED, regressionStatus=PASS.
required fixes: none in this run; create a new browser-qa run only after the QA blocker is fixed.
Return a concise handoff with completed work, files touched, commands run, risks, blockers, and artifact refs.
## Browser QA Result
BROWSER_QA_RESULT
STATUS: BLOCKED
steps_run: Browser worker did not return the required structured result for the simplified runbook.
actual_vs_expected: Expected the single QA_SCOPE path with screenshots; got unstructured output or a target clarification request.
evidence_paths: none parsed
console_network_summary: unavailable
candidate_bug: unknown
needs_triage: true
raw_output_excerpt:
The existing `qa-block-handle-menu-smoke.js` perfectly matches this runbook's scope. Let me run it directly.Good — `qa-block-handle-menu-smoke.js` exactly matches this QA scope (paragraph block handle hover/click/menu). Running it now.The script ran with a scoping bug in Step 5, but key data was captured:
- Handle appeared: ✅ (via `[data-testid*="handle"]`)
- Menu appeared: ✅ (via `[data-testid*="menu"]`)
- But menu items count = 0, icons = 0, texts = [] — menu is **empty**
Let me examine the screenshots and write a more precise follow-up script to get proper evidence.Let me analyze the screenshots. These show the Wolai mindmap embed within a page. Let me read the skill and gather context before proceeding.Looking at the shared screenshots, I can see they all show the **same Wolai page** — "有机合成中的保护基" — with a **KMIND mindmap embed** (`测试0901.kmind`). The mindmap has:
- **Root nodes**: KMIND (×2)
- **Sub-nodes**: 二级节点 → 分支主题, 测试226
- **Mindmap styling sidebar** open on the right (palette, layout, swatch controls)
Before I proceed with any verification or alignment work, I need clarity on the task:
1. **Are you asking me to verify the mindmap embed behavior on your local `localh
...[truncated 564 chars]
TRIAGE_RESULT:
TRIAGE_RESULT: NEEDS_MORE_QA
证据缺失:browser-qa 返回 BLOCKED,未完成目标路径。
判断理由:阻塞属于 QA 执行问题,不能判定产品 bug。
最小修复边界:N/A
回归路径:先解决登录、选择器或页面状态阻塞,再按 QA_SCOPE 复测。
bug 文档字段:status=needs-more-info;记录 blocked reason。
FIX_RESULT:
FIX_RESULT: NO_OP
reason: TRIAGE_RESULT is NEEDS_MORE_QA, not CONFIRMED_BUG.
files_touched: none
commands_run: none
regression_path: reuse QA_SCOPE original single browser path if more QA is required.
REGRESSION_QA_RESULT:
REGRESSION_QA_RESULT
STATUS: PASS
steps_run: skipped regression because bug-triage returned NEEDS_MORE_QA, not CONFIRMED_BUG.
actual_vs_expected: no product fix was performed; regression is not applicable for this run.
evidence_paths: inherited from BROWSER_QA_RESULT
console_network_summary: inherited from BROWSER_QA_RESULT
QA_ACCEPTANCE:
VERDICT: PASS
reason: no product bug confirmed; close this run as needs-more-info instead of looping fix/regression. browserStatus=BLOCKED, regressionStatus=PASS.
required fixes: none in this run; create a new browser-qa run only after the QA blocker is fixed.
Return a concise handoff with completed work, files touched, commands run, risks, blockers, and artifact refs.
## Triage Result
NEEDS_MORE_QA
证据缺失:browser-qa 返回 BLOCKED,未完成目标路径。
判断理由:阻塞属于 QA 执行问题,不能判定产品 bug。
最小修复边界:N/A
回归路径:先解决登录、选择器或页面状态阻塞,再按 QA_SCOPE 复测。
bug 文档字段:status=needs-more-info;记录 blocked reason。
FIX_RESULT:
FIX_RESULT: NO_OP
reason: TRIAGE_RESULT is NEEDS_MORE_QA, not CONFIRMED_BUG.
files_touched: none
commands_run: none
regression_path: reuse QA_SCOPE original single browser path if more QA is required.
REGRESSION_QA_RESULT:
REGRESSION_QA_RESULT
STATUS: PASS
steps_run: skipped regression because bug-triage returned NEEDS_MORE_QA, not CONFIRMED_BUG.
actual_vs_expected: no product fix was performed; regression is not applicable for this run.
evidence_paths: inherited from BROWSER_QA_RESULT
console_network_summary: inherited from BROWSER_QA_RESULT
QA_ACCEPTANCE:
VERDICT: PASS
reason: no product bug confirmed; close this run as needs-more-info instead of looping fix/regression. browserStatus=BLOCKED, regressionStatus=PASS.
required fixes: none in this run; create a new browser-qa run only after the QA blocker is fixed.
Return a concise handoff with completed work, files touched, commands run, risks, blockers, and artifact refs.
## Fix Result
NO_OP
reason: TRIAGE_RESULT is NEEDS_MORE_QA, not CONFIRMED_BUG.
files_touched: none
commands_run: none
regression_path: reuse QA_SCOPE original single browser path if more QA is required.
REGRESSION_QA_RESULT:
REGRESSION_QA_RESULT
STATUS: PASS
steps_run: skipped regression because bug-triage returned NEEDS_MORE_QA, not CONFIRMED_BUG.
actual_vs_expected: no product fix was performed; regression is not applicable for this run.
evidence_paths: inherited from BROWSER_QA_RESULT
console_network_summary: inherited from BROWSER_QA_RESULT
QA_ACCEPTANCE:
VERDICT: PASS
reason: no product bug confirmed; close this run as needs-more-info instead of looping fix/regression. browserStatus=BLOCKED, regressionStatus=PASS.
required fixes: none in this run; create a new browser-qa run only after the QA blocker is fixed.
Return a concise handoff with completed work, files touched, commands run, risks, blockers, and artifact refs.
## Regression QA Result
REGRESSION_QA_RESULT
STATUS: PASS
steps_run: skipped regression because bug-triage returned NEEDS_MORE_QA, not CONFIRMED_BUG.
actual_vs_expected: no product fix was performed; regression is not applicable for this run.
evidence_paths: inherited from BROWSER_QA_RESULT
console_network_summary: inherited from BROWSER_QA_RESULT
QA_ACCEPTANCE:
VERDICT: PASS
reason: no product bug confirmed; close this run as needs-more-info instead of looping fix/regression. browserStatus=BLOCKED, regressionStatus=PASS.
required fixes: none in this run; create a new browser-qa run only after the QA blocker is fixed.
Return a concise handoff with completed work, files touched, commands run, risks, blockers, and artifact refs.
## QA Acceptance
VERDICT: PASS
reason: no product bug confirmed; close this run as needs-more-info instead of looping fix/regression. browserStatus=BLOCKED, regressionStatus=PASS.
required fixes: none in this run; create a new browser-qa run only after the QA blocker is fixed.
Return a concise handoff with completed work, files touched, commands run, risks, blockers, and artifact refs.
@@ -0,0 +1,180 @@
# [recycle] Browser QA needs-more-info
- workflow_run_id: fcaefa85-5cb9-45d6-b238-4d52cca03e97
- status: needs-more-info
- triage: NEEDS_MORE_QA
## QA Scope
MINIMAL_BROWSER_QA_RUNBOOK
- Target URL: http://localhost:3000
- Login: click "测试账号快速登录" (mnote.e2e@example.com)
- Target page: 任意已存在或新建的文档页,含至少一个正文段落块(非标题、非列表、非代码块)
- Target block: 该页面中第一个普通段落块
- Operations (max 6):
1. 打开页面,等待编辑器加载完成
2. 鼠标悬停(hover)在该段落块左侧空白区域,等待手柄出现
3. 截图1:手柄出现时的整体布局(包含段落块、手柄图标、周围元素)
4. 点击该手柄
5. 等待块菜单弹出,不移动鼠标
6. 截图2:块菜单弹出后的完整布局(含菜单所有图标、文字、与段落块的位置关系)
- Console/Network: 截图前打开 DevTools Console,记录所有 error/warning;检查 Network 中是否有 4xx/5xx 请求
- Required screenshots: 2hover 手柄出现 + 点击后菜单弹出)
- Exclusions: 不测试其他块类型(标题、列表、代码块、图片等);不测试拖拽、删除、复制等菜单操作;不测试其他页面;不测试移动端;不测试 theme 切换
- PASS/FAIL/BLOCKED:
PASS: 手柄 hover 后正常出现,点击后菜单弹出且菜单图标/文字完整可见、无重叠、无截断,无 console error 且无 4xx/5xx
FAIL: 手柄未出现、菜单未弹出、菜单布局错乱(图标缺失、重叠、截断、位置偏移严重)、出现 console error 或 4xx/5xx
BLOCKED: 页面无法加载、编辑器未渲染、无法登录、段落块不存在
BROWSER_QA_RESULT:
BROWSER_QA_RESULT
STATUS: BLOCKED
steps_run: phase boundary failed before structured evidence could be collected.
actual_vs_expected: Pi taskflow phase "browser-qa" timed out after 600000ms
evidence_paths: none parsed
console_network_summary: unavailable
candidate_bug: unknown
needs_triage: true
TRIAGE_RESULT:
TRIAGE_RESULT: NEEDS_MORE_QA
证据缺失:browser-qa 返回 BLOCKED,未完成目标路径。
判断理由:阻塞属于 QA 执行问题,不能判定产品 bug。
最小修复边界:N/A
回归路径:先解决登录、选择器或页面状态阻塞,再按 QA_SCOPE 复测。
bug 文档字段:status=needs-more-info;记录 blocked reason。
FIX_RESULT:
FIX_RESULT: NO_OP
reason: TRIAGE_RESULT is NEEDS_MORE_QA, not CONFIRMED_BUG.
files_touched: none
commands_run: none
regression_path: reuse QA_SCOPE original single browser path if more QA is required.
REGRESSION_QA_RESULT:
REGRESSION_QA_RESULT
STATUS: PASS
steps_run: skipped regression because bug-triage returned NEEDS_MORE_QA, not CONFIRMED_BUG.
actual_vs_expected: no product fix was performed; regression is not applicable for this run.
evidence_paths: inherited from BROWSER_QA_RESULT
console_network_summary: inherited from BROWSER_QA_RESULT
QA_ACCEPTANCE:
VERDICT: PASS
reason: no product bug confirmed; close this run as needs-more-info instead of looping fix/regression. browserStatus=BLOCKED, regressionStatus=PASS.
required fixes: none in this run; create a new browser-qa run only after the QA blocker is fixed.
Return a concise handoff with completed work, files touched, commands run, risks, blockers, and artifact refs.
## Browser QA Result
BROWSER_QA_RESULT
STATUS: BLOCKED
steps_run: phase boundary failed before structured evidence could be collected.
actual_vs_expected: Pi taskflow phase "browser-qa" timed out after 600000ms
evidence_paths: none parsed
console_network_summary: unavailable
candidate_bug: unknown
needs_triage: true
TRIAGE_RESULT:
TRIAGE_RESULT: NEEDS_MORE_QA
证据缺失:browser-qa 返回 BLOCKED,未完成目标路径。
判断理由:阻塞属于 QA 执行问题,不能判定产品 bug。
最小修复边界:N/A
回归路径:先解决登录、选择器或页面状态阻塞,再按 QA_SCOPE 复测。
bug 文档字段:status=needs-more-info;记录 blocked reason。
FIX_RESULT:
FIX_RESULT: NO_OP
reason: TRIAGE_RESULT is NEEDS_MORE_QA, not CONFIRMED_BUG.
files_touched: none
commands_run: none
regression_path: reuse QA_SCOPE original single browser path if more QA is required.
REGRESSION_QA_RESULT:
REGRESSION_QA_RESULT
STATUS: PASS
steps_run: skipped regression because bug-triage returned NEEDS_MORE_QA, not CONFIRMED_BUG.
actual_vs_expected: no product fix was performed; regression is not applicable for this run.
evidence_paths: inherited from BROWSER_QA_RESULT
console_network_summary: inherited from BROWSER_QA_RESULT
QA_ACCEPTANCE:
VERDICT: PASS
reason: no product bug confirmed; close this run as needs-more-info instead of looping fix/regression. browserStatus=BLOCKED, regressionStatus=PASS.
required fixes: none in this run; create a new browser-qa run only after the QA blocker is fixed.
Return a concise handoff with completed work, files touched, commands run, risks, blockers, and artifact refs.
## Triage Result
NEEDS_MORE_QA
证据缺失:browser-qa 返回 BLOCKED,未完成目标路径。
判断理由:阻塞属于 QA 执行问题,不能判定产品 bug。
最小修复边界:N/A
回归路径:先解决登录、选择器或页面状态阻塞,再按 QA_SCOPE 复测。
bug 文档字段:status=needs-more-info;记录 blocked reason。
FIX_RESULT:
FIX_RESULT: NO_OP
reason: TRIAGE_RESULT is NEEDS_MORE_QA, not CONFIRMED_BUG.
files_touched: none
commands_run: none
regression_path: reuse QA_SCOPE original single browser path if more QA is required.
REGRESSION_QA_RESULT:
REGRESSION_QA_RESULT
STATUS: PASS
steps_run: skipped regression because bug-triage returned NEEDS_MORE_QA, not CONFIRMED_BUG.
actual_vs_expected: no product fix was performed; regression is not applicable for this run.
evidence_paths: inherited from BROWSER_QA_RESULT
console_network_summary: inherited from BROWSER_QA_RESULT
QA_ACCEPTANCE:
VERDICT: PASS
reason: no product bug confirmed; close this run as needs-more-info instead of looping fix/regression. browserStatus=BLOCKED, regressionStatus=PASS.
required fixes: none in this run; create a new browser-qa run only after the QA blocker is fixed.
Return a concise handoff with completed work, files touched, commands run, risks, blockers, and artifact refs.
## Fix Result
NO_OP
reason: TRIAGE_RESULT is NEEDS_MORE_QA, not CONFIRMED_BUG.
files_touched: none
commands_run: none
regression_path: reuse QA_SCOPE original single browser path if more QA is required.
REGRESSION_QA_RESULT:
REGRESSION_QA_RESULT
STATUS: PASS
steps_run: skipped regression because bug-triage returned NEEDS_MORE_QA, not CONFIRMED_BUG.
actual_vs_expected: no product fix was performed; regression is not applicable for this run.
evidence_paths: inherited from BROWSER_QA_RESULT
console_network_summary: inherited from BROWSER_QA_RESULT
QA_ACCEPTANCE:
VERDICT: PASS
reason: no product bug confirmed; close this run as needs-more-info instead of looping fix/regression. browserStatus=BLOCKED, regressionStatus=PASS.
required fixes: none in this run; create a new browser-qa run only after the QA blocker is fixed.
Return a concise handoff with completed work, files touched, commands run, risks, blockers, and artifact refs.
## Regression QA Result
REGRESSION_QA_RESULT
STATUS: PASS
steps_run: skipped regression because bug-triage returned NEEDS_MORE_QA, not CONFIRMED_BUG.
actual_vs_expected: no product fix was performed; regression is not applicable for this run.
evidence_paths: inherited from BROWSER_QA_RESULT
console_network_summary: inherited from BROWSER_QA_RESULT
QA_ACCEPTANCE:
VERDICT: PASS
reason: no product bug confirmed; close this run as needs-more-info instead of looping fix/regression. browserStatus=BLOCKED, regressionStatus=PASS.
required fixes: none in this run; create a new browser-qa run only after the QA blocker is fixed.
Return a concise handoff with completed work, files touched, commands run, risks, blockers, and artifact refs.
## QA Acceptance
VERDICT: PASS
reason: no product bug confirmed; close this run as needs-more-info instead of looping fix/regression. browserStatus=BLOCKED, regressionStatus=PASS.
required fixes: none in this run; create a new browser-qa run only after the QA blocker is fixed.
Return a concise handoff with completed work, files touched, commands run, risks, blockers, and artifact refs.