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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 bloodarea
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -8,7 +8,18 @@ Let AI agents operate, inspect, test and verify real user interfaces — with ev
8
8
 
9
9
  **English** | [简体中文](README_zh.md)
10
10
 
11
- > **Status: pre-release.** The Web/Chromium channel is implemented across all four tiers: 36 agent tools plus a debug panel for programmatic inspection, structured GUI actions, static visual analysis, dynamic frame/timing analysis, network capture, multi-tab page groups, shared viewport control, a hybrid canvas, an element picker and client-side annotations. Other environments are planned and stay disabled in the runtime picker. **Install:** `dsh plugin --profile web add dsh-agent-team-ui-testing`. Capabilities, limits and verification are documented in [docs/capability-tiers.md](docs/capability-tiers.md) and [docs/verification.md](docs/verification.md).
11
+ <p>
12
+ <a href="https://www.npmjs.com/package/dsh-agent-team-ui-testing"><img src="https://img.shields.io/npm/v/dsh-agent-team-ui-testing?style=flat-square&amp;logo=npm&amp;logoColor=white" alt="npm version"></a>
13
+ <a href="https://github.com/bloodarea/dsh-agent-team-ui-testing/stargazers"><img src="https://img.shields.io/github/stars/bloodarea/dsh-agent-team-ui-testing?style=flat-square&amp;logo=github" alt="GitHub stars"></a>
14
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/bloodarea/dsh-agent-team-ui-testing?style=flat-square" alt="License: MIT"></a>
15
+ <a href="https://nodejs.org/"><img src="https://img.shields.io/badge/node-%3E%3D22-339933?style=flat-square&amp;logo=nodedotjs&amp;logoColor=white" alt="Node.js >= 22"></a>
16
+ <a href="docs/getting-started.md"><img src="https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-0078D6?style=flat-square" alt="Platforms"></a>
17
+ <a href="docs/tools.md"><img src="https://img.shields.io/badge/tools-37-brightgreen?style=flat-square" alt="Agent tools"></a>
18
+ <a href="https://github.com/deepseek-ai/deepseek-harness"><img src="https://img.shields.io/badge/DSH-plugin-4B32C3?style=flat-square" alt="DSH plugin"></a>
19
+ <a href="https://github.com/bloodarea/dsh-agent-team-ui-testing/releases"><img src="https://img.shields.io/badge/status-pre--release-orange?style=flat-square" alt="Status"></a>
20
+ </p>
21
+
22
+ > **Status: pre-release.** The Web/Chromium channel is implemented across all four tiers: 37 agent tools plus a debug panel for programmatic inspection, structured GUI actions, static visual analysis, dynamic frame/timing analysis, network capture, multi-tab page groups, shared viewport control, a hybrid canvas, an element picker and client-side annotations. Other environments are planned and stay disabled in the runtime picker. **Install:** `dsh plugin --profile web add dsh-agent-team-ui-testing`. Capabilities, limits and verification are documented in [docs/capability-tiers.md](docs/capability-tiers.md) and [docs/verification.md](docs/verification.md).
12
23
 
13
24
  ## What is this?
14
25
 
@@ -32,9 +43,10 @@ This project focuses on:
32
43
 
33
44
  ## What works today
34
45
 
35
- - **Shared browser runtime** — one Chromium per host (the system Chrome/Chromium binary; the plugin does not bundle or install a browser), headless by default with an optional headed mode in the Web view (off-screen hidden window + persistent profile); each DSH workspace gets its own page, URL, viewport, history, network buffer and lease. Cookies and storage are shared by the active Chromium profile.
36
- - **36 agent tools** across programmatic, structured GUI, static vision, dynamic behavior, network debugging and per-workspace tab management.
37
- - **Multi-tab page group (P0-E)** — each workspace keeps a primary page plus up to three child tabs, with per-tab leases, per-agent tab selection and popup adoption kept under the same isolation rules.
46
+ - **Shared browser runtime** — one browser per host and per runtime provider (the system Chrome/Chromium or Edge binary; the plugin does not bundle or install a browser), headless by default with an optional headed mode in the Web view (off-screen hidden window + persistent profile); each DSH workspace gets its own page, URL, viewport, history, network buffer and lease. Cookies and storage are shared by the profile of the active provider.
47
+ - **Runtime providers** — the web target environment defaults to `chrome` and also has a wired `edge` provider: its own executable candidates, its own `DSH_UI_TESTING_EDGE` override, its own profile directory and a post-connect product-identity check, reusing the same CDP stack. **A real Edge launch is not verified**: while Edge stays disabled its dependency axis is `not-probed` (disabled providers are never probed); once enabled on a machine without an Edge binary it reports `failed / executable-missing` on the Edge card, and enabling it opens no page. In the settings page the group header is the target-environment switch and each card is a runtime-provider switch; turning the total switch off greys the per-provider switches out while keeping their saved values.
48
+ - **37 agent tools** across programmatic, structured GUI, static vision, dynamic behavior, network debugging, per-workspace tab management and annotations.
49
+ - **Multi-tab page group (P0-E)** — page groups are keyed by (workspace, provider, team root): one team root shares a primary page plus up to three child tabs, while a different independent session or root gets its own group it can neither see nor write. Child tabs (max three) are counted per (workspace, provider) across roots; global live pages stay 8. Per-agent work/observation selections, long-lived ownership and Lead acquire/release/transfer control come on top of the short per-tab lease.
38
50
  - **Dynamic behavior (T4)** — real CDP screencast plus a screenshot-loop path, a bounded frame store with integrity counters, seven deterministic motion analyses, WAAPI motion control with restore, region-only frame delivery, and six-direction frame↔network event correlation.
39
51
  - **Debug panel** — live preview, responsive size controls, shared viewport state, a hybrid canvas, and a client-side annotation layer that agents can read back.
40
52
  - **Element picker** — select an element in the panel preview to append its node description to the chat as an `@` reference chip; the model receives the full text block and the page is not modified.
@@ -45,7 +57,8 @@ This project focuses on:
45
57
  | Group | Tools | What it covers |
46
58
  | --- | --- | --- |
47
59
  | **Programmatic** (10) | `browser_navigate` `browser_eval` `browser_screenshot` `browser_input` `browser_network` `browser_computed_style` `browser_http_request` `browser_viewport` `browser_state` `browser_capabilities` | Navigation, JavaScript, console/network inspection, independent HTTP requests, computed styles, screenshots, raw mouse/wheel/keyboard input, shared viewport state and capability reporting |
48
- | **Page group** (1) | `browser_tabs` | Primary + up to three child tabs per workspace, per-tab leases, agent tab selection and tab lifecycle (list/current/select/open/close/wait) |
60
+ | **Annotations** (1) | `browser_annotation` | Read/draw/erase/clear the annotation document the human sees in the debug panel: six shape types with colour, stroke width and fill/stroke style, append-only draws, revision CAS and a hard foreground requirement |
61
+ | **Page group** (1) | `browser_tabs` | Primary + up to three child tabs per (workspace, provider, root), per-tab short leases, per-agent work/observation selection, ownership + Lead control (acquire/release/transfer) and tab lifecycle (list/current/select/open/close/wait/acquire/release/transfer) |
49
62
  | **Structured GUI** (7) | `browser_snapshot` `browser_click` `browser_type` `browser_scroll` `browser_fill_form` `browser_node_state` `browser_wait_for` | Snapshot → ref targeting, trusted input, forms, node state and waits, with structured evidence and typed errors |
50
63
  | **Static vision** (6) | `browser_vision_image` `browser_vision_find` `browser_vision_compare` `browser_vision_act` `browser_vision_baseline` `browser_vision_check` | Crop/zoom/preprocess/annotate PNGs, deterministic colour/component/edge/template finders, YIQ diff, coordinate input, managed baselines and visual assertions |
51
64
  | **Dynamic** (5) | `browser_dynamic_session` `browser_motion_observe` `browser_motion_analyze` `browser_motion_control` `browser_dynamic_correlate` | Bounded frame capture with integrity counters, region-only frame delivery, seven motion/transition analyses, WAAPI pause/seek/rate/restore, and six-direction frame↔network correlation |
@@ -58,7 +71,7 @@ The panel is the human window into the same runtime the agent uses.
58
71
  - **Live preview** of the workspace page, driven by a screencast with a polling fallback.
59
72
  - **Size bar** — 宽屏 / 横屏 / 平板 / 手机 presets as a segmented control, X/Y canvas position, width × height, display scale, and a `拖拽 | 适应窗口 | 默认` mode selector. Preset clicks reset X/Y and keep the chosen display mode; the selected preset shows a green dot and is mutually exclusive with drag mode.
60
73
  - **Hybrid canvas** — the viewport can be positioned like a canvas: a signed origin, negative coordinates and blank areas, a dark checkerboard in the panel (pure white in screenshots), drag-to-pan and screen-anchored wheel zoom.
61
- - **Annotation mode** — circle, rectangle, line, arrow, brush, text and eraser, plus fill/outline, colour, width and clear-canvas. Drawings are anchored in canvas coordinates, the page receives no input while annotating, and the document is published so agents can read it through `browser_state.annotations`.
74
+ - **Annotation mode** — six shapes (circle/ellipse, rectangle, line, arrow, brush and text), plus eraser, fill/outline, colour, width and clear-canvas controls. Drawings are anchored in canvas coordinates and the page receives no input while annotating. When a user refers to annotations, drawings, circles, boxes, arrows or handwriting, agents read `browser_state.annotations` and call `browser_screenshot {includeAnnotations:true}` to see the overlay. The screenshot renders the Host-accepted, submitted document with the same shape semantics and region/scale mapping as the panel; drafts, toolbars and selection controls are excluded. `includeAnnotations` is a boolean, default `false`; default screenshots and T3/T4 source images omit panel annotations. See [capture and annotation boundaries](docs/capture-p0.md).
62
75
  - **Shared state** — agent and panel read the same viewport; updates use `expectedRevision`, and conflicts are reported instead of silently overwriting.
63
76
 
64
77
  ### Evidence and safety
@@ -71,40 +84,162 @@ The panel is the human window into the same runtime the agent uses.
71
84
  - Runtime access is scoped to a DSH workspace; mutating page work is serialized through a lease and a serial queue.
72
85
  - Navigation is restricted to approved targets (loopback by default; private ranges blocked unless explicitly enabled), with DNS, redirect and popup checks.
73
86
 
74
- ## How it works
75
-
76
- ```text
77
- DSH Agent / Agent Team
78
- │
79
- ▼
80
- UI testing plugin runtime
81
- ├─ shared Chromium (headless by default; headed available in Web view)
82
- ├─ one page per DSH workspace (URL, viewport, history, network, lease)
83
- ├─ agent tool surface (36 tools)
84
- └─ debug panel (preview, size bar, canvas, annotations)
85
- │
86
- ▼
87
- Observations, evidence and verdicts
87
+ ## Architecture design philosophy
88
+
89
+ > **In one sentence:** the AI agent decides *what to test and how to judge it*; the plugin *connects, controls, observes, and preserves evidence*; and the target runtime *actually runs the application*. **Web / Chromium is the implemented target today.** The three adapter contracts (`TargetAdapter` / `CapabilityAdapter` / `P0Provider`), the `core/` + `targets/` split, host-authoritative config, per-step tool admission and environment-aware Skill discovery are implemented for Web; the `engines/` boundary and other platform adapters remain planned.
90
+
91
+ ### Three perspectives on the same test
92
+
93
+ ```mermaid
94
+ mindmap
95
+ root((UI automation testing))
96
+ Execution architecture
97
+ Five-node control chain
98
+ Commands and observations
99
+ Shared human debug view
100
+ Capability architecture
101
+ T1 Programmatic
102
+ T2 Structured GUI
103
+ T3 Static vision
104
+ T4 Dynamic vision
105
+ Infrastructure architecture
106
+ P0-A Image capture
107
+ P0-B Network evidence
108
+ P0-C Viewport control
109
+ P0-D Canvas and annotations
110
+ P0-E Page groups and targets
88
111
  ```
89
112
 
90
- Only the Web/Chromium adapter is implemented. Desktop, Android, TUI, HarmonyOS, game and 3D adapters are planned; each must report its real capabilities instead of pretending to support the same operations.
113
+ | Perspective | Question it answers | Implemented Web example |
114
+ | --- | --- | --- |
115
+ | **Execution architecture** | Who requests an action, who executes it, and how does the result return? | DSH Agent → plugin → CDP → Chromium → page, with results flowing back |
116
+ | **Capability architecture** | Which method provides sufficient evidence for the question? | T1 programmatic, T2 structured GUI, T3 single-frame vision, T4 temporal/frame vision |
117
+ | **Infrastructure architecture** | How are capture, network data, viewports, evidence, and shared state managed reliably? | P0-A–P0-E plus workspace sessions, leases, safety, and evidence mechanisms |
118
+
119
+ These are **three views of one system**, not three separate applications or sequential network layers. A single `browser_click` travels through the execution chain, exercises a T2 capability, and relies on shared infrastructure to retain reviewable information.
120
+
121
+ ### The five-node debug chain: from agent to running application
122
+
123
+ ```mermaid
124
+ flowchart TB
125
+ A["① AI Agent<br/>DeepSeek / Agent Team"]
126
+ C["② Debug Controller<br/>Unified plugin-side control"]
127
+ P["③ Communication Interface<br/>CDP / ADB / RPC / IPC"]
128
+ B["④ Debug Backend<br/>Built-in, third-party, or self-developed"]
129
+ R["⑤ Target Runtime<br/>Actual browser / application / device"]
130
+ A <-->|"Tasks, observations, verdicts"| C
131
+ C <-->|"Commands, events, evidence"| P
132
+ P <-->|"Protocol messages"| B
133
+ B <-->|"Operations and state"| R
134
+ ```
135
+
136
+ 1. **AI Agent** interprets the goal, selects T1–T4 methods, and evaluates whether evidence satisfies acceptance criteria. It may be DeepSeek in DSH or a coordinated Agent Team.
137
+ 2. **Debug Controller** is the plugin's unified entry point for tools, authorization, workspace and target selection, sessions, concurrency, state, and evidence.
138
+ 3. **Communication Interface** transports commands and events using CDP, ADB, RPC, IPC, or other platform-specific mechanisms. **The upper-level capability contract is standardized; the wire protocol need not be.**
139
+ 4. **Debug Backend** executes commands and observes the runtime. It may be Chromium's built-in DevTools backend, an existing OS automation facility, or a future self-developed companion program.
140
+ 5. **Target Runtime** actually hosts the application under test: Chromium, Android, desktop OS, game runtime, etc. It normally exists outside the plugin.
141
+
142
+ These are **logical responsibilities, not five mandatory processes**. Commands flow down; observations, frames, logs, and errors flow up. The human **debug panel is a side-channel view and control surface** for the same managed session, rather than a second independent copy of the tested page.
143
+
144
+ ### Cross-environment reuse: `core`, `targets`, and `engines`
145
+
146
+ The five-node chain describes runtime responsibilities. These three module boundaries describe code ownership and reuse; they do **not** correspond one-to-one with the five nodes.
147
+
148
+ ```mermaid
149
+ flowchart TB
150
+ AI["DSH Agent / Agent Team"] --> CORE["core · Unified controller<br/>Sessions, tools, permissions, evidence"]
151
+ CORE --> CONTRACT["Target Capability Contract<br/>Requests / results / errors / discovery"]
152
+ CONTRACT --> WEB["targets/web · Web Adapter"]
153
+ CONTRACT --> AND["targets/android · Android Adapter (planned)"]
154
+ CONTRACT --> MINI["targets/mini-program · Mini Program Adapter (planned)"]
155
+ WEB --> CDP["CDP"] --> CHROME["Chromium built-in Debug Backend"] --> PAGE["Web Runtime"]
156
+ AND --> ADB["ADB / RPC"] --> AUTOMATE["UI Automator / optional custom APK"] --> PHONE["Android Runtime"]
157
+ MINI --> API["Platform automation API"] --> MP["Platform debug backend"] --> MINIAPP["Mini Program Runtime"]
158
+ ENGINES["engines · Self-developed executors, if needed"] -. "Builds / supplies optional APK" .-> AUTOMATE
159
+ ```
160
+
161
+ | Proposed module boundary | Responsibility | Relationship to the five nodes |
162
+ | --- | --- | --- |
163
+ | **`core/`** | Unified tools, session/lease control, capability selection, shared P0 processing, evidence, errors, permissions, debug-view state | Primarily node ② |
164
+ | **`targets/`** | Environment adapters translate common capability requests into CDP/ADB/etc., normalize results, and honestly report supported/unsupported operations | Bridges node ② to nodes ③–⑤ |
165
+ | **`engines/`** | **Self-developed** executors that need separate builds or deployment, such as a possible Android accessibility Companion APK | Optional implementation of node ④ |
166
+
167
+ `targets/web` does not mean vendoring Chrome or Playwright. Nor does `engines/android` imply an APK is required: ADB / UI Automator may be sufficient for initial Android automation. **`core/` and `targets/` are now real directories**: `core/` owns the contracts, config, identity/geometry/clock, evidence and six-axis readiness; `targets/web` is the real Web adapter and `targets/fake` is a test-only contract fixture. `engines/` remains the reserved boundary for self-developed executors. The CDP integration and the 37 tool implementations still live in `index.js` (composition root / Web implementation), but admission, config and execution all go through the adapter boundary.
168
+
169
+ ### Capability architecture: choose T1 / T2 / T3 / T4 as needed
91
170
 
92
- ## Capability tiers
171
+ Tiers classify **testing methods and the evidence they can supply**. They are **selection and fallback levels**, not a mandatory T1→T2→T3→T4 pipeline. Prefer the least expensive reliable method; escalate or combine methods only when the evidence is insufficient.
93
172
 
94
- The tool surface is organized as **selection and fallback levels**, not a mandatory pipeline. Pick the lowest-cost reliable path; escalate when it cannot produce the required evidence.
173
+ | Tier | Question | Implemented Web example | Representative agent tools |
174
+ | --- | --- | --- | --- |
175
+ | **T1 · Programmatic** | Is the API, program state, or runtime behavior correct? | CDP/HTTP/JavaScript, console, computed styles, page state | `browser_eval`, `browser_http_request`, `browser_network` |
176
+ | **T2 · Structured GUI** | Which element is present, and can it be operated correctly? | DOM/accessibility snapshots, `ref` targeting, forms, trusted input | `browser_snapshot`, `browser_click`, `browser_fill_form` |
177
+ | **T3 · Static vision** | Does one frame show a layout, color, or pixel-level defect? | PNG preprocessing, color/template matching, managed baselines and diffs | `browser_vision_find`, `browser_vision_compare`, `browser_vision_check` |
178
+ | **T4 · Dynamic vision** | Are transitions, animations, drags, and time-dependent behavior correct? | CDP screencast / screenshot loop, frame timing, deterministic motion analysis, frame↔network correlation | `browser_dynamic_session`, `browser_motion_analyze`, `browser_dynamic_correlate` |
95
179
 
96
- | Level | Focus | Typical use |
180
+ **A successful T1 call is not a UI pass; a dispatched click is not proof of a successful login.** Select the observations relevant to the acceptance criteria. Current T3/T4 algorithms are deterministic and do not embed VLM/OCR; the AI agent can reason over the returned evidence. Adapters must report what their runtimes actually support, not invent UI trees or network telemetry to fill all four tiers.
181
+
182
+ ### Infrastructure architecture: P0 is the shared foundation
183
+
184
+ P0 supports multiple tiers. T3 and T4 both depend on image capture; T1 and T4 can consume network events. **P0-A–P0-E are the names of today's Web facilities; cross-platform reuse requires separating portable data models from browser-specific semantics.**
185
+
186
+ | Infrastructure | Implemented Web form | Portable boundary / important caveat |
97
187
  | --- | --- | --- |
98
- | **T1** Programmatic | CDP, HTTP, JavaScript, network, console | API checks, network faults, page state, performance data |
99
- | **T2** Structured GUI | DOM, accessibility tree, snapshot/ref, trusted input | Buttons, forms, navigation, interaction tests |
100
- | **T3** Static vision | Screenshots, deterministic pixel analysis, coordinate actions | Layout, colour, occlusion, canvas, chart and style defects |
101
- | **T4** Dynamic vision | Frame sequences, timing, motion reasoning | Animation, transitions, dragging and time-dependent behavior — escalates from T3 when a single image cannot prove motion |
188
+ | **P0-A · Image capture** | CDP screenshots, validated PNGs, canvas-space regions, dimensions, evidence storage | Common capture/Frame model and coordinate mapping; acquisition remains adapter-specific |
189
+ | **P0-B · Network evidence** | CDP events, timelines/traces, JSONL/HAR, throttling and mocks | Common event/evidence model; other devices/processes may expose less traffic |
190
+ | **P0-C · Viewport control** | Shared viewport dimensions and display scale, revisions/conflicts, input-coordinate constraints | Common display-space/coordinate mapping; physical screens cannot always be freely resized |
191
+ | **P0-D · Hybrid canvas and annotations** | Preview canvas, pan/zoom, anchored annotations readable by agents | Shared human/agent observations; capture-to-input coordinate mapping must remain accurate |
192
+ | **P0-E · Page group management** | Workspace primary + up to three child tabs, leases, `selectedTab` | Generalize target/window/session selection; other platforms may have no browser tabs |
193
+
194
+ Workspace authorization, serialized mutations, evidence validation, typed errors, state revisions, and human reviewability are further cross-cutting concerns shared by the execution and capability layers.
195
+
196
+ ### Walk through an implemented Web testing path
197
+
198
+ Example request: “Check that the login form can be operated, observe feedback after submission, and retain evidence.” This is an **illustrative call path**, not a claim that a real login test has passed.
199
+
200
+ ```mermaid
201
+ sequenceDiagram
202
+ actor Human as Human
203
+ participant AI as DSH / DeepSeek
204
+ participant Core as Plugin tools and controller
205
+ participant CDP as CDP / Chromium Backend
206
+ participant Web as Web page
207
+ participant Panel as Debug panel
208
+ Human->>AI: Test login UI
209
+ AI->>Core: browser_navigate
210
+ Core->>CDP: Navigate
211
+ CDP->>Web: Load page
212
+ AI->>Core: browser_snapshot (T2)
213
+ Core->>CDP: Inspect DOM / accessibility
214
+ CDP-->>Core: Snapshot and element refs
215
+ Core-->>AI: Targetable elements
216
+ AI->>Core: browser_click / browser_fill_form (T2)
217
+ Core->>CDP: Dispatch input
218
+ CDP->>Web: Trigger interaction
219
+ Web-->>CDP: State, network, frame events
220
+ CDP-->>Core: Observations and capture
221
+ Core-->>AI: Structured action evidence
222
+ AI->>Core: browser_vision_check (T3, if needed)
223
+ Core-->>AI: Visual verdict and evidence
224
+ Core-->>Panel: Same-session frames and state
225
+ AI-->>Human: Findings, evidence, unknowns
226
+ ```
227
+
228
+ Mapped to the three perspectives: **execution** carries `browser_*` calls to Chromium and observations back; **capabilities** select T2 here and optionally T1 network checks, T3 single-frame vision, or T4 temporal analysis; **infrastructure** preserves captures, network evidence, and shared state so the human can inspect the same session. In the real implementation, `browser_http_request` uses an independent HTTP path—not every T1 operation goes through CDP. Panel preview is delivered via screencast and WebSocket, with HTTP polling as a fallback.
229
+
230
+ ### What stays invariant for the next target?
231
+
232
+ - **Stable upper-level semantics:** agents request operations, observations, captures, events, and evidence through a common vocabulary; Web-only features remain target-specific extensions.
233
+ - **Adapters translate, never pretend:** Android may use ADB / UI Automator or an optional companion APK; Mini Program may use a platform's developer tools and automation interfaces. Unsupported capabilities must be reported explicitly.
234
+ - **Reuse analysis, vary acquisition:** T3 pixel comparison, T4 frame analysis, and P0 evidence models are candidates for reuse; CDP capture and Android screen capture use different backends.
235
+ - **Distinguish code ownership from deployment:** `targets/` lives on the controller side, `engines/` holds optional self-developed executors, and real Chrome / Android / Mini Program runtimes live outside the repository.
236
+ - **Load targets on demand:** future target selection should avoid needlessly injecting disabled platforms' tool schemas, skill content, or runtime dependencies into the AI agent's context.
102
237
 
103
- A successful T1 call is never treated as a visual pass. UI-specific acceptance criteria still require UI-level observations.
238
+ **Implementation boundary:** Web / Chromium currently provides 37 tools and T1–T4/P0-related features, subject to the coverage and limitations in [capability tiers](docs/capability-tiers.md), [architecture](docs/architecture.md), and [verification](docs/verification.md). T4 acceptance is still partial. The cross-environment contracts, host-authoritative config, config-driven tool/Skill isolation and six-axis readiness are implemented and tested for Web plus a test-only fake adapter; real desktop, Android, Mini Program and other platform adapters remain proposed work.
104
239
 
105
240
  ## Getting started
106
241
 
107
- **Requirements:** a DSH installation and a local Chrome/Chromium binary. The plugin does not bundle or install a browser; it launches the system binary it resolves (see [docs/getting-started.md](docs/getting-started.md)). A headed Web view additionally needs `DISPLAY` or `WAYLAND_DISPLAY` on POSIX hosts; Windows hosts always provide a window system, so headed mode needs no such variable there. The plugin requires Node.js 22+ (native `WebSocket`); the current DSH release itself requires Node.js 24.9+, so a real DSH host follows the stricter version.
242
+ **Requirements:** a DSH installation and a local Chrome/Chromium binary for the default `chrome` runtime provider. Edge is supported as a second runtime provider with its own executable candidates and its own `DSH_UI_TESTING_EDGE` override; its driver code is wired to the same CDP stack, but a real Edge launch has not been verified on this machine — enabling Edge without an Edge binary makes its dependency axis report `failed / executable-missing` (a disabled provider stays `not-probed`) and opens no page. The plugin does not bundle or install a browser; it launches the system binary it resolves (see [docs/getting-started.md](docs/getting-started.md)). A headed Web view additionally needs `DISPLAY` or `WAYLAND_DISPLAY` on POSIX hosts; Windows hosts always provide a window system, so headed mode needs no such variable there. The plugin requires Node.js 22+ (native `WebSocket`); the current DSH release itself requires Node.js 24.9+, so a real DSH host follows the stricter version.
108
243
 
109
244
  The plugin ships as a DSH plugin bundle and is published on npm as `dsh-agent-team-ui-testing`. There is no compatibility matrix beyond the Node.js and DSH requirements above.
110
245
 
@@ -132,7 +267,7 @@ A typical agent flow is: navigate to the target, inspect state with `browser_sta
132
267
 
133
268
  ## Current limits
134
269
 
135
- - **Web target only.** Web/Chromium is the only implemented target environment and runs on Linux, macOS and Windows hosts. Desktop applications, Android, TUI, HarmonyOS, games and 3D are planned targets and stay disabled in the runtime picker.
270
+ - **Web target only.** Web/Chromium is the only implemented target environment and runs on Linux, macOS and Windows hosts. Desktop applications, Android, Mini Program, TUI, HarmonyOS, games and 3D are planned targets and stay disabled in the runtime picker.
136
271
  - **Vision is deterministic, not semantic.** T3 judges single PNGs and T4 judges frame sequences with deterministic algorithms: no VLM/OCR, no semantic naming, so "what does this text mean" and "looks right to a human" stay out of scope.
137
272
  - **T4 verification is partial.** Of the 44 dynamic-behavior criteria, 12 pass, 20 are partial, 0 fail and 12 were not run — the suite exits non-zero only because required checks were not run, not because they failed. Close/dispose restore, WebGPU rendering, media seeking/stalling, header-verified identity, 16 ms flicker coverage and long-lived connections remain unverified.
138
273
  - **Shared browser profile.** Cookies and storage are shared by the Chromium profile; only page, URL, viewport, history, network buffer and leases are isolated per workspace.
@@ -145,8 +280,8 @@ A typical agent flow is: navigate to the target, inspect state with `browser_sta
145
280
  ## Roadmap
146
281
 
147
282
  - Harden the Web channel and close the remaining dynamic-behavior criteria (12 not run, 20 partial).
148
- - Add the cross-environment test core: typed sessions, observations, assertions and reports.
149
- - Validate desktop (Windows/Linux/macOS), Android, TUI, HarmonyOS, game and 3D adapters by demand and platform feasibility.
283
+ - Gradually extract a common capability contract and `core/targets/engines` module boundaries while preserving existing Web tools; build the cross-environment test core (typed sessions, observations, assertions, reports).
284
+ - Validate desktop (Windows/Linux/macOS), Android, Mini Program, TUI, HarmonyOS, game and 3D adapters by demand and platform feasibility.
150
285
  - Add multi-agent scheduling, human takeover and release hardening.
151
286
 
152
287
  ## Documentation
@@ -155,7 +290,7 @@ A typical agent flow is: navigate to the target, inspect state with `browser_sta
155
290
  | --- | --- |
156
291
  | [docs/index.md](docs/index.md) | Documentation index |
157
292
  | [docs/getting-started.md](docs/getting-started.md) | Requirements, install, debug panel and first calls |
158
- | [docs/tools.md](docs/tools.md) | All 36 tools, parameters and error classes |
293
+ | [docs/tools.md](docs/tools.md) | All 37 tools, parameters and error classes |
159
294
  | [docs/capability-tiers.md](docs/capability-tiers.md) | T1–T4 selection model, P0 infrastructure and known limits |
160
295
  | [docs/viewport-control.md](docs/viewport-control.md) | Shared viewport, hybrid canvas and annotations |
161
296
  | [docs/capture-p0.md](docs/capture-p0.md) | Screenshots, image delivery and file lifecycle |
@@ -186,7 +321,7 @@ When filing an issue, include the DSH version/commit, platform and runtime versi
186
321
 
187
322
  ## License
188
323
 
189
- The repository does not currently declare a license. Add an explicit license before redistribution or reuse; third-party code incorporated later must retain its applicable notices.
324
+ Released under the [MIT License](LICENSE). Copyright (c) 2026 bloodarea. Third-party code incorporated later must retain its applicable notices.
190
325
 
191
326
  ---
192
327
 
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Rasterize the panel's accepted annotation document in a CDP isolated world.
3
+ * This function is serialized into Chromium: it must close over no Host values. Its detached
4
+ * OffscreenCanvas never changes the target DOM, styles, focus, viewport or preview, and needs no
5
+ * image/network loads (including on pages with img-src 'none'). Geometry and styles mirror
6
+ * client.js renderAnnotShape/annotArrowGeometry; the input is Host-normalized shape data only.
7
+ */
8
+ export async function renderAnnotationOverlay({ shapes, region, width, height }) {
9
+ const canvas = new OffscreenCanvas(width, height);
10
+ const ctx = canvas.getContext('2d');
11
+ if (!ctx) throw new Error('annotation 2d context unavailable');
12
+ ctx.setTransform(width / region.width, 0, 0, height / region.height,
13
+ -region.x * width / region.width, -region.y * height / region.height);
14
+ const rounded = value => Math.round(value * 100) / 100;
15
+ for (const shape of shapes) {
16
+ ctx.fillStyle = shape.color;
17
+ ctx.strokeStyle = shape.color;
18
+ ctx.lineWidth = shape.strokeWidth;
19
+ ctx.lineCap = 'butt';
20
+ ctx.lineJoin = 'miter';
21
+ ctx.beginPath();
22
+ if (shape.type === 'rect' || shape.type === 'circle') {
23
+ if (shape.type === 'rect') ctx.rect(shape.x, shape.y, shape.width, shape.height);
24
+ else ctx.ellipse(shape.cx, shape.cy, shape.rx, shape.ry, 0, 0, Math.PI * 2);
25
+ if (shape.fill) ctx.fill();
26
+ if (shape.stroke) ctx.stroke();
27
+ } else if (shape.type === 'line') {
28
+ ctx.lineCap = 'round';
29
+ ctx.moveTo(shape.x1, shape.y1);
30
+ ctx.lineTo(shape.x2, shape.y2);
31
+ ctx.stroke();
32
+ } else if (shape.type === 'arrow') {
33
+ const dx = shape.x2 - shape.x1, dy = shape.y2 - shape.y1;
34
+ const length = Math.hypot(dx, dy);
35
+ const headLength = Math.min(3 * shape.strokeWidth, length);
36
+ const headWidth = Math.min(2 * shape.strokeWidth, length);
37
+ const ux = length ? dx / length : 0, uy = length ? dy / length : 0;
38
+ const bx = shape.x2 - ux * headLength, by = shape.y2 - uy * headLength;
39
+ ctx.lineCap = 'round';
40
+ ctx.moveTo(shape.x1, shape.y1);
41
+ ctx.lineTo(bx, by);
42
+ ctx.stroke();
43
+ if (length) {
44
+ // The panel rounds polygon/polyline point strings to two decimal places.
45
+ ctx.beginPath();
46
+ ctx.moveTo(rounded(shape.x2), rounded(shape.y2));
47
+ ctx.lineTo(rounded(bx - uy * headWidth / 2), rounded(by + ux * headWidth / 2));
48
+ ctx.lineTo(rounded(bx + uy * headWidth / 2), rounded(by - ux * headWidth / 2));
49
+ ctx.closePath();
50
+ ctx.fill();
51
+ }
52
+ } else if (shape.type === 'brush') {
53
+ ctx.lineCap = 'round';
54
+ ctx.lineJoin = 'round';
55
+ shape.points.forEach((point, index) => {
56
+ // Host wire data stores a point as [x,y]; the panel converts it to an object only
57
+ // when importing into its local editor. Render the accepted wire document directly.
58
+ if (index) ctx.lineTo(rounded(point[0]), rounded(point[1]));
59
+ else ctx.moveTo(rounded(point[0]), rounded(point[1]));
60
+ });
61
+ ctx.stroke();
62
+ } else if (shape.type === 'text') {
63
+ ctx.font = '400 20px system-ui, -apple-system, "Segoe UI", sans-serif';
64
+ ctx.textBaseline = 'alphabetic';
65
+ // SVG text-before-edge aligns the font's ascent box, whereas Canvas 'top' aligns its em
66
+ // square. Use font metrics so CJK/fallback fonts retain the panel's baseline.
67
+ // SVG's default white-space processing collapses interior spaces and trims the edges.
68
+ const text = shape.text.replace(/[\t\r\n]/g, ' ').replace(/ +/g, ' ').trim();
69
+ const ascent = ctx.measureText(text).fontBoundingBoxAscent;
70
+ ctx.fillText(text, shape.x, shape.y + ascent);
71
+ }
72
+ }
73
+ const blob = await canvas.convertToBlob({ type: 'image/png' });
74
+ const bytes = new Uint8Array(await blob.arrayBuffer());
75
+ let binary = '';
76
+ for (let offset = 0; offset < bytes.length; offset += 32768) {
77
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
78
+ }
79
+ return { base64: btoa(binary), width, height };
80
+ }
81
+
82
+ /** Source-over a transparent annotation raster onto the already-captured browser pixels. */
83
+ export function compositeAnnotationPixels(page, overlay) {
84
+ if (page.width !== overlay.width || page.height !== overlay.height) {
85
+ throw new Error('annotation raster dimensions differ from page capture');
86
+ }
87
+ // Chromium emits RGBA for the transparent overlay; refusing another model avoids interpreting
88
+ // a missing alpha channel as opaque. Page PNGs may be RGB, RGBA or grayscale.
89
+ if (overlay.channels !== 4) throw new Error('annotation raster must contain RGBA pixels');
90
+ const rgba = new Uint8Array(page.width * page.height * 4);
91
+ if (page.channels === 4) rgba.set(page.data);
92
+ else for (let pixel = 0; pixel < page.width * page.height; pixel++) {
93
+ const source = pixel * page.channels, target = pixel * 4;
94
+ rgba[target] = page.data[source];
95
+ rgba[target + 1] = page.channels === 3 ? page.data[source + 1] : page.data[source];
96
+ rgba[target + 2] = page.channels === 3 ? page.data[source + 2] : page.data[source];
97
+ rgba[target + 3] = page.channels === 2 ? page.data[source + 1] : 255;
98
+ }
99
+ for (let offset = 0; offset < rgba.length; offset += 4) {
100
+ const alpha = overlay.data[offset + 3];
101
+ if (!alpha) continue;
102
+ const a = alpha / 255, b = rgba[offset + 3] / 255;
103
+ const outAlpha = a + b * (1 - a);
104
+ for (let channel = 0; channel < 3; channel++) {
105
+ rgba[offset + channel] = Math.round((overlay.data[offset + channel] * a
106
+ + rgba[offset + channel] * b * (1 - a)) / outAlpha);
107
+ }
108
+ rgba[offset + 3] = Math.round(outAlpha * 255);
109
+ }
110
+ return { width: page.width, height: page.height, rgba };
111
+ }
package/browser-safety.js CHANGED
@@ -11,6 +11,18 @@ const TAXONOMY = {
11
11
  // Independent HTTP client (docs/tools.md): HTTP timeouts are not CDP timeouts
12
12
  // (they must never trigger the page "close the browser before releasing the queue" path).
13
13
  HTTP_TIMEOUT: ['timeout', true], HTTP_FAILED: ['network', true], HTTP_REDIRECT_LIMIT: ['network', false],
14
+ // Cross-environment foundation (core/errors.js ERROR_TAXONOMY): adapter/config refusals are
15
+ // *classified* outcomes, so they must survive the tool boundary instead of degrading to INTERNAL.
16
+ // Without these entries a disabled environment or a stale config surfaced as `[INTERNAL]`.
17
+ ADAPTER_NOT_FOUND: ['environment', false], ADAPTER_UNSUPPORTED: ['capability', false],
18
+ ADAPTER_DISABLED: ['config', false], CONFIG_CONFLICT: ['config', true],
19
+ CONFIG_INVALID: ['config', false], CONFIG_STALE: ['config', true],
20
+ TARGET_NOT_BOUND: ['target', false], TARGET_GONE: ['target', true],
21
+ GENERATION_MISMATCH: ['state', false], CAPABILITY_UNSUPPORTED: ['capability', false],
22
+ CAPABILITY_UNAVAILABLE: ['environment', true], DEPENDENCY_MISSING: ['environment', false],
23
+ CONNECTION_UNTRUSTED: ['environment', true], PROBE_TIMEOUT: ['environment', true],
24
+ PROVIDER_UNAVAILABLE: ['environment', true], EVIDENCE_INCOMPLETE: ['evidence', false],
25
+ FORBIDDEN: ['capability', false],
14
26
  // Workspace isolation (docs/architecture.md): these must never fall back to INTERNAL.
15
27
  SCOPE_INVALID: ['input', false], SCOPE_NOT_FOUND: ['scope', false],
16
28
  SCOPE_UNAVAILABLE: ['scope', true], SCOPE_FORBIDDEN: ['authorization', false],
@@ -25,6 +37,13 @@ const TAXONOMY = {
25
37
  // concurrency failure, not a bad argument and not a page failure. `state` + retryable=true means
26
38
  // "read the current view and decide again"; the patch was NOT applied and page lastError is kept.
27
39
  VIEWPORT_CONFLICT: ['state', true],
40
+ // P0 annotations (docs/viewport-control.md): the Agent's annotation write surface has two
41
+ // refusals of its own. `ANNOTATION_NOT_FOREGROUND` is a *precondition on the target surface*
42
+ // (nobody has this tab selected in the debug view, so a drawn note would be invisible) and is
43
+ // not retryable by the caller alone — a human has to select the tab. `ANNOTATION_REVISION_CONFLICT`
44
+ // is the optimistic-concurrency loser of `expectRevision`, retryable after re-reading.
45
+ ANNOTATION_NOT_FOREGROUND: ['action', false],
46
+ ANNOTATION_REVISION_CONFLICT: ['state', true],
28
47
  // P0 capture infrastructure (docs/capture-p0.md): the capture pipeline has its own taxonomy so a
29
48
  // bad screenshot never masquerades as INTERNAL. Size/format refusals are not retryable (the same
30
49
  // page would produce the same bytes); a failed CDP capture and a failed output write are.
@@ -83,6 +102,17 @@ const TAXONOMY = {
83
102
  // whose first document cannot be hosted (`capability`). None of them may read as INTERNAL.
84
103
  TAB_NOT_FOUND: ['scope', false], TAB_FORBIDDEN: ['authorization', false],
85
104
  TAB_LIMIT: ['resource', false], POPUP_UNSUPPORTED: ['capability', false],
105
+ // Team/page access model (M1, docs/internal/m0-rulings-20261109.md §7.3). The pre-existing tab
106
+ // codes above only knew about a short lease; long-lived ownership adds *classification* failures
107
+ // that must not degrade to INTERNAL: a control conflict is `state` (read current, decide again —
108
+ // never an automatic BUSY replay), an unprovable identity is `authorization` (no downgrade to a
109
+ // self-made standalone domain), and "cannot observe without disturbing the page" is a
110
+ // `capability` answer, not a page error. TAB_OBSERVATION_STALE is retryable=true because the fix
111
+ // is simply to observe again, while TAB_CONTROL_CHANGED is not: retrying a stale control version
112
+ // would write with authority the caller no longer has.
113
+ TAB_CONTROL_CHANGED: ['state', false], TAB_OWNERSHIP_STALE: ['state', false],
114
+ TAB_IDENTITY_UNAVAILABLE: ['authorization', false], TAB_OBSERVATION_STALE: ['state', true],
115
+ OBSERVATION_UNAVAILABLE: ['capability', false],
86
116
  // T4 extend on a stopped/retained session: the session is gone but starting a new one may
87
117
  // recover, hence scope + retryable (docs/t4-dynamic-behavior.md §extend).
88
118
  DYNAMIC_SESSION_NOT_ACTIVE: ['scope', true],
@@ -104,7 +134,21 @@ export class BrowserError extends Error {
104
134
  }
105
135
  }
106
136
  export function asBrowserError(error) {
107
- return error instanceof BrowserError ? error : new BrowserError('INTERNAL', String(error?.message || error));
137
+ if (error instanceof BrowserError) return error;
138
+ // A classified cross-environment error (core/errors.js `AdapterError`) carries `code`/`category`/
139
+ // `retryable`/`details`. Rebuilding it as a BrowserError keeps the code so a policy refusal reads
140
+ // as `[ADAPTER_DISABLED]` rather than `[INTERNAL]`; the category comes from this module's taxonomy.
141
+ if (error && typeof error.code === 'string' && Object.hasOwn(TAXONOMY, error.code)) {
142
+ const rebuilt = new BrowserError(error.code, String(error.message || error.code));
143
+ if (error.name === 'AdapterError' || error.category !== undefined) {
144
+ rebuilt.name = 'BrowserError';
145
+ rebuilt.adapterCategory = typeof error.category === 'string' ? error.category : null;
146
+ rebuilt.retryable = typeof error.retryable === 'boolean' ? error.retryable : rebuilt.retryable;
147
+ }
148
+ if (error.details !== undefined) rebuilt.details = error.details;
149
+ return rebuilt;
150
+ }
151
+ return new BrowserError('INTERNAL', String(error?.message || error));
108
152
  }
109
153
  export function checkAbort(signal) {
110
154
  if (signal?.aborted) throw new BrowserError('ABORTED', '操作已取消');