Vision & Multimodal
dsh-screen-reader-cbg33695
cbg33695/dsh-screen-reader · ★ 0
Give the agent eyes on the screen: capture the whole desktop, one window, or one region and hand the IMAGE ITSELF to the session model (no second model, no description layer)…
Manifest details
安装
dsh plugin --profile web add 'github:cbg33695/dsh-screen-reader#16334a8b829efd7a695fa04143d7a3a3eaa6acf0'GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试
For install and environment setup, plus plugin development tutorials, see DSH community docs ↗
README
Table of contents
- ⚠️ Read this first: what the advantage actually is
- When you should not use it
- Contents
- What it solves
- The two tools
- Screen
- Comparing two images
- How images reach the model (this decides how you use it)
- Measured effects
- Vision
- Local pixel diff (the most reliable part)
- Cost
- Boundaries: what it cannot do
- 1. It is a describer, not a gauge
- 2. Low-contrast differences are the danger zone
- 3. Shape semantics are a weak spot
- 4. Resolution is NOT the accuracy lever, but cropping IS
- 5. One frame only — no time, no causality
- 6. The attachment store grows, and there is no cleanup mechanism at all
- 7. Windows only
- Install
- Option 1 — as a profile bundle (recommended, installed on a real instance and composition-verified)
- Option 2 — as an agent preset (verified working)
- Self-check after installing
- Upgrading 0.2 to 0.3: breaking changes
- Privacy
- Design notes
- Why the plugin modules have zero dependencies
- Why half the tools were deleted
- Why the diff is computed locally
- Why see_screen returns the image instead of text
- Why the prompt puts "subject" first
- Verification status
- Known issues and plans
- Reporting
- License
- Relation to other plugins
Gives the agent eyes: it looks at the screen itself, instead of waiting for you to take a screenshot. Plus an exact pixel diff — "did it change, and where" is computed locally, and the model is only asked "what did it become".
Windows only. Requires PowerShell 5.1 with
System.DrawingandPrintWindow.
⚠️ Read this first: what the advantage actually is
It does not make vision better. Images reach the model through the model's own pipeline and are squeezed onto the same normalised token grid. The plugin sees exactly as clearly as built-in vision does — no more.
What it adds are two things a model cannot get on its own, and those two happen to decide whether an agent can work by itself:
| Advantage | Why built-in vision cannot do it | What it means for automation |
|---|---|---|
| 1. The agent grows eyes | A model can only read files that already exist. To let it see the screen you had to press Win+Shift+S, save, and drag the file in — every single time |
The agent can look during its own work: check a setting after changing it, check output after running a script, look at what an interface actually shows when stuck. No human in the loop |
| 2. An exact "did it change" | Ask a vision model "what changed between these two images" and it fabricates plausible differences — this is a thing it is demonstrably bad at | Deciding whether something changed goes from probabilistic to certain: computed locally, exact, free, with bounding boxes. The model is constrained to explain regions the diff already proved changed, so it has no room to invent |
In one sentence: other plugins look at images for the model; this one gives the agent hands and eyes.
That is its only reason to exist. If you are happy to screenshot manually and paste, it is worth little to you — and a manual screenshot is often better, because you choose the crop.
When you should not use it
| Your need | Better choice |
|---|---|
| I just want to ask about one image | The built-in read_image, or @liustack/modlens (★3.9k, L5, Gold, structured JSON). It is more mature at that job, and this plugin does not intend to compete there |
| I need cross-platform | Windows-only, cannot help |
| I need to measure pixels precisely | Use a real measuring tool. This is a describer, not a gauge; measured size error reaches ±40% |
| I want "find what's wrong with my UI" for low-contrast details | That is its danger zone — see Boundaries |
Contents
- What it solves
- The two tools
- How images reach the model (this decides how you use it)
- Measured effects
- Boundaries: what it cannot do
- Install
- Upgrading 0.2 to 0.3: breaking changes
- Privacy
- Design notes
- Verification status
- Known issues and plans
- Reporting
- License
- Relation to other plugins
What it solves
Models have no eyes on your screen. So this happens:
- You say "something looks wrong in my interface" and the model can only work from your description
- The model says "click that button" — it has never seen that button
- You change a setting and the model does not know what it became
- You have an agent render an image or restyle a UI and it cannot verify what it just produced
The first three are "cannot see". The fourth is "cannot see and cannot self-verify" — and that is the real bottleneck for automation.
The plugin gives the model two things:
- Sight: capture the screen (whole desktop / one window / one region) and hand the image itself to the current model, with no second model transcribing it
- Verification: locate changes between two images exactly, and let the model explain only "what it became"
The two tools
Screen
| Tool | Purpose |
|---|---|
see_screen |
Capture and hand the image to the current model. window limits it to one window; region does a normalized crop (the only way to buy detail — see the next section). No second model by default; transcribe: true is the compatibility path for text-only session models |
Comparing two images
| Tool | Purpose |
|---|---|
see_diff |
Two images: an exact local pixel diff locates the changed regions, then the model explains only those. The model is never asked whether anything changed |
There is no "look at one image file" tool here. There used to be
see_image, but it was a worse duplicate of the built-inread_image(it added a model-to-model transcription hop). With built-in vision available, that job belongs to the built-in tool, so it was removed.
How images reach the model (this decides how you use it)
The screen tool does not hand the image to another model to transcribe; it returns the image itself as a content block for the current session model to look at. This path is verified.
On the provider side every image is force-normalised, and that decides all usage:
14px patch grid · 3:1 downsample per axis · 384 tokens per image, capped
| What you captured | What the request actually carried | Screen pixel → request pixel |
|---|---|---|
| Full window 1942×1030 | ≈ 950×504 | 0.49 |
| The same, downscaled to 1295×687 | ≈ 950×504 | 0.49 |
| A crop of 675×387 | ≈ 879×504 | 1.30 |
| A crop of 346×346 (measured) | 346×346, untouched | 1.00 |
(The 675×387 row is derived from source constants, not measured. The 346×346 row is measured.)
Normalisation actually happens in two stages, and the first one is measurable: the harness itself caps the request at roughly 640,000 pixels — a 1920×1080 screenshot went out as 1066×600 (639,600 px) — and the provider then squeezes that onto its token grid. Cropping works because a small crop is passed through untouched by that first stage, skipping the 0.555 linear downscale.
Three consequences are therefore necessary, not coincidental:
- Source resolution does not affect accuracy at full-window scale — two different resolutions land on the same grid
- Cropping is the only way to buy detail — measured at roughly 2× the effective detail density
- Raising capture resolution buys no accuracy — a larger source is simply compressed harder
This part is measured, not derived. Same sidebar region, same prompt:
| Input | Session title as read |
|---|---|
| Full screen 1920×1080 | 杀戳尖塔模组制作 ✗ |
| Crop 346×346 | 杀戮尖塔模组制作 ✓ |
Ground truth came from the session index under ${DSH_HOME}/storages. At full screen, 4 of 5 titles
were character-exact and 1 was wrong; after cropping, none were wrong. The full-screen failure
mode was exactly visually similar characters (戮/戳 share the same right-hand radical) — precisely
what a 0.49 sampling rate predicts.
So the intended usage is: pin the application with window, then zoom with region.
Measured effects
Everything below was measured on real runs, not designed.
Vision
| Item | Result |
|---|---|
| Reading bar-chart values (synthetic, 4 bars) | 4/4 exact (120 / 60 / 180 / 90), colours and ordering correct |
| Reading a Blender Outliner list | 4/4 (Camera / Cube / Light / 球体), matching headless-Blender ground truth |
| Reading a Chinese path from a title bar | Character-exact |
| Judging which object is selected | 0/3 before a prompt fix, 2/2 after |
LOCATION structured output |
2/2 emitted as required, parseable into a region |
| Reading sidebar session titles (full screen 1920×1080) | 4 of 5 titles character-exact, 1 character wrong (戮 → 戳) |
| Reading the same region (crop 346×346) | 5/5 character-exact, and spurious spaces disappeared |
Local pixel diff (the most reliable part)
| Item | Result |
|---|---|
| Real photo (1225×1254 JPEG) with 2 programmatic edits | Exactly 2 regions detected, zero false positives |
| Positional accuracy | Bounding boxes ~30–45 px larger than the true edits (= padding 10 + grid quantisation + dilation 1) |
| An image compared with itself | 0 changed pixels, correctly reported as "no change" |
| Synthetic calibration image (4 known edits) | All 4 regions matched; two of them were merged into one region by dilation (known trade-off) |
Cost
Images are cheap: the provider normalises every request image to ≤384 vision tokens regardless of source size. The earlier claim of "about 1000-1500 tokens per look" was wrong.
What is expensive is having a model turn an image into text. On the transcription compatibility path, that model first spends a large reasoning budget (measured 788–6621 chars) before emitting any prose. That is why the default path calls no second model — the image goes straight to the current model.
Boundaries: what it cannot do
This is the most important section in this document.
1. It is a describer, not a gauge
Measured: a 30 px tall colour band was read as "40–45 px" — about 40% error.
- ✅ Reliable: which is taller, which is greyed out, roughly upper-left or lower-right
- ❌ Unreliable: is the gap 8 px, is this spacing 16 or 12
2. Low-contrast differences are the danger zone
Reverse validation works (grey vs saturated blue "disabled button" is easy), but the genuinely hard cases — two similar greys, a 1 px offset, a faint colour shift — are exactly where it misses, or worse: confidently invents one. And "tell me what's wrong with my UI" needs precisely those.
3. Shape semantics are a weak spot
Measured case: a cube edited into a "pointed little house" in a Blender viewport.
- The model saw "a non-standard shape" and said "this object has been edited"
- But it kept calling it a cube and never attributed the mesh edit to the object
Honest caveat: at that camera angle the point was genuinely subtle, so missing it is partly forgivable. But the pattern — not attributing a mesh edit to the object — appears beyond this one case.
4. Resolution is NOT the accuracy lever, but cropping IS
Same Blender window, same prompt, same model — only the resolution changed:
| Input size | Selected object | Says the cube is unselected | Edit evidence |
|---|---|---|---|
| 1942×1030 (native) | ✅ | not stated | only "black triangular face" |
| 1295×687 (44.5% fewer pixels) | ✅ | ✅ explicitly "not selected" | ✅ "non-standard shape… has been edited" |
The downscaled one scored better. Conclusion: the provider normalises its input, so at full-window scale the source resolution does not affect accuracy. The mechanism is in the previous section, and the same mechanism explains why cropping works — measured at roughly 2× the effective detail density.
5. One frame only — no time, no causality
- ✅ It can answer: "what is on the screen right now"
- ❌ It cannot answer: "did this dialog appear because I clicked X"
Rolling screen memory (screen_memory / screen_watch) existed to cover this and was removed in
0.3: no real use case ever demonstrated its value, while every recorded frame wrote a file into the
attachment store. So today it genuinely sees only the present.
6. The attachment store grows, and there is no cleanup mechanism at all
Every vision call writes a content-addressed file into ${DSH_HOME}/attachments, and the plugin
cannot prevent it.
Measured (2026-09-14, this machine's web profile): 215 files / 40.24 MB, accumulating 9 → 25 → 83 → 87 → 13 files per day since 09-10. Its composition:
| Subtree | Contents | Measured |
|---|---|---|
v1/objects |
normalized attachments — what image blocks in session history reference | 118 files / 35.25 MB |
v1/request-images |
model-request variant cache (per route and pixel budget) | 97 files / 4.98 MB |
v1/tmp |
temporary | 0 |
Why the plugin cannot clean it up — this is not "was not done", it is not possible:
The attachments service describes itself as an "Immutable binary attachment service", and its
methods are only validateImage / saveImage(s) / readImage / imageHostPath /
readImageRequest. There is no delete, no prune, no gc. And readImage verifies that the bytes
still match the recorded reference, so deleting files behind the service's back makes images in past
conversations unreadable — those references are persisted in the session log.
This is not specific to this plugin: any image entering a conversation writes here — ones you
paste yourself, the built-in read_image, generate_image. The 80 WEBP files in the table above are
not from this plugin (it stores PNG only). The real fix belongs at the DSH level (a retention policy
for that immutable store), not inside one plugin.
0.2.1 had a vision_storage tool that reported and optionally pruned that directory; 0.3 removed
it. Removing it took away no capability: its "prune" was the dangerous half, and a safe prune never
existed. To see how big it is, just look at the directory.
The only honest way to reclaim the space: delete ${DSH_HOME}/attachments outright — at the cost
of images in every past conversation no longer rendering. That is a trade-off, not a cleanup.
7. Windows only
scripts/capture.ps1 and scripts/imageops.ps1 depend on PowerShell 5.1, System.Drawing,
PrintWindow and DwmGetWindowAttribute. On other platforms they fail honestly rather than
pretending to succeed.
Install
Option 1 — as a profile bundle (recommended, installed on a real instance and composition-verified)
dsh plugin --profile <profile-name> add <package-or-local-path>
# for a local directory:
# dsh plugin --profile web add C:\path\to\dsh-screen-reader
dsh plugin forwards its arguments to pnpm and then automatically appends every dependency that
declares dsh.bundle.patch to the dsh.profile.bundles layer stack — this package declares it, so
no manual configuration is needed.
DSH must be restarted before the row joins the running composition: the bundle layer stack is composed at process start, so a running process never notices a newly installed package.
Check that it landed, without restarting:
dsh --profile <profile-name> --dump-config
# the output should contain:
# # == dsh-screen-reader
# - id: screen-reader
# name: dsh-screen-reader
Then restart DSH and ask any session "which tools do you have related to screens and images?" — it
should list two: see_screen and see_diff.
The row is a link: to the package directory (that is what pnpm does for local directory
dependencies), so do not delete or move that directory — DSH would fail to find it at startup. To
uninstall: dsh plugin --profile <profile-name> remove dsh-screen-reader.
⚠️ The 0.2.0 bundle was broken; 0.2.1 fixes it. Reviewing the code, I found that
lib/screen.jsandlib/toolbox.jslocated their PowerShell helpers withnew URL('capture.ps1', import.meta.url)— but in the package the JS lives inlib/while the helpers live inscripts/, so they looked for alib/capture.ps1that does not exist. Any bundle install failed on the first capture withThe argument '.../lib/capture.ps1' to the -File parameter does not exist.The runtime now handles both layouts (the same source is correct as a preset and as a package), and that check is now an assertion in the generator (tools/build-lib.mjs). If you installed 0.2.0, please upgrade. To be explicit: what is verified is the install and the composed tree. The tool list inside a restarted session is still unconfirmed — that needs a process restart, which I did not perform mid-session.
Option 2 — as an agent preset (verified working)
Put lib/ and scripts/ into a preset directory, wire up agent.cordis.yml, and open a session on
that preset. See the DSH docs for preset syntax and relative-path resolution.
Self-check after installing
Which tools do you have related to screens and images?
You should see two: see_screen, see_diff.
- They appear → installed. Then actually call
see_screenonce to confirm it really returns an image - They do not → the plugin was not loaded. Try the other install method, or file an issue with your DSH version, profile name and the full error
Upgrading 0.2 to 0.3: breaking changes
0.3 is a large reduction. The tool count goes from 8 to 2.
| Tool in 0.2 | In 0.3 | Why |
|---|---|---|
see_screen |
kept | Gives the agent eyes. This is the core |
see_diff |
kept | Exact local diff; a model cannot do it |
see_image |
removed | A worse duplicate of the built-in read_image |
screen_watch |
removed | No real use case; wrote one attachment file per frame |
screen_memory |
removed | Same (the time dimension is abandoned) |
vision_routes |
removed | Diagnostic |
vision_selftest |
removed | Never ran; covered only the diff chain, never see_screen |
vision_storage |
removed | Diagnostic |
What to do when upgrading:
- If a prompt or script of yours names a removed tool, change it. Calling a removed tool returns "unknown tool"; it does not silently degrade
see_screen's plugin declaration changed:injectwent from['tools','timer']to['tools']. If you install as a preset and wrote your ownagent.cordis.yml, you do not need to change anything —injectlives in the plugin source, not in configuration- Preset rows need no change: the two plugin rows are still
./plugin/screen.jsand./plugin/toolbox.js; they simply register fewer tools - Old versions do not auto-upgrade; the plugin is a local/package install with no update channel
Unchanged: the parameters and return values of the two kept tools, and how cordis.patch.yml
inserts the row.
Privacy
This section matters, because screen contents are the most sensitive thing here.
- Nothing happens unless something captures. 0.3 removed continuous recording, so there is no
background capture at all. A screenshot is taken only when you (or the agent) explicitly call
see_screen - The screenshot goes only to the current session's model. It travels the same channel as images you paste yourself, under the same data policy. The plugin sends nothing to any third party
- On disk the plugin keeps one image: a fixed path overwritten in place
(
${DSH_HOME}/vision/screen.png), deleted when the plugin stops - But the attachment store is a separate matter: every vision call adds a content-addressed file
under
${DSH_HOME}/attachmentsand nothing can clean it up (measured 215 files / 40.24 MB; see boundary 6) see_diffnever captures the screen: it only reads the two image files you point it at- Suggestion: call it when needed; there is no longer any reason to leave it "on", because it no longer captures anything by itself
Design notes
Why the plugin modules have zero dependencies
The loader resolves relative specifiers against the preset directory and bare package names against
the harness install — a local preset cannot reach @deepseek-ai/dsh-tools. So the plugin deliberately
depends on nothing: instead of defineTool(...) it constructs the runtime shape that helper produces,
and writes raw JSON Schema (which is why every schema spells out required). It imports only
node:fs and node:url.
Cost: no automatic parameter validation from defineTool, so every execute does its own type
coercion.
Why half the tools were deleted
The only criterion was: can the model already do this itself?
- It can (look at an image, read text in an image) → delete, use the built-in capability
- It cannot (see the screen, decide exactly which pixels changed) → keep
By that criterion see_image was duplication, the three diagnostics were not capabilities, and
continuous recording had no demonstrated value against a certain cost (unbounded attachment growth).
The plugin went from 8 tools and ~78 KB of source to 2 tools and ~54 KB.
Why the diff is computed locally
Vision models are poor at fine-grained change spotting and will invent "plausible" differences. A pixel diff is exact, free, and yields bounding boxes. So the split is: compute the diff locally, let the model only explain it.
Why see_screen returns the image instead of text
It used to hand the capture to a second model to transcribe. That path had three problems: an extra
layer of transcription error, subjection to that model's reasoning budget (which routinely consumed
the entire budget and left the prose empty), and a second billed call. Now the image is returned as a
content block for the current model to look at. transcribe: true remains for text-only session models.
Why the prompt puts "subject" first
Measured: if the prompt asks for verbatim text first, the model transcribes text and ignores the picture. After making "answer the subject first, text last" an ordering requirement, judging the selected object went from 0/3 to 2/2.
Verification status
| Item | Status |
|---|---|
| Screen / window / region capture, DPI-native resolution | ✅ verified standalone (including failure paths and idempotence) |
| Local pixel diff | ✅ verified standalone (synthetic + real photo) |
| Vision prompt | ✅ measured over several rounds |
| Tools hand the image to the model (image content block) | ✅ measured (a probe confirmed real delivery) |
see_screen's transcription compatibility path |
✅ measured over several rounds (see Cost) |
| Bundle install | ✅ verified end to end (0.3.0): dsh plugin --profile web add <local dir> succeeded; --dump-config shows - id: screen-reader in the composed tree; after a restart, querying Tool.listTools on the running process returns see_screen and see_diff — and they are visible in a session that did not select the screen preset, proving the row is host-plane and reaches every conversation. Both backends of the installed artifact were also exercised for real (capture 576×54; diff 4 regions with the input images surviving) |
| A single accuracy number | ⚠️ only one tiny-sample character accuracy (below); not a benchmark |
Practical value of screen_watch / screen_memory |
❌ never demonstrated by any real use case (the direct reason 0.3 removed them) |
vision_selftest scoring |
❌ never ran (removed) |
There is still no "92% accurate" benchmark figure here. Every item under "Measured effects" is a concrete case, not a benchmark.
The closest thing to a number: in one comparison over 5 session titles / 50 Chinese characters, full-screen 1920×1080 got 1 character wrong, and a 346×346 crop got 0 wrong. That sample is far too small to call an accuracy rate — all it shows is that the full-screen failure mode is visually similar characters, and that cropping fixes it.
Known issues and plans
What 0.3.2 changed (text-only session models go from broken to working):
see_screennow decides for itself whether the session model accepts images, and transcribes automatically when it does not. The root cause: before sending, the provider scansmessagesfor an image, and if the model does not declareimageininputModalitiesit throwsLlmErroroutright (dsh-llm-deepseekL1602-1605) — the whole request fails and the model never sees even the text. And image input is the minority of what a machine may have configured:Route Image input deepseek-official/deepseek-v4-flash❌ text only deepseek-official/deepseek-v4-pro❌ text only deepseek-official/deepseek-v4-flash-vision-exp✅ local-ollama/qwen3:1.7b/qwen3:0.6b/qwen3-4b-fast❌ text only (Measured on one machine: 1 of 6 routes accepts images.) So in a text-only session this tool is not merely unhelpful — calling it once wrecks that turn, and the agent learns to leave it alone. It now reads
{provider, model}fromagent.options(viaagents.currentInitiator(), verified working) and asksllm.resolveModelInfo(...).inputModalities; when images are not accepted it takes the transcription path.Two certain bugs fixed along the way:
- The transcription path still sent the image — the attachment was saved before the
wantTextbranch, andrenderadds an image block wheneverimageRefis present. Sotranscribe: truenever actually worked on a text-only model. The transcription path now writes no attachment and returns no image block. - The transcription path always judged itself a failure —
okwas written ashasImage && ..., and that path has noimageRefby design, sook=falseandrendertook its first branch: "看图失败:no capture". A working path was disabled by its own success test.okis now decided by which path actually ran.
- The transcription path still sent the image — the attachment was saved before the
When the session model cannot be determined, it is treated as "does not accept images." The risk is asymmetric: sending an image to a model that rejects it fails the entire conversation turn, while transcribing at worst costs one look without the original image.
The result gains
visionPath(image/transcribe),sessionModelandnote, and the rendered line says plainly that the session model does not accept images and a transcription was used — a degradation has to be stated, not hidden.
What 0.3.1 changed (the capture output contract) — this is a breaking field rename:
TARGETsplit intoSOURCE/SOURCESIZE/REGION.capture.ps1used to emit aTARGETthat carried both a description of what was captured and a size-looking string like1920x1080, whileSIZEheld the artifact's dimensions. SoTARGET=virtual screen 1920x1080could accompany a written file of1190x486— anything reading that field as an image size read the wrong number. This is not hypothetical: another session tripped over exactly that during a real acceptance test. Now each of the four lines says one thing:SOURCE(words only, no dimensions),SOURCESIZE(the captured surface),REGION(present only when a crop was used),SIZE(the artifact — the only field describing the output).see_screen's output fields follow:targetbecamesource+sourceSize+region, and the rendered line now separates the two sizes:截图范围:virtual screen(区域 …);源 1920x1080 → 成品 1190x486. If a prompt or script of yours readstarget, change it.- Also fixed a bug I introduced while making that change:
$rxis a[double], and an expression starting with a double makes PowerShell treat+ ','as numeric addition, throwingCannot convert value "," to type System.Double. Now uses-join. The old code only escaped this because it started from a string. - Verified across all four modes (screen / screen+region / window / window+region), and
SIZEwas checked byte-for-byte against the real dimensions in the PNG's IHDR.
What 0.3.0 changed:
see_diffdeleted your input images (data loss; fixed):imageops.ps1's retention rule deleted the N oldest files in the output directory no matter who wrote them. So when the two images you wanted to compare happened to live in the same folder as the crop files, retention ran after writing, protected this run's crops, and then ate youra.png/b.png. Silent data loss of exactly the thing that cannot be regenerated. Retention now deletes only files this tool itself produced (diff*_A.png/diff*_B.png), and every call site additionally protects the explicit inputs it was handed. Verified: inputs survive, and the crops still stop growing- Tool count 8 → 2 (criterion in Design notes). Removed
see_image,screen_watch,screen_memory,vision_routes,vision_selftest,vision_storage - Removed with them: the change fingerprint, rate-limit gate, rolling buffer, the
timerdependency, concurrency mutex — and a branch insee_screenthat could never be reached (it always calledobservewithforce=true, so those checks were dead code) see_screensource 50.3 KB → 35.1 KB;toolbox27.5 KB → 19.1 KB- Breaking-change list in the previous section
What 0.2.1 fixed:
- The published ps1 paths were broken (0.2.0's bundle could never work): now resolved at runtime
for both layouts (the same source is correct as a preset and as a package), with the generator
tools/build-lib.mjscommitted to the repo and asserting it capture.ps1 -Outwith a bare name polluted the current directory: bare names now resolve into${DSH_HOME}/vision/and get a.pngextension; absolute paths unchanged- Documentation correction: crop detail density revised down from a derived "2.7×" to a measured ~2×
Plans:
- Produce a real accuracy benchmark — the biggest remaining gap. It was going to be
vision_selftest, but that covered only the diff chain and neversee_screen, which is where a number is most needed. So it was removed and the benchmark needs redesigning - Rewrite the tool definitions with
@deepseek-ai/dsh-tools'defineToolto get parameter validation back - Wire up the
Configschema (tunables are file-header constants today) - Trim the now-unreferenced modes in
scripts/imageops.ps1(calib-draw/calib-bad/storage/prune), which existed for removed tools - Shape semantics: try a two-pass "locate the subject, then zoom into it", already proven to help selection accuracy
Known design trade-offs:
- About 40 lines of duplicated vision-routing and error handling between
lib/screen.jsandlib/toolbox.js. The reason is in the file headers: the loader caches modules by URL, and a relative import would tie the two plugins' lifetimes together - Diff bounding boxes are 30–45 px larger than the true change (tunable via
-Padding/-Dilate/-GridW) - The time dimension (rolling screen memory) has been abandoned. If a real use case appears, it can be
recovered from git history (
git show 98306ac5:lib/screen.js)
Reporting
This plugin is experimental and admits to plenty it cannot do. The feedback I want most is "here is where it got it wrong for you", not praise.
Please include:
- Install method (bundle / preset) and
dsh --version - The tool you called and its full arguments
- The complete return value or error (not just "it doesn't work")
- If an image is involved: attach that image. Without it I cannot tell whether the fault is the plugin or the model
License
MIT. See LICENSE.
Relation to other plugins
| Plugin | Focus | Relation |
|---|---|---|
Built-in read_image |
Read one image file | see_image was a duplicate of it; removed in 0.3 |
@liustack/modlens |
Image Q&A with structured JSON (★3.9k, L5, Gold) | More mature at "ask about one image". This plugin does not intend to compete there |
| This plugin | Eyes for the agent + exact pixel-change detection | Its distinct value is "the agent looks by itself" and "an exact local diff", not image Q&A |