dsh-agent-team-ui-testing 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -35
  3. package/annotation-capture.js +111 -0
  4. package/browser-safety.js +45 -1
  5. package/client.js +3709 -134
  6. package/context-menu.js +134 -0
  7. package/core/clock.js +88 -0
  8. package/core/config-store.js +566 -0
  9. package/core/config.js +892 -0
  10. package/core/contracts.js +240 -0
  11. package/core/errors.js +156 -0
  12. package/core/evidence.js +141 -0
  13. package/core/geometry.js +217 -0
  14. package/core/identity.js +180 -0
  15. package/core/index.js +24 -0
  16. package/core/p0.js +1101 -0
  17. package/core/readiness.js +175 -0
  18. package/core/registry.js +239 -0
  19. package/core/tab-access.js +1350 -0
  20. package/docs/adapters.md +160 -0
  21. package/docs/architecture.md +44 -9
  22. package/docs/capability-tiers.md +20 -4
  23. package/docs/capture-p0.md +39 -2
  24. package/docs/diagnose-reason-codes.md +157 -0
  25. package/docs/element-picker.md +24 -6
  26. package/docs/getting-started.md +47 -1
  27. package/docs/index.md +10 -7
  28. package/docs/multi-tab.md +55 -20
  29. package/docs/network-p0.md +2 -2
  30. package/docs/panel-context-menu.md +41 -0
  31. package/docs/t2-structured-gui.md +2 -2
  32. package/docs/t3-vision.md +6 -1
  33. package/docs/t4-dynamic-behavior.md +1 -0
  34. package/docs/tools.md +65 -7
  35. package/docs/verification.md +26 -2
  36. package/docs/viewport-control.md +21 -8
  37. package/index.js +3263 -251
  38. package/integration/cross-environment.js +1535 -0
  39. package/integration/diagnose.js +945 -0
  40. package/integration/tab-principal.js +493 -0
  41. package/locale/en.json +1 -1
  42. package/locale/zh.json +1 -1
  43. package/network-p0.js +24 -7
  44. package/package.json +4 -3
  45. package/select-control.js +298 -0
  46. package/skills/ui-testing-common/SKILL.md +54 -0
  47. package/skills/ui-testing-fake/SKILL.md +43 -0
  48. package/skills/{ui-testing → ui-testing-web}/SKILL.md +64 -24
  49. package/skills/{ui-testing → ui-testing-web}/manifests/declared-capabilities.json +9 -2
  50. package/skills/{ui-testing → ui-testing-web}/references/contracts/capability.md +3 -3
  51. package/skills/{ui-testing → ui-testing-web}/references/contracts/evidence.md +1 -1
  52. package/skills/{ui-testing → ui-testing-web}/references/contracts/operation.md +1 -1
  53. package/skills/{ui-testing → ui-testing-web}/references/core/fallback.md +1 -1
  54. package/skills/{ui-testing → ui-testing-web}/references/core/page-identity.md +3 -2
  55. package/skills/{ui-testing → ui-testing-web}/references/core/selection.md +1 -1
  56. package/skills/{ui-testing → ui-testing-web}/references/core/tool-routing-and-limits.md +6 -3
  57. package/skills/{ui-testing → ui-testing-web}/references/platforms/index.md +1 -1
  58. package/skills/{ui-testing → ui-testing-web}/references/platforms/web.md +2 -2
  59. package/skills/ui-testing-web/references/scenarios/annotations.md +104 -0
  60. package/skills/{ui-testing → ui-testing-web}/references/scenarios/functional.md +1 -1
  61. package/skills/{ui-testing → ui-testing-web}/references/scenarios/visual-motion.md +1 -1
  62. package/skills/{ui-testing → ui-testing-web}/references/tiers/t1-programmatic.md +2 -2
  63. package/skills/{ui-testing → ui-testing-web}/references/tiers/t3-static-visual.md +2 -2
  64. package/skills/{ui-testing → ui-testing-web}/references/tiers/t4-dynamic-visual.md +2 -1
  65. package/targets/fake/adapter.js +1163 -0
  66. package/targets/fake/fixtures.js +177 -0
  67. package/targets/web/adapter.js +1231 -0
  68. package/targets/web/provider.js +415 -0
  69. package/skills/ui-testing/references/scenarios/annotations.md +0 -87
  70. /package/skills/{ui-testing → ui-testing-web}/references/core/dynamic-visual.md +0 -0
  71. /package/skills/{ui-testing → ui-testing-web}/references/core/network-and-state.md +0 -0
  72. /package/skills/{ui-testing → ui-testing-web}/references/core/report-template.md +0 -0
  73. /package/skills/{ui-testing → ui-testing-web}/references/core/reporting.md +0 -0
  74. /package/skills/{ui-testing → ui-testing-web}/references/core/safety.md +0 -0
  75. /package/skills/{ui-testing → ui-testing-web}/references/core/verdict-examples.md +0 -0
  76. /package/skills/{ui-testing → ui-testing-web}/references/core/verdict-gate.md +0 -0
  77. /package/skills/{ui-testing → ui-testing-web}/references/core/verification.md +0 -0
  78. /package/skills/{ui-testing → ui-testing-web}/references/core/workflow.md +0 -0
  79. /package/skills/{ui-testing → ui-testing-web}/references/tiers/t2-structured.md +0 -0
  80. /package/skills/{ui-testing → ui-testing-web}/templates/capability-note.md +0 -0
  81. /package/skills/{ui-testing → ui-testing-web}/templates/platform.md +0 -0
  82. /package/skills/{ui-testing → ui-testing-web}/templates/regression-case.md +0 -0
  83. /package/skills/{ui-testing → ui-testing-web}/templates/scenario.md +0 -0
@@ -1,5 +1,7 @@
1
1
  # 选元素加入聊天框
2
2
 
3
+ `POST /plugins/ui-testing/element` 除 `{x,y,detail}` 外还接受 `{cursorOnly:true}`:只回命中元素的 computed `cursor`(空白背景回 `default`/页面的 `auto`),供预览层把「里面是手型、外面也是手型」做出来;`detail:true` 的命中同时带 `cursor`。它仍是纯读(不 ensure、不进队列、不占租约、不写 `lastError`)。
4
+
3
5
  调试视图的「选元素」模式:在预览里悬浮会框出光标下的页面元素,点击后把该元素的节点信息追加到 DSH 聊天框末尾,并把键盘焦点交回聊天框。对标 Chrome DevTools 的检查元素,但只发送一份文本块,不改页面、不写标注。
4
6
 
5
7
  ## 前置
@@ -58,6 +60,7 @@ selector: <稳健 CSS path>(degraded 时标注「非唯一定位,仅作线
58
60
  role: <role> · name: <name>
59
61
  rect(文档坐标): x=…, y=…, w×h
60
62
  page: <url>
63
+ frame: <子 frame 文档地址>(selector 在该 frame 文档内有效) ← 仅嵌套 frame 命中时出现
61
64
  outerHTML: 截断 <显示>/<原始> 码点(末尾 …,完整文本未取得) ← 仅截断时出现
62
65
  ```
63
66
 
@@ -94,20 +97,35 @@ outerHTML: 截断 <显示>/<原始> 码点(末尾 …,完整文本未取得
94
97
 
95
98
  - `x` / `y` 是页面文档 CSS 像素,必须是有限数且 `|v| ≤ 1e7`;否则 `400 INVALID_ARGUMENT`。小数会被 floor 后再命中。
96
99
  - 命中:`200 {ok:true, element:{tag,id,classes,rect,frameId,scroll,page:{url,title},loaderId?}}`;`detail:true` 追加 `selector` 与 `selectorQuality`(`unique`/`degraded`/`none`)、`outerHTML` 与 `outerHTMLTruncated`/`outerHTMLLength`、`text` 与 `textTruncated`/`textLength`、`role`/`name`(AX best-effort)以及 shadow 事实 `shadowHost`/`closedShadow`。
100
+ - 命中原生控件时排除浏览器自带的 UA shadow 节点,读取控件本身;页面作者创建的 open/closed shadow 命中规则不变。
101
+ - 命中 `<select>` 时追加 `select:{multiple,size,value,options:[{value,label,disabled,selected}],truncated,target?}`(最多 200 项,超出置 `truncated:true`)。`disabled` 包含控件、fieldset、option 和 optgroup 的有效禁用状态。可读取文档身份时,`target:{backendNodeId,loaderId}` 绑定该节点与文档;只有命中 select 才追加 `select` 字段。
102
+ - 命中嵌套 frame(同进程的 `<iframe>`)内的元素时,`rect` 仍是**顶层页面**文档坐标(iframe 偏移与滚动已折算),并追加 `frame:{id,url,main:false}`:`selector`/`outerHTML` 来自该 frame 的文档,`loaderId`/`select.target.loaderId` 也是该 frame 的文档代次。主帧命中不带 `frame` 字段,形状不变。
97
103
  - 截断是显式的:`outerHTML` 超过 240 码点时切到 240 并以 `…` 结尾(`outerHTMLTruncated:true`、`outerHTMLLength` 为原始码点数);`text` 同理(上限 160)。
98
104
  - 未命中:`200 {ok:true, element:null, reason}`,`reason` 为:
99
105
  - `no-page`:该 scope 没有运行中的页面(冷态);
100
- - `no-hit`:空白页面背景或该点没有节点;
101
- - `stale`:CDP 读取失败/超时,或主帧身份无法核对;
102
- - `frame`:命中在子 frame(v1 只选主帧);
106
+ - `no-hit`:空白页面背景或该点没有节点(含子 frame 的 `html`/`body`);
107
+ - `stale`:CDP 读取失败/超时、主帧身份无法核对,或命中帧已不在当前 frame 树里;
103
108
  - `out-of-viewport`:文档点在当前可见视口之外。
104
109
  - 只读:不启动 Chromium、不进串行队列、不占租约、不记录客户端指标、不写 `lastError`;每次 CDP 调用上限 1500ms。
105
110
 
106
111
  `GET /element` 固定 `405`(该路由只接受 POST)。
107
112
 
113
+ `POST /plugins/ui-testing/select` 写回一个选项(面板浮层用;门禁与 `/input` 完全一致——A10 环境门、scoped 鉴权、`ready`/租约(BUSY)、串行队列,且校验在派发之前,拒绝时零派发):
114
+
115
+ ```json
116
+ { "value": "world", "optionIndex": 1, "target": { "backendNodeId": 42, "loaderId": "文档身份" } }
117
+ ```
118
+
119
+ - `value` 必填且必须是字符串。面板提交 `/element` 返回的 `target` 与点击的 `optionIndex`;Host 按**整棵 frame 树**校验该 `loaderId` 仍是活着的文档(主帧或嵌套 frame),再解析同一节点,不受后续聚焦变化影响。`optionIndex` 是非负安全整数,写前校验对应 option 的 value,因此重复 value 也能精确选择。
120
+ - 兼容调用可给唯一 `selector`(否则 `selector-not-found` / `selector-not-unique` / `selector-invalid`);没有 `target` 或 `selector` 时才使用当前聚焦的 `<select>`。
121
+ - 写入用 `HTMLSelectElement.prototype.selectedIndex` 的 native setter,随后依次派发 `input`、`change`,返回 `200 {ok:true, value, selectedIndex, selector}`。禁用控件、禁用 option 和禁用 optgroup 均在写前拒绝。
122
+ - 拒绝统一 `400 INVALID_ARGUMENT`,`details.reason` 包括 `stale-target`(文档变化或节点无法解析)、`target-detached`、`option-index-out-of-range`、`option-value-mismatch`、`option-disabled`、`no-focused-element`、`not-a-select`、`select-disabled`、`unsupported-shape`(`multiple`/`size>1`)、`no-options`、`value-not-in-options` 与上述 selector 三类。
123
+ - 浮层等待页面点击完成后读取选项;关闭、scope/标签/文档切换(含嵌套 frame 导航)、画布原点或比例变化会使在途结果失效。选中或按 Esc 关闭后,键盘焦点交回预览。
124
+ - `multiple`、`size>1` 和无选项的控件只在地址栏下方、预览区域外显示键盘提示,不在页面画面上渲染提示浮层,也不抢走预览的键盘焦点。提示仍可见时,重复点击不支持的控件会保留同一条提示,不重新触发或改变预览布局;关闭后才可再次触发。按 Esc、点击预览外或其他内容可关闭提示,正常单选仍可直接打开选项列表。
125
+
108
126
  ## 限制
109
127
 
110
- - **只选主帧**:iframe / OOPIF 内部元素返回 `reason:"frame"`,与 T2 `snapshot` 的 `frame:"main"` 范围一致;跨帧坐标/selector 不做推断。
128
+ - **同进程嵌套 frame 可选**:页面内 `<iframe>` 的元素可以命中,`rect` 是顶层文档坐标、`selector` 属于该 frame 文档(响应里带 `frame`),`<select>` 浮层与写回同样可用。跨进程 OOPIF、closed shadow root 内部与不归属本页 frame 树的命中仍不支持,按 `no-hit`/`stale` 处理,不做坐标/selector 推断。
111
129
  - **closed shadow**:可命中 shadow root 内元素,但 `selector` 是该 root 作用域的路径,`document.querySelector(selector)` 可能找不到;`detail.shadowHost` 给出宿主信息、`closedShadow` 标明 root 是否 closed。
112
130
  - **主面板 `recent` 限制**:主面板身份解析不到会话时不写入聊天框,只提示;请使用右侧边栏的 session 面板。
113
131
  - **画布平移 `O ≠ 0`**:本版 GUI 验收只覆盖 `O = 0` 与页面滚动(`C` 变化);平移后的悬浮映射按共享画布的既有 fail-closed 规则处理。
@@ -118,8 +136,8 @@ outerHTML: 截断 <显示>/<原始> 码点(末尾 …,完整文本未取得
118
136
  ## 验证
119
137
 
120
138
  ```bash
121
- node tests/acceptance/pick-element-host.mjs # Host:坐标空间/命中/detail/只读语义/边界(140 checks)
122
- node tests/acceptance/pick-element-gui.mjs # GUI:隔离 DSH + 无头 Chrome 的真实面板操作(58 checks)
139
+ node tests/acceptance/pick-element-host.mjs # Host:坐标空间/命中/detail/嵌套 frame/只读语义/边界(145 checks)
140
+ node tests/acceptance/pick-element-gui.mjs # GUI:隔离 DSH + 无头 Chrome 的真实面板操作(选元素/悬停/@ 引用 + `--select-only` 分支含下拉浮层与右键菜单)
123
141
  ```
124
142
 
125
143
  两个套件都自启资源:Host 套件使用 fake-Cordis harness + 插件自己的无头 Chromium;GUI 套件自建隔离 DSH(临时 `DSH_HOME`、随机端口,绝不用 3080/3081)并另开无头 Chrome 驱动真实面板。报告与截图分别写入 `tests/acceptance/runs/`、`tests/acceptance/evidence/`;`NOT_RUN` 原样保留,不当作通过。
@@ -35,6 +35,8 @@ dsh plugin --profile web add github:bloodarea/dsh-agent-team-ui-testing#vX.Y.Z
35
35
  1. 把包安装进要使用的 DSH profile。
36
36
  2. 首次安装后**重启 DSH**,让插件 bundle 完成注册。
37
37
  3. 从 **更多 → 调试视图** 打开面板,或从右侧边栏的插件卡片进入。
38
+ 面板里的键盘会转发到页面(含 `Ctrl/Cmd/Alt/Shift` 组合、F1–F12、Home/End/PgUp/PgDn/Delete/Insert)。原生弹层(如 `alert/confirm`)由浏览器 UI 绘制、面板里看不到。预览区右键优先交给页面:页面自绘菜单正常显示,未取消默认行为时面板显示后退、前进、重新加载与复制相关项,详见[页面右键菜单](panel-context-menu.md)。DSH Web 自己的右键菜单不会在预览里弹出;**单选 `<select>` 例外**:点一下就在点击点弹出面板侧浮层(↑↓ / Enter / Esc、点外部关闭),选项来自 Host 的只读 `POST /element`,选中后走 `POST /select` 写回。`multiple`/`size>1`/无选项会在地址栏下方、预览外提示改用键盘,Esc 可关闭提示;同进程 `<iframe>` 内的单选 `<select>` 同样有浮层。自定义下拉沿用页面自己的交互。
39
+ 两个入口共用同一套视图:**右上角的「选择运行环境」组合框决定下方渲染哪个目标环境的视图**,而组合框**只列出设置页里已打开的目标环境**——例如只开了 Web 与 Android,就只出现这两项,没开的连选项都没有。当前只有 Web 视图实现了:选中 Android 这类已启用但没有视图的环境,会显示「视图层暂不支持」占位,并明确写出「执行环境仍然是 Web 视图,这里只切换视图」——**切视图不改执行环境**。把某个环境的开关关掉后,若当时正停在它的视图上,面板会退回宿主当前的执行环境。
38
40
  4. 插件开发时改为把本仓库链接或复制进 profile,不必安装 npm 包。
39
41
  5. 生效范围:
40
42
 
@@ -71,10 +73,54 @@ browser_screenshot {out} # 留一张可复核的 PNG
71
73
  - **实时预览**:CDP screencast 帧,WebSocket 推送优先、HTTP 轮询兜底。
72
74
  - **尺寸栏**:宽屏 / 横屏 / 平板 / 手机预设,X/Y 画布位置,宽 × 高,显示比例,`拖拽 | 适应窗口 | 默认` 模式。
73
75
  - **混合画布**:视口可像画布一样定位,支持负坐标、空白区域、拖拽平移与滚轮缩放。
74
- - **标注**:圆形、矩形、线段、箭头、画笔、文字、橡皮擦;标注锚定画布坐标,模式内页面不接收输入,Agent 可通过 `browser_state.annotations` 读回。
76
+ - **标注**:圆形、矩形、线段、箭头、画笔、文字六种形状,以及橡皮擦等编辑工具;标注锚定画布坐标,模式内页面不接收输入。用户提到画/圈/框/箭头/笔迹等面板标注时,Agent 先读 `browser_state.annotations`,再用 `browser_screenshot {includeAnnotations:true}` 查看 Host 已接受的文档叠加像素;不含未提交草稿与工具栏。`includeAnnotations` 为 boolean,默认 false;默认截图及 T3/T4 原始源不含面板标注,详见[截图与图片交付](capture-p0.md)。
75
77
  - **内部标签页**:真人点击 `target=_blank` / `window.open` 时,面板会在地址栏下方新增内部标签并自动切过去,原页面保留为另一个标签;标签栏由「标注」左侧开关控制显隐,单标签时也显示并可新建;`+` 只在标签栏内,工具栏不常驻。Agent 工具默认操作各自会话的 selectedTab(无选择 primary),可用 `browser_tabs` 查看/选择;GUI 切页不改 Agent 选择。自动弹窗与未授权弹窗仍被阻断。
76
78
  - **调试工具**:「更多 → 打开调试工具」为当前页面请求一个独立窗口,加载 Chrome 自带的 DevTools(与 F12 等价)。需要当前工作区的浏览器运行时已启动;只有 GUI 与 DSH 同机、且通过 loopback(127.0.0.1/localhost)访问时可用,远程 GUI 暂不支持。弹窗被浏览器拦截时,面板会显示提示和「在新标签页打开调试工具」链接。
77
79
  - **共享状态**:面板与 Agent 使用同一 `expectedRevision`,冲突会明确报告而不是静默覆盖。
80
+ - **地址栏**:地址栏右侧的时钟按钮展开「最近访问」下拉,按栈展示最近 7 条已访问的 HTTP(S) 地址(最近的在上;重复访问只置顶不重复;空地址、`about:blank` 与应用自身地址不记录)。条目点击直接跳到该地址。历史存在浏览器 `localStorage`(同一 GUI 源的多个面板共享),按 `dsh-agent-team-ui-testing:address-history:v1` 保存,最多 7 条;鼠标点下拉外部或按 <kbd>Esc</kbd> 收起,切换作用域或进入标注模式也会收起。地址栏与标签页按钮之间的 **转到** 按钮等同按 <kbd>Enter</kbd> 提交当前地址(同一套校验与错误提示,标注模式或导航中禁用)。
81
+
82
+ ## 设置页
83
+
84
+ 打开 DSH 的「设置」,左侧导航里的 `UI 测试` 就是「目标环境配置」:
85
+
86
+ 卡片表示五节点链路的第五节点——被调试的运行时或应用;框架与图形 API 放在说明中。候选目标如下:
87
+
88
+ | 环境分组 | 卡片目标 | 技术栈说明举例 |
89
+ | --- | --- | --- |
90
+ | Web | Chrome、Edge、Firefox(运行环境 = provider) | 相应浏览器中运行的网页 |
91
+ | Linux | Linux 原生桌面应用、Electron 应用、Tauri 应用 | GTK / Qt / Flutter、Chromium、WebKitGTK |
92
+ | Windows | Windows 原生桌面应用、.NET 桌面应用、WinUI 3 应用、Electron 应用、Tauri 应用 | Win32 / Qt / Flutter、WPF / Windows Forms、Windows App SDK、WebView2 |
93
+ | macOS | macOS 原生桌面应用、Electron 应用、Tauri 应用 | AppKit / SwiftUI / Qt / Flutter、Chromium、WKWebView |
94
+ | Android | Android 原生应用、Android WebView 应用、Flutter Android 应用、React Native 应用 | Views / Compose、Capacitor / Cordova、Flutter 引擎、原生视图与 JS 运行时 |
95
+ | TUI | Python、Node.js、Go、Rust 终端应用 | Textual、Ink / blessed、Bubble Tea、Ratatui |
96
+ | HarmonyOS | 鸿蒙原生应用、ArkUI-X 应用、Flutter 鸿蒙应用、React Native 鸿蒙应用 | ArkUI / ArkTS 与相应适配应用 |
97
+ | 游戏 | Unity 游戏、Unreal 游戏、Godot 游戏、Cocos 游戏 | 对应引擎运行实例(浏览器内运行的 Web 游戏归 Web 环境,不在此重复列出) |
98
+ | 3D | Web 三维应用、原生三维应用、Unity 三维应用、Blender、3ds Max、MikuMikuDance、PMX Editor | Three.js / Babylon.js / PlayCanvas、WebGL / WebGPU、原生窗口与 Unity Player、Blender / 3ds Max 编辑界面、MMD 与 PMX 模型工具 |
99
+ | 小程序 | 微信、支付宝、抖音、QQ、百度智能、快手、京东、美团、小红书、淘宝、钉钉小程序 | 对应小程序容器中的应用 |
100
+
101
+ - 十个环境分组、共 48 张卡片,分组**默认收起**;标题右侧显示当前筛选下的目标数量。展开/收起状态保存在浏览器 `localStorage`,刷新或重开设置页保持。
102
+ - 右上角「隐藏未实现」默认关闭;开启后只保留已实现的运行环境(卡片上标「可用」的那些),没有剩余目标的分组一并隐藏。它只筛选页面展示,不改变工具、Skill 或目标的启用配置。
103
+ - 卡片只保留名称、启用分开关,以及页脚:左下角是状态槽——**已实现**标一枚绿色「可用」,未实现放一行灰色小字「未支持」或「未接入」;右下角固定「诊断」按钮。不再显示「当前使用 / 未选中」标记(开关本身已反映启用状态),也不再使用「已实现 / 未支持 / 未接入」的药丸标签。说明文字统一压到两行(超出省略,鼠标悬停可看全文),因此同一行卡片高度稳定一致。
104
+ - 设置页的**目标环境总开关**:v1 只有 Web 有适配器,但**其余 9 个环境允许先打开开关**——「启用」不等于「可执行」:开关打开后它才出现在面板的组合框里(视图层没实现则显示占位),而执行环境仍然只能是已实现且已启用的那一个(把 `planned` 环境设为当前执行环境依旧会被宿主拒绝)。宿主返回的英文校验原因(`environment X is planned and cannot be selected in v1`、`unknown environment/provider`、`test-only`)在界面上统一翻成中文,原文只留在悬停提示里。
105
+ - 点卡片上的「诊断」会在**独立弹层**(frame-wide overlay,不再挤在设置页里)打开诊断面板,面板宽度 `min(1180px, 94vw)`,设置页本身仍是环境分组列表;关闭方式有 ✕ 按钮、`Esc`、点遮罩三种,关闭会中止进行中的检查并把焦点还给卡片按钮。打开面板会**真实执行一次诊断 run**(`POST /plugins/ui-testing/diagnose`,NDJSON 流):打开即发起一次 `observe` run,不会启动浏览器;页面按五个阶段(① 宿主集成/工具注册/调用通道、② 插件 Core/配置/调度、③ 通信接口探测、④ 执行后端与依赖、⑤ 目标列表/绑定/连接)逐条回填。节点与边只用 Host 的五个结果状态:**正常 / 警告 / 异常 / 跳过 / 未知**(「正常」绿、「警告」琥珀、「异常」红虚线、「跳过」灰虚线、「未知」灰蓝点线);界面上只显示中文,英文状态码与原因码(`WARN`、`browser-not-running` 等)只出现在 `data-uts-*` 属性与 NDJSON 的 `reason` 里,供 agent 解析。所以「警告」是**已经得出的诊断结论**,不是让你再去点一次。四个板块右上角原来那几条英文装饰(`HOST RUN · STAGED CHECKS` 等)已去掉,同样的值仍从 `data-uts-*` 属性可读。每个调试节点的状态区固定预留两行(30px),超出两行才截断;**鼠标悬停在节点上**会给出完整信息:角色与名称、职责、完整状态、原因码、阻塞于哪一段、耗时;顶部胶囊的悬停提示同样带完整状态句。面板四块从上到下是**调试流程**(步骤 1–5 的链路)、**各模块诊断状态**(六轴卡片)、**诊断日志**、**步骤职责**;调试流程里每张步骤卡片的职责描述固定占两行,「能力就绪」的检查项一行一项、最多三行(还有几条就写在第三行末尾,如「快照:跳过 +3」,悬停看全部),六张模块卡片都按三行占位,所以卡片高度不随结果长短跳动;界面上用中文术语(本轮、宿主、阶段开始/结束、只读求值、目标列表、快照、截图),英文原词只在 `data-uts-*` 与 NDJSON 里,逐条对照见 [诊断状态码](diagnose-reason-codes.md)。还没有任何 run 结果时,总览与节点写「**待诊断**」;run 没能跑出结果时写「**诊断未完成**」(错误另起一行说明)——这两个词不属于五态,只是「本轮还没有结论」。状态码与状态词本身是英文机器契约(`data-uts-*`、NDJSON 的 `reason`),界面上只显示中文译文,逐条对照见 [诊断状态码](diagnose-reason-codes.md)。没有 run 就没有 `PASS`:这里不拿 readiness 快照冒充「已检查」。拓扑里 ④「Chromium DevTools」为绿色意味着 ③→④ 这一段在本 run 里 PASS,不会再出现「节点绿、前面那条边却断」的矛盾。页面顶部**始终**有「开始诊断」按钮:手动触发一轮 `start` 模式的真实检查;浏览器未运行时由该动作启动所选浏览器并创建受管调试页,已连接时只重新检查、不重启浏览器;打开页面本身的自动检查仍然是不启动浏览器的 `observe`,在用户点击之前不会启动任何浏览器。离开诊断页会中止进行中的 run(Host 侧随之停止),不会留下迟到执行。每次点「诊断」进入本页都会发起一次新的 `observe` run(相同 provider 也一样),旧结果只用于离开前的回显;进入时不会重放上一次的动画。
106
+ - **两层开关。** 分组标题栏里是**目标环境总开关**(写 `environments.<env>.enabled`);卡片里的「用户启用」是**运行环境分开关**(Web 分组下写 `environments.web.providers.<id>.enabled`,即 `chrome` / `edge` / `firefox` 各一份)。两者独立:开一个 provider 不会切换当前执行端,也不会改动其它 provider;关闭总开关时所有分开关置灰不可点,但**已保存的值原样保留**(不写 0),重新打开后按原值显示。
107
+ - **运行环境(provider)与当前执行端。** 一个 `web` 目标环境下可以有多个 provider,执行时按 `provider` 单选。选择链为:会话选择 > 父会话 > 工作区 > 全局 > 默认 `chrome`;当前 provider 被禁用时**不会静默切换到别的浏览器**,而是保留选择并阻断执行(错误码见 [adapters.md](adapters.md) 的错误语义)。卡片不再单独标出「当前使用 / 未选中」,该值仍由 Host 投影提供(`GET /state` 的 `resolved.provider` 与 `[data-uts-provider-current]` 属性),只是不在界面上重复写第二遍。预览面板仍按工作区展示,不按 provider 分栏。
108
+ - **配置形状。** `environments.web` 下有 `provider`(当前执行端,默认 `chrome`)与 `providers.{chrome,edge,firefox}.enabled`;`enabledProviders` 是**派生**列表,不单独存储。更早版本写下的、没有 `provider` 字段的配置文件会被自动按「chrome 默认选择 + chrome 启用」解释,**不会**被判为配置损坏。
109
+ - **配置真值在 Host**:设置页每次打开时自动读取一次 `GET /plugins/ui-testing/config?view=global`(页面上不再有「重新读取」按钮),启用开关通过 `PUT /plugins/ui-testing/config` 提交并携带 `expectedRevision`。成功用返回的 revision 更新;冲突(`CONFIG_CONFLICT`)会展示最新 revision 并回滚,不显示“已保存”。配置文件默认在 `${DSH_HOME}/ui-testing/config.json`,可用 `DSH_UI_TESTING_CONFIG_DIR` 覆盖。
110
+ - **工具布局由面板决定**:设置页里启用了哪些环境,模型请求里就出现这些环境的工具(外加两个环境无关诊断),不会带入其它环境的 schema;关掉某个环境即从请求里消失,不需要重启。
111
+ - **各模块诊断状态(六轴)与诊断日志都来自本轮诊断**:六轴仍是原来的词表(`工程支持 / 目标绑定 / 用户启用 / 能力就绪 / 依赖可用 / 连接可信`),值取自 `run-end.axes`,每轴带 `source=run`、`runId`、`checkedAt` 与 `checks`;「用户启用」始终是 Host 配置里保存的开关,「能力就绪」来自本次真正跑过的六条**只读**探测:只读表达式求值、目标列表、结构化快照、截图、计算样式、网络元数据读取;它们不改页面、不导航、不落盘(截图只留字节数/尺寸/哈希摘要,快照只留节点数与 id)。六条全过才标「可用」,部分通过标「部分可用」,冷态没有连接时标「未探测」。「本次 run / 就绪快照 / 副作用 / 实际调用」这些运行信息不占版面,放在状态胶囊的**悬停提示**里;「开始诊断」会不会启动浏览器、会不会重启,也在按钮的**悬停提示**里说明。没有 run 时才回退到 readiness 快照并标明来源。事件流每个 `stage-end` 一行,显示 Host 实测 `durationMs`、状态、原因与 `blockedBy`;SKIPPED 阶段没有实际耗时,显示「—」而不是 0。run 进行中暂停 5 秒 readiness 轮询;run 结束后六轴与拓扑以 run 结果为准,轮询只更新「就绪快照」时间戳,不会把结果换回 readiness。证据边界:readiness 快照只说明配置与探测结果,不等于一次真实调用;只有本 run 的 `actualCalls`(`browser_capabilities`、只读 `browser_eval`)才是真实执行的证据。
112
+ - 依赖探测只做低干扰检查(可执行文件是否存在),不启动浏览器、不扫描未启用环境;连接与能力探测在需要时另行执行,读取型探测不证明输入已生效。
113
+ - 面板顶栏「运行环境」选择器读取同一份 Host 配置的 `enabledEnvironments`;切换时 `POST /plugins/ui-testing/config/select` 只写当前会话,成功才更新本地状态。未实现环境仍禁用,且不会触发任何探测。
114
+ - 生效方式:`client.js` 改动刷新当前 DSH GUI 后生效;`index.js`、工具注册、Skill provider 与配置存储改动需要重启或重挂插件。已注入历史的 Skill 正文不会被开关自动移除,切换只对后续轮次生效。
115
+
116
+ ## 运行环境(provider):Chrome 与 Edge
117
+
118
+ - **可执行文件按厂商分立**:Chrome 与 Edge 各有一份候选清单(Linux `microsoft-edge` / `microsoft-edge-stable`、Windows `Microsoft/Edge/Application/msedge.exe`、macOS Edge `.app` 内的可执行文件),互不混用。
119
+ - **覆盖变量按 provider 分开**:Chrome 用 `DSH_UI_TESTING_CHROME`,Edge 用 `DSH_UI_TESTING_EDGE`。显式值**权威且不回退**:Edge 的值不可用时直接报 `DEPENDENCY_MISSING`(reason `override-invalid`),不会改用 Chrome。
120
+ - **profile 按 provider 独立**(无头用一次性临时目录;有头持久目录也按 provider 分开),Chrome 与 Edge 不共享同一个有头 profile。
121
+ - **启动后校验产品身份**:连上 CDP 之后用 `Browser.getVersion` 的产品串确认实际启动的是哪个厂商(Edge 必须含 `Edg/`,Chrome 必须含 `Chrome/` 且不含 `Edg/`)。"连上"不等于"开对了";对不上报 `DEPENDENCY_MISSING`(reason `identity-mismatch`),Edge 拿不到产品串则按 `identity-unverified` 拒绝启动。
122
+ - **Edge 的当前边界(务必按字面理解)**:Edge 的驱动与参数化代码已接入(复用同一套 CDP 连接、目标生命周期、输入、结构树、截图、网络与帧采集实现),但**本轮没有在装了 Edge 的机器上实际拉起过**。在没有 Edge 二进制的机器上:Edge 未启用时依赖轴是「未探测」(未启用不探测);**启用之后**依赖轴变为 `failed / executable-missing`(卡片上「依赖」一行直接显示),此时启用它不会有任何页面被打开——这**不代表 Edge 已验证可用**。
123
+ - **Firefox 当前未支持**:卡片置灰,开关不可点。
78
124
 
79
125
  ## 安全边界
80
126
 
package/docs/index.md CHANGED
@@ -1,23 +1,25 @@
1
1
  # 文档
2
2
 
3
- 本目录提供插件的使用说明、能力参考与贡献者指南。Web / Chromium 已实现 WEB-P0 基础设施与 T1–T4 工具;其他运行环境尚未提供适配器。
3
+ 本目录提供插件的使用说明、能力参考与贡献者指南。Web / Chromium 已实现 WEB-P0 基础设施与 T1–T4 工具,并通过三类 adapter 契约接入 Host 权威配置、逐 step 工具准入与按环境 Skill 发现;其他运行环境尚未提供真实适配器。
4
4
 
5
5
  ## 开始使用
6
6
 
7
7
  | 文档 | 内容 |
8
8
  | --- | --- |
9
9
  | [快速开始](getting-started.md) | 安装要求、安装与接入、打开面板与首次调用 |
10
- | [工具参考](tools.md) | 36 个工具的参数、返回与错误分类 |
10
+ | [诊断状态码(中英对照)](diagnose-reason-codes.md) | 诊断页每个状态码的英文机器契约与中文界面文案、出现阶段、动态码族与维护方式 |
11
+ | [工具参考](tools.md) | 37 个工具的参数、返回与错误分类 |
11
12
  | [能力分层与限制](capability-tiers.md) | WEB-P0 与 T1–T4 的职责、选路与支持边界 |
12
13
 
13
14
  ## 功能参考
14
15
 
15
16
  | 文档 | 内容 |
16
17
  | --- | --- |
17
- | [多标签页](multi-tab.md) | 每工作区 primary + 3 child、per-tab BUSY、Agent selectedTab、`browser_tabs`、GUI 内部标签栏与 `+` |
18
- | [视口、画布与标注](viewport-control.md) | 共享视口、坐标、缩放、拖拽、标注与并发控制 |
19
- | [选元素加入聊天框](element-picker.md) | 面板选元素模式、悬浮框选、文档坐标、`@` 元素引用 chip(显示紧凑、模型文本完整)、降级与限制 |
20
- | [截图与图片交付](capture-p0.md) | 截图区域、分辨率、文件留存与证据边界 |
18
+ | [多标签页](multi-tab.md) | 页面组按 (workspace, provider, 团队根) 分组、双选择(work/observe)、`browser_tabs` 九动作含队长 acquire/release/transfer、长期归属与控制权、GUI 内部标签栏与 `+` |
19
+ | [视口、画布与标注](viewport-control.md) | 共享视口、坐标、缩放、拖拽、光标跟随、标注与并发控制 |
20
+ | [页面右键菜单](panel-context-menu.md) | 页面自绘菜单优先、默认菜单常用操作、可信事件观测与失效边界 |
21
+ | [选元素加入聊天框](element-picker.md) | 面板选元素模式、悬浮框选、文档坐标、`@` 元素引用 chip(显示紧凑、模型文本完整)、原生下拉浮层与绑定写回、降级与限制 |
22
+ | [截图与图片交付](capture-p0.md) | 截图区域、分辨率、includeAnnotations 标注叠加、文件留存与证据边界 |
21
23
  | [网络调试](network-p0.md) | 请求、WebSocket、SSE、链路、控制与证据导出 |
22
24
  | [结构化 GUI](t2-structured-gui.md) | snapshot/ref、点击、输入、表单与等待 |
23
25
  | [静态视觉](t3-vision.md) | 图像处理、定位、比较、基线与视觉断言 |
@@ -28,6 +30,7 @@
28
30
  | 文档 | 内容 |
29
31
  | --- | --- |
30
32
  | [架构](architecture.md) | 运行时、模块职责、工作区隔离与扩展位置 |
33
+ | [三类 adapter:契约、接入与坑点](adapters.md) | TargetAdapter / CapabilityAdapter / P0Provider 的接口形状、配置约束、六轴就绪写法、12 条实测坑点与新平台接入步骤 |
31
34
  | [验证与贡献](verification.md) | 环境要求、测试命令、结果判读与提交要求 |
32
35
 
33
- 公开文档只描述当前行为、可复现的用法和已知限制。实施计划、协作记录、逐轮验收日志和本机环境信息不纳入公开文档;自动生成的报告与截图默认保留在本地。内置 Agent 操作手册随 [ui-testing Skill](../skills/ui-testing/SKILL.md) 分发。
36
+ 公开文档只描述当前行为、可复现的用法和已知限制。实施计划、协作记录、逐轮验收日志和本机环境信息不纳入公开文档;自动生成的报告与截图默认保留在本地。内置 Agent 操作手册随 [ui-testing-web Skill](../skills/ui-testing-web/SKILL.md) 分发。
package/docs/multi-tab.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 多标签页(P0-E)
2
2
 
3
- 一个 DSH workspace 是一个**页面组**:一个稳定的 primary 槽位 + 最多 3 个 child 标签,共 4 个标签。每个标签是一个独立 Runtime,拥有自己的 page、URL/history、view、标注、网络缓冲、T2 ref 表、串行队列和 single-owner lease;Cookie/localStorage/缓存仍由同一个 Chromium profile 共享,**不是登录态隔离**。
3
+ 页面组按 **(workspace, provider, 团队根)** 分组:同一个团队根(Lead 与它的成员)共享同一组页;不同独立会话、不同团队根即使同在一个 workspace,也各有自己的一组,互相看不见、也不能互相写。每组有一个稳定的 primary 槽位 + child 标签;primary 是每个根各一个槽,child 的**最多 3 个**是跨 root 合计的(不是每个根各 3 个),全局同时 live 上限 8。每个标签是一个独立 Runtime,拥有自己的 page、URL/history、view、标注、网络缓冲、T2 ref 表、串行队列和 single-owner lease;Cookie/localStorage/缓存仍由同一个 Chromium profile 共享,**不是登录态隔离**。
4
4
 
5
5
  ## 身份与选择
6
6
 
@@ -8,7 +8,9 @@
8
8
  | --- | --- |
9
9
  | workspace | 由 DSH 会话/workspace registry 决定;客户端不能靠路径重分组 |
10
10
  | tab | 不透明 id;primary 固定为 `primary`,child 也有独立 `runtimeId` |
11
- | Agent selection | 每个**真实执行会话**(`exec.agent.id`)独立保存 selectedTab;未显式选择时默认 primary |
11
+ | 团队根(root) | 由宿主原生 identity 决定:Team 内是 `membership.root.id`,独立会话是它自己。页面组与权限都按 root 划分,**不由 workspace、cwd 或 parent 链推断** |
12
+ | Agent workSelection | 每个**真实执行会话**(`exec.agent.id`)独立保存的**写**目标;未显式选择时默认指向它**自己已拥有**的页(自己拥有 primary 就是 primary),没有任何自有页时保持**未绑定**(写操作返回 `TARGET_NOT_BOUND`,须先 `open`)。写工具只用它 |
13
+ | Agent observationSelection | 同一会话的**只读**目标,由 `select{mode:"observe"}` 显式设置;读工具优先用它,未设置时回退到 workSelection。观察不会改 workSelection,也不 claim 写租约 |
12
14
  | GUI active | 每个调试面板独立;GUI 切标签不改变 Agent selection,Agent 改 selection 也不自动切 GUI |
13
15
  | binding | HTTP/WS 用 `X-UI-Testing-Scope-Binding: <runtimeId>` 直接指定某个标签 |
14
16
 
@@ -18,27 +20,36 @@
18
20
 
19
21
  ## browser_tabs
20
22
 
21
- 唯一的 P0-E 管理工具(第 36 个工具);旧 35 个工具的参数 schema 不变,调用默认走当前会话的 selectedTab。
23
+ 唯一的 P0-E 管理工具(第 36 个工具,新增的 P0-D `browser_annotation` 是第 37 个);其他工具的参数 schema 不变,调用默认走当前会话自己的选择(写用 workSelection,读优先 observationSelection)。
22
24
 
23
- ```json
25
+ ```jsonc
24
26
  {
25
- "action": "list | current | select | open | close | wait",
26
- "tabId": "可选;select/wait 使用;close 省略表示当前标签",
27
+ "action": "list | current | select | open | close | wait | acquire | release | transfer",
28
+ "tabId": "select/open?/close/wait/acquire/release/transfer 使用;close 省略=当前标签",
29
+ "mode": "仅 select:work(默认)| observe",
27
30
  "kind": "仅 wait:primary | tab",
28
31
  "source": "仅 wait:primary | gui-popup | agent-open",
29
- "timeoutMs": "仅 wait:0–30000,默认 5000",
30
- "pollMs": "仅 wait:50–1000,默认 100"
32
+ "timeoutMs": "仅 wait(等待预算)| acquire(等待安全边界的预算):0–30000,默认 5000",
33
+ "pollMs": "仅 wait:50–1000,默认 100",
34
+ "expectedControlRevision": "仅 acquire/release/transfer:必填,CAS 期望的 controlRevision",
35
+ "expectedOwnershipRevision": "仅 transfer:必填,CAS 期望的 ownershipRevision",
36
+ "toSessionId": "仅 transfer:本 root 内有效成员 SessionId(提交前重取原生 roster 校验)"
31
37
  }
32
38
  ```
33
39
 
40
+ 字段按 action 归类,**非所属 action 的字段会被拒绝**(`INVALID_ARGUMENT`),不会被静默忽略:例如 `list` 只接受 `action`,带上 `tabId` 或 `timeoutMs` 都是参数错误。
41
+
34
42
  | action | 行为 |
35
43
  | --- | --- |
36
44
  | `list` | 只读列出当前 workspace 的标签与生命周期字段(见下表),并附带 `notices`、`recentClosed` 与调用者 selection;不启动 Chromium、不取 lease |
37
- | `current` | 只读确认当前会话的 selectedTab;若有 pendingReset,报告 primary 与 resetReason 并确认复位 |
38
- | `select` | 校验 tab 属于同 workspace,原子 claim 后选择;目标被其他 owner 占用返回 `BUSY`,旧 selection 不变 |
39
- | `open` | 新建 about:blank child 并选择;不要求先有 popup |
40
- | `close` | 仅允许关闭当前会话持有有效 lease 的标签;省略 tabId 关闭当前标签;关闭/退休记入 `recentClosed` |
45
+ | `current` | 只读确认当前会话的选择(返回 `workSelection` 与 `observationSelection`,`selection` 是 work 的兼容投影);若有 pendingReset,报告 primary 与 resetReason 并确认复位 |
46
+ | `select` | 只改**调用者自己**的选择,不 claim 租约、不 activate。`mode:"work"`(默认)要求本会话可写该页(owner/controller)或本 root 的 Lead 可管理它;`mode:"observe"` 只要同 root 可读。跨 root 的目标一律 not-found 语义;被拒绝时不改变调用者已有选择 |
47
+ | `open` | 新建 about:blank child 并选择(同时写入 work/observation 两个选择);不要求先有 popup |
48
+ | `close` | 走长期权属判定(owner 或当前 controller;本 root 的 Lead 也可以);省略 tabId 关闭当前标签;关闭/退休记入 `recentClosed` |
41
49
  | `wait` | 纯 Host 内存轮询:等待**调用开始后新出现**的标签;超时抛 `WAIT_TIMEOUT`(`details` 含 `elapsedMs/polls/condition`) |
50
+ | `acquire` | 仅本 root 的 Lead:先关门(`owned → draining`,controller 不变),等当前操作到达安全边界(有界、可取消,**不中断**在途操作),再提交 `controller=Lead`、`controlRevision+1`。超时返回 `BUSY` 并撤销 draining,取消返回 `ABORTED`,两者都不会留下后台接管 |
51
+ | `release` | 仅本 root 的 Lead:把控制权还给 owner(`controlRevision+1`,owner 不变)。相同当前版本的重复调用返回 `changed:false` |
52
+ | `transfer` | 仅本 root 的 Lead:把 owner+controller 一起转给本 root 的有效 roster 成员(`ownershipRevision` 与 `controlRevision` 各 +1,`creatorSessionId` 保留)。目标不是成员/成员失效返回 `TAB_FORBIDDEN` |
42
53
 
43
54
  `list` 的每个标签在既有 `tabId/runtimeId/kind/title/url/status/selected/busy/owner/reset` 之外新增:
44
55
 
@@ -49,7 +60,13 @@
49
60
  | `openEventId` | 真人 popup 的 open 事件 id;`agent-open`/GUI `+`/primary 为 `null` |
50
61
  | `createdAt` / `lastUsedAt` | 入口创建与最近使用的时间戳(epoch ms);页面工具调用会刷新 `lastUsedAt` |
51
62
  | `idleRemainingMs` | 距 idle 回收剩余的毫秒数,按 `registry.idleMs` 计算(下限 0) |
52
- | `manageable` | 当前 Agent 会话是否可操作该标签(未被其他会话占用);`close` 仍需本人有效 lease 且无在途操作 |
63
+ | `manageable` | **兼容字段**:当前 Agent 会话是否未被其他会话的短租约占用。它不表达 ACL,不要用它推导权限——看 `permissions` |
64
+ | `accessVersion` | 访问投影版本(当前 `1`) |
65
+ | `rootSessionId` / `creatorSessionId` / `ownerSessionId` / `controllerSessionId` | 长期归属与控制权;`creator` 是审计字段,转交后保留 |
66
+ | `ownershipRevision` / `controlRevision` / `accessState` | 归属与控制的 CAS 版本、控制状态(`owned`/`draining`/`lead-controlled`/`retired`) |
67
+ | `permissions` | `{read,write,close,manage}`,由调用者身份对该页现算;`write` 表示当前可直接派发,Lead 需要先接管时 `write:false`、`manage:true` |
68
+
69
+ `busy` / `owner` 仍是**短租约**投影(`owner` 只表示"谁最近在写"),与长期归属/控制是两件事:页面归属不会因为 15 秒、思考、断线或 inactive 而改变。
53
70
 
54
71
  `list` 同时返回两个本 workspace 的有界投影(各最多 8 条):
55
72
 
@@ -63,11 +80,22 @@
63
80
  - `kind`/`source` 可叠加过滤;`timeoutMs:0` 只采样一次。
64
81
  - 不启动 Chromium、不取/续 lease、不刷新 idle 计时;命中返回 `{matched:true, matchedTab, elapsedMs, polls, condition}`。统一结果元数据里的 `tab:{id,runtimeId,selectionRevision}` 仍是调用所路由标签,命中的新标签在 `matchedTab`。
65
82
 
66
- 错误码:`TAB_NOT_FOUND`、`TAB_LIMIT`、`TAB_FORBIDDEN`、`BUSY`、`WAIT_TIMEOUT`、`INVALID_ARGUMENT`;匿名调用 select/open/close 返回 `UNSUPPORTED`(`agent-session-required`)。结果统一附带 `tab:{id,runtimeId,selectionRevision}`。
83
+ 错误码:
84
+ - `TAB_NOT_FOUND`:跨 root 或跨 workspace 的目标统一用这个码,**不回显**对方的 id/URL/标题,不泄露存在性;
85
+ - `TAB_FORBIDDEN`:能看见的本 root 目标但当前角色/归属不允许(例如成员写队友的页、transfer 目标不是成员);
86
+ - `TAB_CONTROL_CHANGED`:controller 不匹配、正在 `draining`、或 `expectedControlRevision` 过期——读 current 后重新决策,**不要当 BUSY 自动重放**;
87
+ - `TAB_OWNERSHIP_STALE`:`expectedOwnershipRevision` 不匹配;
88
+ - `TAB_IDENTITY_UNAVAILABLE`:当前调用者的身份/成员关系无法证明(不自动降级成"独立会话");
89
+ - `TAB_LIMIT`:容量;
90
+ - `BUSY`:**只表示短暂忙**(页面队列被占、接管等待超时),不是权限答案;
91
+ - `WAIT_TIMEOUT`、`INVALID_ARGUMENT`(含"非所属 action 的字段")。
92
+ 匿名调用 select/open/close 返回 `UNSUPPORTED`(`agent-session-required`)。结果统一附带 `tab:{id,runtimeId,selectionRevision}`;管理动作另附 `changed` 与 `control`(最终 controller/版本),被自动接管执行的写操作附 `autoAcquired:true`。
67
93
 
68
94
  ## BUSY 与并发
69
95
 
70
- - **BUSY 按 tab 计**:Agent A 在 tab1 工作不阻止 Agent B 在 tab2;同一 tab 的不同 owner 才返回 BUSY(含 15s idle lease)。
96
+ - **15 秒 lease 只表示"短暂忙",不决定权限**:谁能写由 root → 角色 → controller/版本决定。只读观察不占写租约(同一时间别人仍可写该页),写操作之间仍按 tab 串行。
97
+ - **BUSY 按 tab 计**:Agent A 在 tab1 工作不阻止 Agent B 在 tab2;同一 tab 上另一会话正在执行写操作时,后到的写才返回 `BUSY`。
98
+ - **两 root 互不阻塞**:不同独立会话/团队根在同一个 workspace 各有自己的页,各自 select `primary` 都成功,谁也不会把对方挤成 BUSY。
71
99
  - 每个 tab 独立队列;Registry 的额度/元数据锁不是全局页面队列。
72
100
  - GUI 可以预览 Agent 占用的 tab(`/state`、`/frame`);真实输入/导航等页面写返回 409 `BUSY`。GUI 可切换到其他 tab,预览帧继续。
73
101
  - 左下闲忙/FPS/ms 只来自 **GUI active tab** 的 `/state`,不做 workspace 聚合;Agent 在别的 tab 忙不影响当前显示。
@@ -82,15 +110,18 @@
82
110
  - 标签有标题、active 指示和关闭按钮(aria-label);关闭 child 后回 primary 或最后可用标签;Host views 移除会同步 UI。
83
111
  - **关闭最后一个(primary)标签 = 进入空态**:标签条里不再有任何标签,只保留 `+`;地址栏为空、预览回到默认空态。primary 槽位在 Host 侧变冷态(仍保留 runtimeId/binding),Agent 侧 `browser_tabs.close primary` 语义相同。空态下点 `+` 会新建一个空白标签并选中它。
84
112
  - **空态会跨刷新/面板重挂载保持**:面板挂载时的 seed 截图(`/screenshot`)只读取已运行页面,不会为冷态 primary 启动页面;只有用户显式导航或点 `+` 才会重新产生页面。
85
- - child 关闭被 Agent lease 挡住时(GUI 与 Agent 同时操作),面板会提示「标签正被 Agent 操作;稍后再关闭」,不再静默失败。
113
+ - child 关闭被 Agent lease 挡住时(GUI 与 Agent 同时操作),面板会提示「标签正被 Agent 操作;稍后再关闭」;其他拒绝(绑定失效、Host 找不到该运行时)提示「关闭标签失败,请稍后重试」,关闭请求不再静默成功。
114
+ - **面板按 runtime id 寻址当前标签**:active 标签是 child 时,`/state`、`/frame`、`/close`、`/tabs` 都带 `scope-binding` = 该标签的 runtimeId(primary 同理),Host 用这个 id 解析到**该标签自己的** entry(primary 或 child),不借用 Agent session 的 selectedTab,也不把 child 的 binding 当成 primary 的 workspace key。`scope=recent` 与 binding 都直接命中已存在的 entry,不会为同一 workspace 再造一个 Runtime。绑定必须命中真实 entry:`/close` 命中不到返回 404 `SCOPE_NOT_FOUND`(不返回假成功);`/tabs.open` 解析不到所属 primary 也报 `SCOPE_NOT_FOUND`,只有真的到容量上限才是 `TAB_LIMIT`。
86
115
  - 4/8 容量与 per-tab lease 同样约束 GUI:第 5 个标签/tab 返回 `TAB_LIMIT` 并在本地提示。
87
116
 
88
117
  ## 容量与来源
89
118
 
90
119
  | 限制 | 值 |
91
120
  | --- | --- |
92
- | 每 workspace 标签 | primary + 3 child = 4 |
93
- | 全局同时 live pages + 创建预留 | 8(沿用原 profile 上限) |
121
+ | primary 槽 | 每个 (workspace, provider, 团队根) 一个 |
122
+ | 每个 (workspace, provider) 的 child 标签 | 最多 3(**跨 root 合计**,不按 root 各给 3) |
123
+ | 每 workspace 的乘积预算 | (workspace, provider) 4 页;当前实现强制的是上面两条 child/primary 规则 + 全局 8,完整的预留与合计算法在后续容量切片重写(`browser_capabilities.tabs.maxTabsPerWorkspace:4` 报的是该预算值) |
124
+ | 全局同时 live pages + 创建预留 | 8(沿用原 profile 上限,跨 provider、跨 root 一个数) |
94
125
  | Agent lease idle | 15s(活动操作继续续期) |
95
126
  | 标签 idle 回收(child 与 primary 页) | 600000ms(10 分钟);可用环境变量 `DSH_UI_TESTING_IDLE_MS` 覆盖,当前值见 `browser_capabilities.tabs.idleMs` |
96
127
  | blocked popup notices / 关闭审计 | 各最多 8 条/workspace |
@@ -116,12 +147,16 @@
116
147
 
117
148
  ## 能力发现
118
149
 
119
- `browser_capabilities` 的 `tabs` 段报告版本、工具、`actions`(含 `wait`)、`maxTabsPerWorkspace:4`、`maxChildTabs:3`、`maxGlobalPages:8`、`idleMs`、`noticesLimit:8`、`closedHistoryLimit:8`、`keepalive` 说明与当前 selectedTab/views;`limits.tabs` 提供同一组限额与生命周期字段。读取 capabilities 不启动 Chromium、不取 lease。
150
+ `browser_capabilities` 的 `tabs` 段报告版本、工具、`actions`(九个动作)、`maxTabsPerWorkspace:4`、`maxChildTabs:3`、`maxGlobalPages:8`、`idleMs`、`noticesLimit:8`、`closedHistoryLimit:8`、`busyScope`、`keepalive` 说明与当前 selectedTab/views;`limits.tabs` 提供同一组限额与生命周期字段。读取 capabilities 不启动 Chromium、不取 lease,也不会为陌生 root 创建持久 entry。
120
151
 
121
152
  ## 边界(NOT)
122
153
 
123
- - 不提供跨 workspace 选页;不把 profile 隔离伪装成登录态隔离;notices/recentClosed 不跨 workspace 聚合。
154
+ - 不提供跨 workspace / 跨团队根选页(跨 root 的目标一律 not-found 语义,不回显对方 id/URL/标题);不把 profile 隔离伪装成登录态隔离;notices/recentClosed 不跨 workspace 聚合。
124
155
  - 不做跨 tab 的缓存/网络/标注聚合,也不把其他 Agent 的 selectedTab 暴露给 list。
125
156
  - `wait` 只观察宿主内存中的标签表,不订阅浏览器事件;被浏览器自身拦截(未产生 target)的 popup 不会有 notice,表现为"click 成功但无新标签"。
126
157
  - 前台/可见性可能影响后台帧率;P0-E 不承诺 CPU/GPU/profile 隔离。
158
+ - **观察不切前台,但后台页的帧新鲜度不保证**:只读工具既不占写租约、也不调用 `activate()`(不切前台、不改 `visibilityState`、不打断别人的写)。代价是**有头模式**下当前前台不是该页时,Chromium 可能不合成它,截图/预览可能拿到旧帧;无法保证实时帧时应按 `OBSERVATION_UNAVAILABLE` 处理(该码已定义,尚未在全部观察路径接线)。
159
+ - **GUI 面板侧没有 Principal,也没有接管入口**:面板能 list/open/close 与预览,但 `acquire/release/transfer` 只有 Agent 工具面可用;面板仍只靠同源浏览器认证,不能证明它代表哪个会话。
160
+ - **证据归属未按 root 隔离**:`browser_network*`、T3 产品/基线、T4 动态会话仍按 workspace/session 归属,跨 root 共享同一采集器与存储,不能当作 root 隔离的证据。
161
+ - **DevTools 直连地址仍会投影**:受管页可能仍返回 `127.0.0.1:<port>/devtools/...` 直连地址,该通道不受本页描述的权限模型约束。
127
162
  - 刷新/断线恢复、多面板并发、真实跨浏览器 SSO、idle/LRU 极端与 T3/T4 全矩阵仍按各套件 NOT_RUN 报告,不能由本页推断通过。
@@ -70,14 +70,14 @@ browser_network_session { action:"stop" }
70
70
  ## 时序与分析
71
71
 
72
72
  - 事件带单调 `seq` 与统一时间轴;`view:"timeline"` 把 http/ws/sse/marker 混排。
73
- - `view:"aggregate"` 必须给 `groupBy`;`search` 命中正文/WS/SSE 时只回**脱敏预览片段**。
73
+ - `view:"aggregate"` 必须给 `groupBy`;`aggregate` 的合法指标是固定白名单 `count`|`bytes`|`duration`|`p50`|`p95`|`failures`,给其它字符串返回 `NET_INVALID_ARGUMENT`(并回带 `allowed` 列表);`search` 命中正文/WS/SSE 时只回**脱敏预览片段**。
74
74
  - 游标不透明,`cursor` 与 `sinceSeq` 二选一;指向已淘汰 seq 时返回 `gap`。
75
75
 
76
76
  ## 业务链路
77
77
 
78
78
  `browser_network_trace` 提供步骤窗口与对象提取:
79
79
 
80
- - `begin-step` / `end-step` / `mark` 建立时间窗,`window` 按 wallMs 查询。
80
+ - `begin-step` / `end-step` / `mark` 建立窗口。窗口有 **wallMs 与 seq 两个边界**:一个事件属于该步骤必须同时落在 `[fromWallMs, toWallMs]` 与 `[fromSeq, toSeq]` 内。只看 wallMs 会把"步骤开始前就已入库、但其 response/finish 阶段时间戳更晚"的事件误算进下一步,因此 `stepId` 过滤与派生归属都以 seq 边界为准。
81
81
  - `objectRules` 声明要提取的业务对象;不做隐式猜测。
82
82
  - `correlationRules` 声明关联字段;`basis` 只有 `explicit`/`redirect`/`initiator` 才算因果,`temporal` 只是时间邻近(默认过滤,需 `includeTemporal:true`)。
83
83
  - `link` 建立 seq 与证据的显式关联,重复调用幂等。
@@ -0,0 +1,41 @@
1
+ # 调试视图里的页面右键菜单
2
+
3
+ 在预览的页面内容上按右键,会向目标页发送一次可信的右键按下、`contextmenu` 和抬起。DSH 自身的右键菜单不会在预览区内打开;预览区以外保持宿主行为。
4
+
5
+ ## 页面菜单与默认菜单
6
+
7
+ - 页面调用 `preventDefault()` 时,保留页面自己的行为。页面自绘菜单仍在预览帧中显示,菜单项沿用普通左键输入。
8
+ - 页面没有取消默认行为时,面板在点击处显示菜单:后退、前进、重新加载、复制页面地址;存在相应上下文时增加复制选中文本、复制链接地址、复制图片地址。后退/前进按页面历史禁用。
9
+ - 方向键、Home/End 移动,Enter/空格执行,Esc/Tab 关闭并恢复预览焦点;点击外部关闭。菜单自动限制在预览框内,空间不足时可滚动。
10
+ - 复制使用 GUI 浏览器的剪贴板 API。权限失败会显示提示;密码输入的选中文本不读取。地址只接受不含用户名/密码的 HTTP(S) URL,复制图片地址不会下载或复制图片二进制。
11
+
12
+ 此菜单覆盖上述常用操作,没有提供完整 Chromium 菜单里的打印、另存为、翻译、扩展项、剪切与粘贴。浏览器原生 UI 本身仍不进入页面 screencast 或截图;面板菜单也不包含在目标页的 `browser_screenshot` 中。
13
+
14
+ ## 观测与失效
15
+
16
+ Host 在命中 frame 的隔离 world 中安装临时 capture 监听器,保存真实 Event;完整右键派发后才读取最终 `defaultPrevented`。结果分为 `handled`(页面取消默认)、`default`(可显示面板菜单)、`unknown`(没有足够证据)。`unknown` 不显示面板菜单;例如页面较早的 window capture 监听器调用 `stopImmediatePropagation()`,或命中跨进程 frame、期间发生导航。
17
+
18
+ 同进程 iframe 在自己的 frame 中观测。标注/选元素模式、画布空白、画面未同步时不发送右键。右键抬起在同一 Host 事务内完成,用户把指针移出图片不会遗留按下状态。
19
+
20
+ 右键响应携带同次操作结束时的权威页面状态,面板按 Runtime/revision 接收后绑定文档,避免刷新后的帧先到而状态轮询晚到导致菜单误关。菜单响应还绑定面板 scope、画布与请求序号;切页、预览相关滚动、缩放、失焦或视图不可操作时会关闭并丢弃旧响应;聊天等无关区域的滚动不影响菜单。Host 动作令牌只属于当前 Runtime,60 秒内单次有效,并在执行时核对主文档和命中 frame 的 loaderId。旧文档令牌返回 `INVALID_ARGUMENT` / `stale-target`。
21
+
22
+ ## 面板接口
23
+
24
+ 两个认证 GUI 页面操作均沿用环境门禁、scope 解析、输入租约与 Runtime 串行队列,不增加 Agent 工具:
25
+
26
+ | 路由 | 请求 | 返回 |
27
+ | --- | --- | --- |
28
+ | `POST /plugins/ui-testing/context-menu` | `{x,y,modifiers?}`;视口 CSS px,修饰键 CDP 位掩码 0–15 | `{ok,status,menu?,state}`;menu 含短期 token 与白名单 items,state 为同次操作的权威快照 |
29
+ | `POST /plugins/ui-testing/context-action` | `{token,action}` | 执行后退/前进/重新加载;拒绝过期 token 和未知/禁用项 |
30
+
31
+ 复制项的文本在右键上下文中读取并有长度上限;实际复制在 GUI 的真人菜单点击中发起,以保留剪贴板手势权限。
32
+
33
+ ## 验证
34
+
35
+ ```bash
36
+ node --test tests/contracts/panel-context-menu.test.mjs
37
+ node tests/acceptance/context-menu-host.mjs
38
+ node tests/acceptance/pick-element-gui.mjs --context-only
39
+ ```
40
+
41
+ Host 验收驱动真实 Chromium,覆盖最终取消状态、传播中断、iframe、密码保护、旧令牌及导航动作。GUI 验收覆盖页面自绘菜单、面板默认菜单、复制、Esc、离图释放及预览内外宿主菜单隔离;调用已有实例的方法见[验证与贡献](verification.md);`DSH_CONTEXT_GUI_FIXTURE_URL` 可指定同契约的右键靶页地址。
@@ -56,7 +56,7 @@ provenance / targets / before / after / dispatch / networkDelta / escalation / i
56
56
  | `NOT_INTERACTABLE` | 隐藏/禁用/零面积/离屏/被遮挡/持续运动;`details.reason` 说明原因 |
57
57
  | `WAIT_TIMEOUT` | 等待超时,页面不关闭 |
58
58
  | `INVALID_ARGUMENT` | 参数非法或条件互斥 |
59
- | `BUSY` | 工作区页面被其他 owner 占用 |
59
+ | `BUSY` | 该页队列正被其他会话的写操作占用(短暂忙,不是权限答案);只读工具不占写租约 |
60
60
 
61
61
  只读的 `snapshot`/`node_state`/`wait_for` 同样受 BUSY 限制。
62
62
 
@@ -65,4 +65,4 @@ provenance / targets / before / after / dispatch / networkDelta / escalation / i
65
65
  - 语义来源是主框架的无障碍树;跨 iframe 定位不在范围内。
66
66
  - 快照树是压缩投影:`nodeCount` 与 `digest` 会随构建变化,跨版本差异不算回归。
67
67
  - 视觉缺陷(颜色、遮挡、像素差异)不属 T2,请升级到 [静态视觉](t3-vision.md)。
68
- - 面板的「选择元素」见 [选元素加入聊天框](element-picker.md):在预览中悬浮框选页面元素,点击把节点信息追加到聊天框;v1 只选主帧。
68
+ - 面板的「选择元素」见 [选元素加入聊天框](element-picker.md):在预览中悬浮框选页面元素,点击把节点信息追加到聊天框;主帧与同进程嵌套 frame 都能命中(T2 自身的 ref/selector 仍只覆盖主文档)。
package/docs/t3-vision.md CHANGED
@@ -12,6 +12,7 @@ Source = { kind:"live", scale?:0.1–4, region?:画布CSS Rect(O=0 时与文
12
12
 
13
13
  - 不接受任意文件路径、URL、附件 ID 或调用方 base64,也不从附件服务反读图像。
14
14
  - `live` 省略 `scale` 时使用本次调用开始时冻结的共享 `view.scale`。
15
+ - T3 原始源图不含面板标注;`browser_screenshot` 的 `includeAnnotations:true` 不传播到 T3。用户指代面板标注时,先读 `browser_state.annotations` 并用带标注截图查看,详见[截图与图片交付](capture-p0.md)。T3 的 `annotations` 仅给输出图片添加后处理标记。
15
16
  - `product` 按工作区隔离:每 scope 16 张、合计 128 MiB、单张 32 MiB / 16M 像素;页面导航或关闭后仍可离线分析,但 `act` 的可操作资格失效;不跨重启恢复。
16
17
 
17
18
  ## 六个工具
@@ -20,7 +21,7 @@ Source = { kind:"live", scale?:0.1–4, region?:画布CSS Rect(O=0 时与文
20
21
  | --- | --- | --- |
21
22
  | `browser_vision_image` | 裁剪、缩放、预处理、统计、识别、标注并交付 PNG | 处理顺序固定:source → crop → resize → preprocess → 统计/识别 → 标注 → 编码;分析与识别在标注之前 |
22
23
  | `browser_vision_find` | 颜色/连通域/边缘/模板定位 | 无匹配是**成功空列表**;`semanticName` 恒 `null`,候选不是可操作 ref |
23
- | `browser_vision_compare` | 两图 YIQ 差异与标注差异图 | 两图必须同像素尺寸,否则 `VISION_SIZE_MISMATCH` |
24
+ | `browser_vision_compare` | 两图 YIQ 差异与标注差异图 | 两图必须同像素尺寸,否则 `VISION_SIZE_MISMATCH`;两图几何映射不兼容时 `verdict=BLOCKED`、`reason=mapping-incompatible`(位图比率照算,放在 `ratioVerdict`),只有显式 `bitmapOnly:true` 才返回纯位图结论并标 `bitmap-only` |
24
25
  | `browser_vision_act` | 按截图坐标派发真实输入 | 六步校验全过才派发,任何一步不过**零派发**;`verdict` 固定 `INCONCLUSIVE(dispatch-only)` |
25
26
  | `browser_vision_baseline` | 受管基线的增删改查 | 不可变 revision、HEAD CAS、删除是墓碑 |
26
27
  | `browser_vision_check` | 回归与六类缺陷用例 | 整批先纯参数校验再逐 case 执行;不导航、不建基线、不读 DOM |
@@ -59,6 +60,10 @@ browser_vision_image / browser_vision_compare # 前后对比
59
60
  browser_vision_baseline create → browser_vision_check # 建基线并做回归
60
61
  ```
61
62
 
63
+ ## 颜色参数的写法
64
+
65
+ 识别/缺陷类参数里的颜色(`color`、`background` 等)一律是**整数数组**:`[r,g,b]` 或 `[r,g,b,a]`,每通道 0–255;写成 `{r,g,b}` 对象会返回 `INVALID_ARGUMENT`,detail 为 `expected-rgba-array-3`(或 `-4`)。`tolerance` 是可选整数。
66
+
62
67
  ## 错误
63
68
 
64
69
  | 错误码 | 含义 |
@@ -9,6 +9,7 @@ browser_dynamic_session { action:"start", config:{ captureVisual:true, source:"s
9
9
  ```
10
10
 
11
11
  - `source:"screencast"` 复用面板预览的 JPEG 流(不额外起流,不打断预览);`source:"screenshot-loop"` 用 PNG 精确采样,提供真实采样区间但 `sourceTimestamp:null`。
12
+ - 两种帧源均不含面板标注;`includeAnnotations` 仅用于 `browser_screenshot`,不改变 T4 采帧。用户指代标注时另读 `browser_state.annotations` 并截取 `includeAnnotations:true` 的静态图片,不能把它作为 T4 帧序列证据。
12
13
  - `captureVisual:false` + `networkSessionId` 建立**纯网络关联视图**:不启动采集、不采帧。
13
14
  - `action` 支持 `start`/`stop`/`status`/`reset`/`extend`。
14
15
  - `extend` 只对 `running` 会话生效:单次 ≤ 60000 ms,自 `startedAt` 累计 ≤ 300000 ms;`stopped`/`retained` 返回 `DYNAMIC_SESSION_NOT_ACTIVE`,**不会复活会话**。