dsh-click 0.3.26 → 0.3.28

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.
package/CHANGELOG.md CHANGED
@@ -1,16 +1,25 @@
1
1
  # Changelog
2
2
 
3
- ## [0.3.26] - 2026-10-27
3
+ ## [0.3.28] - 2026-11-13
4
4
 
5
5
  ### Fixed
6
6
 
7
- - **Element-targeted actions now verify the target instead of the whole window (issue #29).** `screen_read` stores a per-element hash; `click`/`type`/`scroll` re-read the tree and allow the action when the addressed element is unchanged, even if an unrelated clock, cursor, spinner, or memory label moved the whole-tree hash. `staleCheckTree` gains `target` and becomes the default; `strict` preserves the old whole-window refusal, and `off` remains the explicit escape hatch. Screenshot-only observations and `key` fall back to the strict whole-tree check. Added unchanged-target and changed-target regression tests.
7
+ - **The helper is now DPI-aware, so every coordinate and screenshot is in physical pixels.** `powershell.exe` starts DPI-unaware, so `GetWindowRect` / `GetSystemMetrics` / `GetClientRect` returned logical pixels while UIA `BoundingRectangle` and the screen DC were physical. On a 200% display that is a clean 2x mismatch: `screen_shot` allocated a 1920x1080 bitmap and BitBlt copied the top-left 1920x1080 of the 3840x2160 surface (a crop, not a scaled image), and elements in the right/bottom half failed the client-area check. The helper now sets Per-Monitor V2 (`SetProcessDpiAwarenessContext(-4)`) before any window API, falling back to `SetProcessDpiAwareness(2)` and `SetProcessDPIAware()`. Fixes the size/unit half of #35.
8
+ - New read-only `metrics` op returns the effective DPI awareness, screen and virtual-screen metrics and the monitor count, so a user can report the DPI state without taking a screenshot.
8
9
 
9
- ## [0.3.25] - 2026-10-26
10
+ ## [0.3.27] - 2026-11-05
10
11
 
11
- ### Added
12
+ ### Changed
13
+
14
+ - **Host 0.2.2 line support.** devDependencies move from `0.2.1-rc.2` to `0.2.2-rc.1`, `@deepseek-ai/cordis` to `^4.1.0`, the include/loader plugins to `^1.1.0`, and schemastery to `~3.19.0` so one copy resolves against `@deepseek-ai/dsh-llm` 0.2.2. Add `@deepseek-ai/dsh-tool-executor-native` and `@deepseek-ai/dsh-session-projection` (both 0.2.2-rc.1) plus `@deepseek-ai/dsh-package-manifest`, which dsh-llm 0.2.2 types import without declaring. `engines.dsh` appends `>=0.2.2-0 <0.3.0` (append-only) and `engines.node` becomes `^22.19.0 || >=24.9.0`.
15
+
16
+ ### Fixed
12
17
 
13
- - **`staleCheckTree: "strict" | "off"` (default `strict`) — an explicit escape hatch for windows with a ticking clock, cursor, spinner, or live memory label (issue #29).** `strict` preserves the existing whole-window tree-hash refusal; `off` skips only the tree hash, while identity, observation age, and (unless `staleCheckPixels: false`) pixel checks still apply. This makes dynamic windows operable today; per-element scoped verification remains the planned default replacement.
18
+ - **Batch tool dispatch in the suites and the Loader runner.** 0.2.2 replaced the single-call `tools.execute({ callId, name, arguments })` with `ToolBatchInput` → `ToolBatchResult`; every call site now builds a one-call batch (`calls: [{ type: 'tool-call', id, name, arguments: JSON.stringify(...) }]`) and projects `results[0].result`, so the assertions keep their flat result shape.
19
+ - **Raw execution is behind a pluggable executor.** The test harness mounts `@deepseek-ai/dsh-tool-executor-native` right after `ToolRuntime`, and the Loader composition profile carries its row; without them `ctx.tools.execute()` has no provider.
20
+ - **Loader failures surface again.** `cordis` 4.1.0 settles a failed row's error on that row's fiber instead of rejecting `loader.await()`, so `scripts/loader-runner.mjs` now awaits every row fiber after `loader.await()`; the invalid-config and default-export negations fail on the real reason instead of a downstream missing-tool symptom. The row-state scan remains as a fallback.
21
+ - **Approval service mounting in the harness.** `ApprovalService` 0.2.2 statically injects `sessionProjections`, so the harness constructs `SessionProjectionRegistry` on the root context before mounting it, and the `systemPrompt` stub gains `context` / `getContextOrder` because the service registers an approval-policy context section.
22
+ - **Invalid-config composition case timeout.** Its three sequential child boots now import the full 0.2.2 host graph and can exceed the 5 s vitest default on a drvfs/Windows mount; the case gets an explicit 30 s budget inside the runner's own 120 s spawn timeout.
14
23
 
15
24
  ## [0.3.24] - 2026-10-19
16
25
 
package/README.en.md ADDED
@@ -0,0 +1,441 @@
1
+ <div align="center">
2
+
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/dsh-click-hero-en-dark.webp">
5
+ <img src="docs/assets/dsh-click-hero-en-light.webp" width="100%" alt="dsh-click: a desktop window's accessibility elements (e12 Edit, e13 Button) are read by screen_read into an observationId; actions cite it via basedOn, cross the freshness, approval and process-identity safety boundary, run through the Windows UIA helper, and land as dsh-click/observed and dsh-click/action audit events">
6
+ </picture>
7
+
8
+ # dsh-click
9
+
10
+ **See the screen, then act — every mutating click is gated, every observation and action is audited.**
11
+
12
+ Native desktop control for DeepSeek Harness (Windows first): an accessibility tree plus pixel hints let text-only models “see”, window-scoped click and input let them “act”, and approval, freshness, and process-identity checks sit in between.
13
+
14
+ [Install](#three-step-setup) · [Configuration](#configuration) · [FAQ](#faq) · [简体中文](README.md) · [English](README.en.md) · [Español](README.es.md) · [Português](README.pt.md) · [हिन्दी](README.hi.md)
15
+
16
+ [![License](https://img.shields.io/badge/license-Apache--2.0-3366CC?style=flat-square&labelColor=2B3245)](LICENSE)
17
+ [![DSH](https://img.shields.io/badge/DSH-%E2%89%A50.2.1%20%3C0.3.0-4B67B4?style=flat-square&labelColor=2B3245)](#compatibility)
18
+ [![npm](https://img.shields.io/npm/v/dsh-click?style=flat-square&labelColor=2B3245&color=CB3837)](https://www.npmjs.com/package/dsh-click)
19
+ [![CI](https://img.shields.io/github/actions/workflow/status/PerryLink/dsh-click/ci.yml?branch=main&label=CI&style=flat-square&labelColor=2B3245)](https://github.com/PerryLink/dsh-click/actions/workflows/ci.yml)
20
+ [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24.9-339933?style=flat-square&labelColor=2B3245)](#compatibility)
21
+
22
+ [![Stars](https://img.shields.io/github/stars/PerryLink/dsh-click?style=flat-square&labelColor=2B3245)](https://github.com/PerryLink/dsh-click/stargazers)
23
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-click?style=flat-square&labelColor=2B3245&color=CB3837)](https://www.npmjs.com/package/dsh-click)
24
+ [![dshfind](https://dshfind.com/api/badge/PerryLink/dsh-click?metric=downloads)](https://dshfind.com/plugins/PerryLink/dsh-click?ref=badge)
25
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/PerryLink/dsh-click/badge)](https://securityscorecards.dev/viewer/?uri=github.com/PerryLink/dsh-click)
26
+ [![DSH Market](https://img.shields.io/badge/DSH_Market-listed-4B67B4?style=flat-square&labelColor=2B3245)](https://dsh.market/)
27
+ [![Gitee](https://img.shields.io/badge/Gitee-mirror-C71D23?style=flat-square&labelColor=2B3245&logo=gitee)](https://gitee.com/perrylink/dsh-click)
28
+
29
+ ```sh
30
+ dsh plugin --profile <name> add dsh-click
31
+ ```
32
+
33
+ </div>
34
+
35
+ <sub>Community plugin, not affiliated with DeepSeek. Canonical repository: <https://github.com/PerryLink/dsh-click> (Gitee is a mirror).</sub>
36
+
37
+ ## What it is
38
+
39
+ `dsh-click` gives the model a complete observe → act loop: `screen_read` returns a window’s accessibility tree (element ids, types, names, rectangles, supported patterns) plus pixel-location hints, or `screen_shot` captures an image; then `click` / `type` / `scroll` / `key` act on that same window by element id or coordinates. Every mutating action must cite a fresh observation (`basedOn`) — a changed screen or an expired observation is refused with a model-readable reason — and crosses `ctx.approval` by default, with the target process identity verified before and after. It does not steal foreground focus by default (`focusFallback: never`), and every observation and action lands as a sanitized `dsh-click/observed` / `dsh-click/action` audit event.
40
+
41
+ <picture>
42
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/dsh-click-flow-v5-dark.svg">
43
+ <img src="docs/assets/dsh-click-flow-v5-light.svg" width="100%" alt="dsh-click boundary two-sided structure: the read-only observe plane on the left produces an observationId; the SAFETY BOUNDARY in the middle checks basedOn, freshness, approval, helper dispatch and process identity, returning refusals to screen_read; the mutating act plane on the right acts through the helper; both sanitized audit events sit on the bottom rail">
44
+ </picture>
45
+
46
+ ## Three-step setup
47
+
48
+ 1. **Install** (pick one channel):
49
+
50
+ ```sh
51
+ # git channel (latest main; equivalent to git+https://github.com/PerryLink/dsh-click.git)
52
+ dsh plugin --profile web add github:PerryLink/dsh-click
53
+ # npm channel (published releases)
54
+ dsh plugin --profile web add dsh-click
55
+ # tarball channel: pnpm pack in this repo, then
56
+ dsh plugin --profile web add ./dsh-click-<version>.tgz
57
+ ```
58
+
59
+ The git channel’s `prepare` script builds with production dependencies only.
60
+
61
+ 2. **Minimal config** (sane defaults; touch `autoApproveWindows` only to exempt specific windows):
62
+
63
+ ```yaml
64
+ - insert:
65
+ - id: dsh-click
66
+ name: dsh-click
67
+ config:
68
+ requireApproval: true
69
+ autoApproveWindows: ['^Notepad']
70
+ focusFallback: never
71
+ ```
72
+
73
+ 3. **Verify it mounts** (after a restart):
74
+
75
+ ```sh
76
+ dsh --profile web --dump-config | grep -A3 'id: dsh-click'
77
+ ```
78
+
79
+ Then ask the agent to look at a window and act — the approval prompt appears for every mutating action:
80
+
81
+ ```text
82
+ > Open Notepad, type "hello", then read back what is on screen.
83
+ ```
84
+
85
+ **Uninstall**: `dsh plugin --profile web remove dsh-click` (or remove the row from the profile patch).
86
+
87
+ > If pnpm reports `ERR_PNPM_IGNORED_BUILDS` for this package (esbuild’s harmless platform-binary validation), add `allowBuilds: { esbuild: true }` to your `pnpm-workspace.yaml` — the `dsh` CLI prints the exact snippet.
88
+
89
+ <details>
90
+ <summary>Install from the DSH Desktop market</summary>
91
+
92
+ Every PerryLink plugin is listed in the built-in DSH Desktop market: **Market → Sources → Add source → paste** `https://perrylink-dsh-catalog.perrylink.workers.dev/catalog-source.json` **→ select**. Installation still requires the market’s npm identity check and your confirmation.
93
+
94
+ </details>
95
+
96
+ ## Core capabilities
97
+
98
+ `dsh-click` splits the native desktop into an auditable observation face and action face:
99
+
100
+ - **`screen_shot`** — captures a window (or the primary screen) and downscales it to `maxScreenshotSide`. With a vision-capable model the result carries the image; text-only models get a text description and keep working.
101
+ - **`screen_read`** — the structured observation: the accessibility tree (element ids, types, names, rectangles, supported patterns) plus pixel-location hints with colors — plain text, no image model required.
102
+ - **`screen_find`** — for applications that expose no accessibility tree, finds clickable text or a described visual target via optional OCR (`ocr.*`, tesseract probed at mount) or visual grounding, returning screen coordinates plus an `observationId`; read-only, never needs approval.
103
+ - **`click` / `type` / `scroll` / `key`** — window-scoped actions addressed by element id or coordinates. Delivery prefers UIA invoke, falls back to posted window messages, and never steals foreground focus by default; `type` takes the UIA `ValuePattern` fast path and otherwise injects real keystrokes through `SendInput` (CJK-safe); `key` reports honestly when an app ignores posted input.
104
+ - **`app_list` / `app_launch`** — enumerate running applications and their visible windows; launch one by name or executable path.
105
+
106
+ Every mutating action crosses one shared safety boundary:
107
+
108
+ 1. **Freshness** — the action must cite a `basedOn` observation; the window is re-captured right before acting, and the action is refused when the pixel hash (`staleCheckPixels`) or the accessibility tree (`staleCheckTree`) changed, or when the observation is older than `maxObservationAgeMs`.
109
+ 2. **Approval** — `ctx.approval` gates every action by default; `autoApproveWindows` title/executable regexes can exempt specific windows (still freshness-checked, process-verified, and audited).
110
+ 3. **Process identity** — the owning process’s pid and executable path are verified before **and** after the act; a mid-act swap refuses loudly (`PROCESS_CHANGED`).
111
+ 4. **Audit** — observations and actions land in the session log as `dsh-click/observed` / `dsh-click/action` events (sanitized, log-only).
112
+
113
+ ```text
114
+ model harness
115
+ │ screen_read ──▶ observationId (+ elements, pixels) ← structured text
116
+ │ click {basedOn, target} ──▶ freshness check ──▶ approval ──▶ helper (UIA)
117
+ │ pixel hash changed? ── refuse + re-observe
118
+ │ pid/exe changed after act? ── PROCESS_CHANGED
119
+ │ ◀── canonical JSON + audit events (dsh-click/action)
120
+ ```
121
+
122
+ ### Tool surface
123
+
124
+ | Tool | Read-only | Needs approval | Notes |
125
+ |---|---|---|---|
126
+ | `screen_shot` | ✅ | — | Returns an `observationId` later actions cite in `basedOn`; image attachment when the model accepts images |
127
+ | `screen_read` | ✅ | — | Accessibility tree + pixel hints; element ids are what actions address |
128
+ | `screen_find` | ✅ | — | OCR/visual grounding when there is no UIA tree; returns coordinate targets + an `observationId` |
129
+ | `click` | | ✅ | Exactly one of `elementId` or `(x, y)`; UIA invoke preferred, posted messages fallback |
130
+ | `type` | | ✅ | Value-pattern elements only; backs up and restores control text on failure |
131
+ | `scroll` | | ✅ | Element (scroll pattern) or window (posted wheel) |
132
+ | `key` | | ✅ | Posted key combinations (`"Ctrl+S"`); apps that ignore posted input refuse loudly |
133
+ | `app_list` | ✅ | — | Running applications and their visible windows |
134
+ | `app_launch` | | ✅ | By name or executable path, with optional arguments |
135
+
136
+ ### Examples
137
+
138
+ These are the plugin’s real entry points; install once, then call one.
139
+
140
+ **Detailed example** — command `app_list`, input:
141
+
142
+ ```json
143
+ {}
144
+ ```
145
+
146
+ Output:
147
+
148
+ ```text
149
+ 1 running application(s) with windows:
150
+ - Demo (pid 4242, C:\Apps\demo.exe)
151
+ - [windowId 1122867] "Demo App" at (10, 20) 800x600
152
+ ```
153
+
154
+ *Observed from `plugins/dsh-click/tests/tools.spec.ts:159-185; renderer at src/tools.ts:933-942`*
155
+
156
+ **Failure branch** — input:
157
+
158
+ ```text
159
+ {}
160
+ ```
161
+
162
+ Output:
163
+
164
+ ```text
165
+ Error: dsh-click backend unavailable on linux: Linux backend is reserved but not implemented yet (Windows-first); only Windows can act today
166
+ ```
167
+
168
+ *Observed from `plugins/dsh-click/src/platform/selection.ts:61-62,81; plugins/dsh-click/src/tools.ts:133-136; plugins/dsh-click/tests/tools.spec.ts:226-245`*
169
+
170
+ **Brief examples**: `screen_read` · `app_list`
171
+
172
+ ## Configuration
173
+
174
+ All tunables are Schemastery `Config` fields (changeable from cordis.yml). An id-targeted override replaces the whole row — restate every key you need. `cordis.patch.yml` documents each key inline.
175
+
176
+ Most-used keys:
177
+
178
+ | Key | Default | Meaning |
179
+ |---|---|---|
180
+ | `requireApproval` | `true` | Gate every mutating action behind approval; observers never ask |
181
+ | `autoApproveWindows` | `[]` | Window-title/executable regexes that skip the approval ask (still freshness-checked, process-verified, and audited) |
182
+ | `focusFallback` | `never` | Whether an action may bring the target window to the foreground as a last resort (`never` / `allow`) |
183
+ | `imageMode` | `auto` | `screen_shot` rendering: `auto` (image when the model accepts images, text otherwise) or `text` |
184
+ | `staleCheckPixels` | `true` | Compare a fresh pixel hash before every action and refuse on change |
185
+ | `staleCheckTree` | `target` | Accessibility-tree freshness: `target` verifies the addressed element (default), `strict` refuses any changed node, `off` skips the tree hash (identity/age/pixel checks still apply) |
186
+ | `maxObservationAgeMs` | `30000` | Maximum age in ms of an observation an action may cite (1000..600000) |
187
+ | `auditSessionEvents` | `true` | Whether to append `dsh-click/observed` / `dsh-click/action` audit events; set `false` to stop audit appends entirely |
188
+
189
+ <details>
190
+ <summary>Full configuration table (17 rows)</summary>
191
+
192
+ | Key | Default | Meaning |
193
+ |---|---|---|
194
+ | `requireApproval` | `true` | Gate every mutating action behind approval; observers never ask |
195
+ | `autoApproveWindows` | `[]` | Window-title/executable regexes that skip the approval ask (still freshness-checked, process-verified, and audited) |
196
+ | `auditSessionEvents` | `true` | Append `dsh-click/observed` / `dsh-click/action` session audit events. The adaptive gate already skips the append on envelope-less hosts (rc.6–rc.8, 0.1.1-rc.2, and 0.1.2-rc.1, which fails closed on unknown types at read); set `false` to stop audit appends entirely. `0.1.2-rc.1` (adapted 2026-09-02): the session envelope keeps its ignorable field for stored-log read compatibility only — `Session.append` still cannot stamp it, so audit-gate behavior is unchanged |
197
+ | `focusFallback` | `never` | Whether an action may bring the target window to the foreground as a last resort (`never` / `allow`) |
198
+ | `imageMode` | `auto` | `screen_shot` rendering: `auto` (image when the model accepts images, text otherwise) or `text` |
199
+ | `helperTimeoutMs` | `30000` | Per-helper-call timeout in ms (1..300000) |
200
+ | `maxHelperOutputBytes` | `25165824` | Cap on one helper response in bytes (1024..67108864) |
201
+ | `maxScreenshotSide` | `2560` | Longest screenshot side in pixels (320..7680); larger captures are downscaled |
202
+ | `staleCheckPixels` | `true` | Compare a fresh pixel hash before every action and refuse on change |
203
+ | `staleCheckTree` | `target` | Accessibility-tree freshness: `target` verifies the addressed element (default), `strict` refuses any changed node, `off` skips the whole-tree hash for windows with a ticking clock/cursor (identity/age/pixel checks still apply) |
204
+ | `maxObservationAgeMs` | `30000` | Maximum age in ms of an observation an action may cite (1000..600000) |
205
+ | `maxCachedObservations` | `8` | LRU cap on cached observations (1..64) |
206
+ | `maxElements` | `500` | Cap on accessibility elements per `screen_read` (1..2000) |
207
+ | `maxTreeDepth` | `32` | Maximum accessibility tree-walk depth (1..64) |
208
+ | `maxTextLength` | `200` | Truncation length for sanitized model-visible strings (16..10000) |
209
+ | `rollbackEnabled` | `true` | Back up and restore control text when `type` fails |
210
+ | `ocr.enabled` / `command` / `language` | `true` / `tesseract` / `eng` | Optional OCR for the `screen_find` path (probed at mount; degrades to unavailable when tesseract is absent) |
211
+
212
+ </details>
213
+
214
+ Example override in your profile patch:
215
+
216
+ ```yaml
217
+ - insert:
218
+ - id: dsh-click
219
+ name: dsh-click
220
+ config:
221
+ requireApproval: true
222
+ autoApproveWindows: ['^Notepad']
223
+ focusFallback: never
224
+ ```
225
+
226
+ ## FAQ
227
+
228
+ <details>
229
+ <summary>Where do screenshots and observations live? Can screen content hit the disk?</summary>
230
+
231
+ A `screen_shot` image goes into the harness attachment store (content-addressed, under the host’s own attachment policy) and is provided to the model only through that tool result. Observations themselves stay in memory (bounded LRU, `maxCachedObservations`, lost on restart); beyond that the plugin writes nothing to disk, makes no network requests, and stores no credentials. The only session-log content is the `dsh-click/*` audit events, and window titles, executable paths, and free text are sanitized and length-capped before they are written or shown.
232
+
233
+ </details>
234
+
235
+ <details>
236
+ <summary>What happens on macOS / Linux? Silent breakage?</summary>
237
+
238
+ Nothing is silent. Both backends are reserved but unimplemented, so every call fails closed with a model-readable reason (`dsh-click backend unavailable on linux: ...`), and the profile still boots everywhere. On Windows, a missing `subprocess` service or an unavailable helper refuses the call loudly instead of pretending success.
239
+
240
+ </details>
241
+
242
+ <details>
243
+ <summary>Does it fight other plugins over commands, services, or slots?</summary>
244
+
245
+ It provides no service key and consumes only two optional seams (`dsh-click/backend`, `dsh-click/grounding`, for tests/embeddings); it registers no client slot, no `webServer` route, and no waterfall listener; every audit event sits in the `dsh-click/*` namespace. Each tool registration is wrapped in `ctx.effect`, so stop/hot-reload removes exactly the tools it registered.
246
+
247
+ </details>
248
+
249
+ <details>
250
+ <summary>How big is the performance overhead? Does every action re-capture the screen?</summary>
251
+
252
+ Observations are produced on demand: `screen_read` only walks the accessibility tree (plain text), while `screen_shot` encodes a PNG. Every mutating action re-captures the target window once for the freshness comparison (pixel hash plus tree by default) — that is part of the safety boundary. Helper calls are bounded by `helperTimeoutMs` / `maxHelperOutputBytes`, and the observation cache keeps 8 entries by default.
253
+
254
+ </details>
255
+
256
+ ## Security boundaries
257
+
258
+ - **Observe before act, every time.** Actions must cite a fresh observation; a changed screen (pixel hash) or an expired observation is refused with a model-readable reason demanding re-observation.
259
+ - **Approval is the default.** `requireApproval: true` unless you explicitly exempt specific windows; every action — allowed or not — is audit-logged.
260
+ - **No foreground stealing.** The helper never brings a target window to the foreground (`focusFallback: 'never'` by default); input is delivered through UIA or posted messages so background windows are not disturbed. `focusFallback: 'allow'` is the only sanctioned exception.
261
+ - **Process identity is re-verified** immediately before and after each action; a mid-act process swap fails the outcome (`PROCESS_CHANGED`).
262
+ - **Sanitized output.** Control characters are stripped, tabs collapse, credential-shaped values (keys, tokens, JWTs, bearer headers) are redacted before anything reaches the model or the log.
263
+ - **Fail closed.** Unsupported platforms, a missing subprocess service, or an unavailable helper refuse every call loudly — profiles keep booting everywhere.
264
+
265
+ ## Permissions & data
266
+
267
+ - **Permissions**: mutating actions cross the official `ctx.approval` seam — the plugin never re-implements or bypasses it. `autoApproveWindows` only ever *skips the ask for specific windows*; it cannot disable the freshness or process-identity checks. The `dshWorkshop` manifest declares `fs:read`, `subprocess:spawn`, `session:append`, `network:none`, `native-code:none`.
268
+ - **Data**: the plugin stores nothing on disk except the screenshots the attachment store keeps (content-addressed, under the harness’s own attachment policy). Observations are cached in memory (LRU, bounded). No network requests, no credential storage.
269
+ - **Session log**: `dsh-click/observed` and `dsh-click/action` are log-only audit events carrying sanitized window/process facts — titles, paths, and free text are redacted and length-capped before they are written or shown.
270
+
271
+ ## Known limitations
272
+
273
+ - **Windows first.** macOS and Linux backends are reserved; on those platforms every call fails closed with a clear reason.
274
+ - **Text-only fidelity.** `screen_read` depends on the application exposing UIAutomation; apps without an accessible tree yield pixel hints only, plus the `screen_find` OCR/grounding path. Coordinate clicks remain available.
275
+ - **Posted-input apps.** Some applications ignore posted window messages (games, some Electron surfaces); `key` reports this honestly instead of pretending success. Since 0.3.24 the `type` `SendInput` fallback covers real keyboard input and CJK text, but it is still bounded by the target app’s own input model.
276
+ - **Session audit on envelope-less harness builds.** The audit events ride an adaptive gate: hosts that know the vocabulary append plainly, hosts with the `ignorable` envelope append with the marker, and envelope-less hosts — `0.1.0-rc.6`–`0.1.0-rc.8`, `0.1.1-rc.2`, and `0.1.2-rc.1` (which cannot stamp the ignorable marker — the envelope field is retained for stored-log read compatibility only — and fails closed on unknown types at read) — get no audit append; the tool results remain the reconstructable audit trail. Set `auditSessionEvents: false` to stop audit appends entirely.
277
+
278
+ ## Interoperability with other DSH plugins
279
+
280
+ `dsh-click` exposes a tool surface only and does not join other plugins’ seams:
281
+
282
+ - **Two hard services only.** `inject = ['tools', 'subprocess']`; it provides no service key, so it cannot collide on one.
283
+ - **No client or HTTP resources.** It registers no client slot, no `webServer` route, and no `agent/pre-step` / `tools/post-execute` waterfall listener, and mutates no globals.
284
+ - **Namespaced audit.** It writes only `dsh-click/observed` and `dsh-click/action`; when a host does not know the vocabulary the adaptive gate skips them, and the tool results remain the reconstructable audit trail.
285
+ - **Optional seams degrade.** `dsh-click/backend` and `dsh-click/grounding` exist for tests/embeddings only; real deployments fall through to platform selection and require no other plugin.
286
+ - **Registrations are effects.** Every `ctx.tools.register` is wrapped in `ctx.effect`, so stop/hot-reload removes exactly the tools it registered.
287
+
288
+ ## Compatibility
289
+
290
+ | Surface | Status |
291
+ |---|---|
292
+ | Harness | DeepSeek Harness `dsh-v0.2.1-alpha.1` (GitHub tag, verified 2026-09-25). Verified 2026-09-11 against the `dsh-v0.1.7-alpha.1` master checkout (full gate chain + profile install smoke). `engines.dsh`: `>=0.2.1-0 <0.3.0 \|\| >=0.2.2-0 <0.3.0` |
293
+ | Node | `^22.19.0 \|\| >=24.9.0` |
294
+ | Platforms | **Windows first** (UIAutomation + Win32 input, via a bundled PowerShell helper); macOS/Linux backends are reserved and fail closed with a clear reason |
295
+ | Model | Text-only models fully supported (`screen_read` returns structured text); vision models additionally get `screen_shot` images |
296
+
297
+ ## Ecosystem & quality
298
+
299
+ - [Gitee mirror](https://gitee.com/perrylink/dsh-click) (sync mirror; GitHub is canonical)
300
+ - [dshfind plugin page](https://dshfind.com/plugins/PerryLink/dsh-click) (installs and ranking)
301
+ - [OpenSSF Scorecard report](https://securityscorecards.dev/viewer/?uri=github.com/PerryLink/dsh-click) (driven by [.github/workflows/scorecard.yml](.github/workflows/scorecard.yml))
302
+ - [dsh-plugin-doctor verification report](https://github.com/PerryLink/dsh-plugin-doctor#verified-徽章) (K10–K13 all `pass`)
303
+ - [DSH Market](https://dsh.market/) (listed)
304
+ - **1024 store channel**: `npm i -g dsh1024` once, then `dsh1024 plugin --profile web add dsh-click` (counts toward the [deepseek1024.com](https://deepseek1024.com) install ranking)
305
+ - [Ecosystem knowledge base](https://perrylink.github.io/dsh-plugin-guide/) — measured data, not marketing: plugin development guide · plugin-selection data · maintenance criteria
306
+
307
+ ## Development
308
+
309
+ ```sh
310
+ pnpm install # node ^22.19 || >=24
311
+ pnpm run typecheck # tsc: src + tests against the local harness checkout
312
+ pnpm run typecheck:ci # tsc against the published host types (no paths)
313
+ pnpm test # vitest: 79 tests, 13 files (helper smoke runs on Windows)
314
+ pnpm run build # tsdown bundle + tsc declarations (lib/)
315
+ pnpm run verify:self-contained # dependency specs resolve from the registry
316
+ pnpm run verify:artifacts # built ESM face + native helper present
317
+ pnpm run check:readmes # five-language README consistency
318
+ pnpm pack # the published tarball
319
+ ```
320
+
321
+ The README images are generated programmatically by [`scripts/gen-readme-assets.py`](scripts/gen-readme-assets.py) (Python + Pillow) and are safe to re-run: `python scripts/gen-readme-assets.py`.
322
+
323
+ Topics: `dsh`, `dsh-plugin`, `deepseek-harness`, `deepseek`, `cordis`, `computer-use`, `windows-automation`, `uiautomation`, `desktop-control`, `screen-reader`
324
+
325
+ ## Family & thanks
326
+
327
+ This project is part of the DeepSeek Harness plugin family from [PerryLink](https://github.com/PerryLink) (all Apache-2.0). If it is useful, **a star helps the next person find it** — nothing is gated behind it.
328
+
329
+ - [Windows helper smoke checklist](docs/windows-smoke.md) — real input/focus verification for 0.3.24+.
330
+ - [@LIU12312-Source](https://github.com/LIU12312-Source) — real `SendInput`/foreground-focus fallback for the Windows helper (PR #21).
331
+ - [@Mchsd](https://github.com/Mchsd) — added the `auditSessionEvents` opt-out for harnesses whose session reader rejects the `dsh-click` audit events (#2).
332
+ - [@PerryLink](https://github.com/PerryLink) — creator and maintainer: tool surface, action safety boundary, Windows native helper, sanitizers, and the five-language docs.
333
+
334
+ <details>
335
+ <summary>Expand all plugins</summary>
336
+
337
+ | Plugin | Status |
338
+ |---|---|
339
+ | **[dsh-aqua-input-check](https://github.com/PerryLink/dsh-aqua-input-check)** | |
340
+ | **[dsh-archive-check](https://github.com/PerryLink/dsh-archive-check)** | |
341
+ | **[dsh-auto-review](https://github.com/PerryLink/dsh-auto-review)** | |
342
+ | **[dsh-autotier](https://github.com/PerryLink/dsh-autotier)** | 🧊 FROZEN — see the repo README |
343
+ | **[dsh-bid-ca-precheck](https://github.com/PerryLink/dsh-bid-ca-precheck)** | |
344
+ | **[dsh-bid-qual-check](https://github.com/PerryLink/dsh-bid-qual-check)** | |
345
+ | **[dsh-cert-mcp](https://github.com/PerryLink/dsh-cert-mcp)** | 🧊 FROZEN — see the repo README |
346
+ | **[dsh-checkpoint-rewind](https://github.com/PerryLink/dsh-checkpoint-rewind)** | |
347
+ | **[dsh-click](https://github.com/PerryLink/dsh-click)** | |
348
+ | **[dsh-composer-history](https://github.com/PerryLink/dsh-composer-history)** | |
349
+ | **[dsh-contract-stance](https://github.com/PerryLink/dsh-contract-stance)** | |
350
+ | **[dsh-customs-doc-check](https://github.com/PerryLink/dsh-customs-doc-check)** | |
351
+ | **[dsh-data-quality](https://github.com/PerryLink/dsh-data-quality)** | 🧊 FROZEN — see the repo README |
352
+ | **[dsh-demurrage-ledger](https://github.com/PerryLink/dsh-demurrage-ledger)** | |
353
+ | **[dsh-doublecheck](https://github.com/PerryLink/dsh-doublecheck)** | |
354
+ | **[dsh-drill-script-check](https://github.com/PerryLink/dsh-drill-script-check)** | |
355
+ | **[dsh-eia-guide-check](https://github.com/PerryLink/dsh-eia-guide-check)** | |
356
+ | **[dsh-emergency-plan](https://github.com/PerryLink/dsh-emergency-plan)** | |
357
+ | **[dsh-essay-rubric-check](https://github.com/PerryLink/dsh-essay-rubric-check)** | |
358
+ | **[dsh-evidence-check](https://github.com/PerryLink/dsh-evidence-check)** | |
359
+ | **[dsh-export-ctl-check](https://github.com/PerryLink/dsh-export-ctl-check)** | |
360
+ | **[dsh-fast](https://github.com/PerryLink/dsh-fast)** | |
361
+ | **[dsh-fmea-table-check](https://github.com/PerryLink/dsh-fmea-table-check)** | |
362
+ | **[dsh-forecast-penalty](https://github.com/PerryLink/dsh-forecast-penalty)** | |
363
+ | **[dsh-fund-research](https://github.com/PerryLink/dsh-fund-research)** | |
364
+ | **[dsh-github](https://github.com/PerryLink/dsh-github)** | |
365
+ | **[dsh-gongwen-flow-check](https://github.com/PerryLink/dsh-gongwen-flow-check)** | |
366
+ | **[dsh-gongwen-word-check](https://github.com/PerryLink/dsh-gongwen-word-check)** | |
367
+ | **[dsh-guard-plan-qc](https://github.com/PerryLink/dsh-guard-plan-qc)** | |
368
+ | **[dsh-hazchem-check](https://github.com/PerryLink/dsh-hazchem-check)** | |
369
+ | **[dsh-hazplan-check](https://github.com/PerryLink/dsh-hazplan-check)** | |
370
+ | **[dsh-hidden-risk-map](https://github.com/PerryLink/dsh-hidden-risk-map)** | |
371
+ | **[dsh-hs-classify](https://github.com/PerryLink/dsh-hs-classify)** | |
372
+ | **[dsh-icd-rule-check](https://github.com/PerryLink/dsh-icd-rule-check)** | |
373
+ | **[dsh-industry-research](https://github.com/PerryLink/dsh-industry-research)** | |
374
+ | **[dsh-lawcite-adapter](https://github.com/PerryLink/dsh-lawcite-adapter)** | |
375
+ | **[dsh-laya](https://github.com/PerryLink/dsh-laya)** | |
376
+ | **[dsh-lc-doc-check](https://github.com/PerryLink/dsh-lc-doc-check)** | |
377
+ | **[dsh-learning-gap-check](https://github.com/PerryLink/dsh-learning-gap-check)** | |
378
+ | **[dsh-library](https://github.com/PerryLink/dsh-library)** | |
379
+ | **[dsh-local-ai](https://github.com/PerryLink/dsh-local-ai)** | |
380
+ | **[dsh-lsp-actions](https://github.com/PerryLink/dsh-lsp-actions)** | |
381
+ | **[dsh-mask](https://github.com/PerryLink/dsh-mask)** | |
382
+ | **[dsh-mcp-panel](https://github.com/PerryLink/dsh-mcp-panel)** | |
383
+ | **[dsh-medrec-qc](https://github.com/PerryLink/dsh-medrec-qc)** | |
384
+ | **[dsh-memento](https://github.com/PerryLink/dsh-memento)** | 🧊 FROZEN — see the repo README |
385
+ | **[dsh-nsfc-form-check](https://github.com/PerryLink/dsh-nsfc-form-check)** | |
386
+ | **[dsh-nurse-record-check](https://github.com/PerryLink/dsh-nurse-record-check)** | |
387
+ | **[dsh-observe](https://github.com/PerryLink/dsh-observe)** | |
388
+ | **[dsh-origin-rvc-check](https://github.com/PerryLink/dsh-origin-rvc-check)** | |
389
+ | **[dsh-ota-review-check](https://github.com/PerryLink/dsh-ota-review-check)** | |
390
+ | **[dsh-output-styles](https://github.com/PerryLink/dsh-output-styles)** | |
391
+ | **[dsh-paper-doc-adapter](https://github.com/PerryLink/dsh-paper-doc-adapter)** | |
392
+ | **[dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules)** | |
393
+ | **[dsh-permit-report-check](https://github.com/PerryLink/dsh-permit-report-check)** | |
394
+ | **[dsh-pipeline-check](https://github.com/PerryLink/dsh-pipeline-check)** | |
395
+ | **[dsh-pleading-draft](https://github.com/PerryLink/dsh-pleading-draft)** | |
396
+ | **[dsh-plugin-doctor](https://github.com/PerryLink/dsh-plugin-doctor)** | 🚫 RETIRED (repo archived) |
397
+ | **[dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide)** | |
398
+ | **[dsh-plugin-kit](https://github.com/PerryLink/dsh-plugin-kit)** | 🚫 RETIRED (repo archived) |
399
+ | **[dsh-plugin-upgrade](https://github.com/PerryLink/dsh-plugin-upgrade)** | 🚫 RETIRED (repo archived) |
400
+ | **[dsh-policy-brief-draft](https://github.com/PerryLink/dsh-policy-brief-draft)** | |
401
+ | **[dsh-power-loss-split](https://github.com/PerryLink/dsh-power-loss-split)** | |
402
+ | **[dsh-power-ticket-check](https://github.com/PerryLink/dsh-power-ticket-check)** | |
403
+ | **[dsh-ppap-check](https://github.com/PerryLink/dsh-ppap-check)** | |
404
+ | **[dsh-protest-deadline](https://github.com/PerryLink/dsh-protest-deadline)** | |
405
+ | **[dsh-railway-window](https://github.com/PerryLink/dsh-railway-window)** | |
406
+ | **[dsh-repair-order-qc](https://github.com/PerryLink/dsh-repair-order-qc)** | |
407
+ | **[dsh-research-report](https://github.com/PerryLink/dsh-research-report)** | |
408
+ | **[dsh-review-reply-check](https://github.com/PerryLink/dsh-review-reply-check)** | |
409
+ | **[dsh-rulefile-check](https://github.com/PerryLink/dsh-rulefile-check)** | |
410
+ | **[dsh-safety-brief-check](https://github.com/PerryLink/dsh-safety-brief-check)** | |
411
+ | **[dsh-score](https://github.com/PerryLink/dsh-score)** | 🧊 FROZEN — see the repo README |
412
+ | **[dsh-session-sync](https://github.com/PerryLink/dsh-session-sync)** | |
413
+ | **[dsh-site-log-check](https://github.com/PerryLink/dsh-site-log-check)** | |
414
+ | **[dsh-skill-pack-security](https://github.com/PerryLink/dsh-skill-pack-security)** | |
415
+ | **[dsh-soe-decision-check](https://github.com/PerryLink/dsh-soe-decision-check)** | |
416
+ | **[dsh-soilwater-check](https://github.com/PerryLink/dsh-soilwater-check)** | |
417
+ | **[dsh-sop-sync-check](https://github.com/PerryLink/dsh-sop-sync-check)** | |
418
+ | **[dsh-spc-gbt-adapter](https://github.com/PerryLink/dsh-spc-gbt-adapter)** | |
419
+ | **[dsh-talk](https://github.com/PerryLink/dsh-talk)** | |
420
+ | **[dsh-tender-extract](https://github.com/PerryLink/dsh-tender-extract)** | |
421
+ | **[dsh-tender-matrix](https://github.com/PerryLink/dsh-tender-matrix)** | |
422
+ | **[dsh-test-drive](https://github.com/PerryLink/dsh-test-drive)** | |
423
+ | **[dsh-ticktick](https://github.com/PerryLink/dsh-ticktick)** | 🚫 RETIRED (repo archived) |
424
+ | **[dsh-translate](https://github.com/PerryLink/dsh-translate)** | |
425
+ | **[dsh-warranty-calc](https://github.com/PerryLink/dsh-warranty-calc)** | |
426
+ | **[dsh-budget](https://github.com/PerryLink/dsh-budget)** | 🧊 FROZEN — see the repo README |
427
+ | **[dsh-claude-move](https://github.com/PerryLink/dsh-claude-move)** | 🧊 FROZEN — see the repo README |
428
+ | **[dsh-defend](https://github.com/PerryLink/dsh-defend)** | 🧊 FROZEN — see the repo README |
429
+ | **[dsh-draw](https://github.com/PerryLink/dsh-draw)** | 🧊 FROZEN — see the repo README |
430
+ | **[dsh-reach](https://github.com/PerryLink/dsh-reach)** | 🧊 FROZEN — see the repo README |
431
+ | **[dsh-background-agents](https://github.com/PerryLink/dsh-background-agents)** | 🚫 RETIRED (repo archived) |
432
+ | **[dsh-session-pin](https://github.com/PerryLink/dsh-session-pin)** | 🚫 RETIRED (repo archived) |
433
+ | **[dsh-team-rooms](https://github.com/PerryLink/dsh-team-rooms)** | 🚫 RETIRED (repo archived) |
434
+
435
+ </details>
436
+
437
+ ## License
438
+
439
+ [LICENSE](LICENSE) (Apache License 2.0) © 2026 dsh-click contributors
440
+
441
+ <p align="center"><sub>Apache-2.0 · Community plugin, not affiliated with DeepSeek.</sub></p>