dsh-vision-router 1.1.0 → 1.2.0

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/README.md CHANGED
@@ -10,8 +10,8 @@
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome · DSH plugin" /></a>
13
- <a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v1.1.0"><img src="https://img.shields.io/badge/release-v1.1.0-5B4CF0?style=flat-square" alt="Release v1.1.0" /></a>
14
- <a href="tests"><img src="https://img.shields.io/badge/verified-94%20tests-2EA44F?style=flat-square" alt="Verified: 94 tests" /></a>
13
+ <a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v1.2.0"><img src="https://img.shields.io/badge/release-v1.2.0-5B4CF0?style=flat-square" alt="Release v1.2.0" /></a>
14
+ <a href="tests"><img src="https://img.shields.io/badge/verified-144%20tests-2EA44F?style=flat-square" alt="Verified: 144 tests" /></a>
15
15
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat-square" alt="License: MIT" /></a>
16
16
  <a href="package.json"><img src="https://img.shields.io/badge/Node.js-%3E%3D22-339933?style=flat-square&amp;logo=nodedotjs&amp;logoColor=white" alt="Node.js >=22" /></a>
17
17
  <img src="https://img.shields.io/badge/runtime-no%20Python-8A2BE2?style=flat-square" alt="No Python" />
@@ -21,11 +21,12 @@
21
21
  <p align="center">English · <a href="README.zh.md">中文</a></p>
22
22
 
23
23
  > [!WARNING]
24
- > 📌 **Announcement (v1.1.0)**
24
+ > 📌 **Announcement (v1.2.0)**
25
25
  >
26
- > Now supports **Extra vision wrappers**: any custom text route (e.g. opencode) sends images out of the box — plus long-screenshot OCR, a "Test connection" button and artifact preview cards in the settings panel. Stealth mode is off by default and the official DeepSeek row is no longer taken over implicitly.
26
+ > v1.2.0 closes the loop on zero-config vision: the lower-right chat picker only selects conversation models, settings only select user vision models, internal `Vision HTTP` is hidden, and a five-model anonymous OVH chain remains the final no-signup/no-key fallback. It also adds the three-step model guide, durable inline `vision_present` images, BOM doctor/repair, resilient update checks/safe updates, and fixes image tool results poisoning text-only DeepSeek histories.
27
27
 
28
28
  <p align="center">
29
+ <img src="assets/vision-demo.gif"<p align="center">
29
30
  <img src="assets/vision-demo.gif" width="640" alt="Demo: paste an image, the agent locates the send button with vision_ground / vision_crop / vision_pixel_diff and answers with coordinates" />
30
31
  </p>
31
32
 
@@ -34,7 +35,7 @@
34
35
  Most DSH vision plugins bridge images to DeepSeek as *text descriptions* — lossy, one-shot, and blind to pixels. This plugin keeps the **original pixels on the vision model's side** and DeepSeek on the reasoning side, and makes looking at an image an **ordinary tool call**:
35
36
 
36
37
  - **One command install.** The package ships its own composition patch (`dsh.bundle.patch`): `dsh plugin add` wires the row, the admission wrapper and the attachment limits automatically — zero manual file edits. Taking over the official DeepSeek route is an optional setting (stealth mode, off by default).
37
- - **Free by default.** The vision chain starts with a built-in OVHcloud anonymous endpoint (`Qwen2.5-VL-72B-Instruct`, no account, no key, 2 req/min per IP). Paid chains (OpenRouter, Pi-AI providers, direct OpenAI-compatible endpoints) are optional upgrades.
38
+ - **Free by default.** Vision tools end with a five-model OVHcloud anonymous fallback: no account, no key, 2 requests/minute per IP per model, roughly 10 RPM in theory across independent buckets. User-provided vision models run first.
38
39
  - **No Python.** The whole pipeline — downscale, grounding, crop, pixel diff, palette, OCR, SVG trace, cutout, HTML screenshot — runs on sharp / potrace / tesseract / system Chrome.
39
40
  - **Continuous multi-step image work.** An image turn is a text turn that calls tools: `vision_ground` → `vision_crop` → `vision_describe` → `vision_pixel_diff` → fix → screenshot again. The agent keeps iterating until the work is done.
40
41
  - **DeepSeek stays the brain.** Text turns are untouched in model, cost and context. The vision model is only the eyes, called on demand; answers are cached by image content.
@@ -48,29 +49,60 @@ The closest alternative is [@anionex/dsh-vision-toolkit](https://github.com/Anio
48
49
  |---|---|---|
49
50
  | Image Q&A out of the box | ✅ Built-in free chain (anonymous OVHcloud endpoint) — no account, no key | Requires your own vision API key (local pixel tools work without one) |
50
51
  | Runtime | ✅ Node only — no Python | Python 3.11+ managed runtime |
51
- | Getting an image in | ✅ Paste it — the turn auto-routes to the vision chain and auto-mounts the tools | Workspace path + `/vision-tools` command, then explicit tool calls |
52
+ | Getting an image in | ✅ Pick a “+ Auto Vision” group once, then paste directly | Workspace path + `/vision-tools` command, then explicit tool calls |
52
53
  | Turn routing | ✅ Image turns switch to vision, text turns switch back to DeepSeek — optional stealth takeover keeps the model picker looking stock | Tool-driven; no whole-turn auto-routing |
53
54
  | Profiles | Web | Web + Headless |
54
55
  | Playbooks | The pixel loop: ground → crop → diff → fix → screenshot again | Richer case library (long-screenshot OCR, UI restoration, GUI automation) |
55
- | Tests | 86 | 162 |
56
+ | Tests | 144 | 162 |
56
57
  | Install | One command | One command (npm) |
57
58
 
58
59
  Both are MIT-licensed and one command away. Pick this plugin when you want images to *just work* with zero setup; pick theirs when you need headless profiles or the extended playbook library. (Feature comparison reflects their README as of 2026-08.)
59
60
 
60
61
  ## Quick start
61
62
 
63
+ ### 1. Install and load the plugin
64
+
65
+ Recommended for normal npm/npx installs (the same launch style used by the DSH README):
66
+
67
+ ```sh
68
+ npx @deepseek-ai/dsh plugin --profile web add dsh-vision-router
69
+ npx @deepseek-ai/dsh web
70
+ ```
71
+
72
+ If you run DeepSeek Harness from a source checkout with pnpm, use the workspace script instead — `dsh` is not necessarily on your shell `PATH`:
73
+
62
74
  ```sh
63
- dsh plugin --profile web add dsh-vision-router
75
+ cd deepseek-harness
76
+ pnpm dsh plugin --profile web add dsh-vision-router
77
+ pnpm dsh web
78
+ ```
79
+
80
+ If you already installed the DSH CLI globally and `dsh` is on `PATH`, the shorter `dsh ...` form works too.
81
+
82
+ > [!NOTE]
83
+ > If you install the plugin **into a Web process that was already running long-term**, let that DSH Web process reload once so the plugin bundle itself is discovered. After the plugin is loaded, adding/removing models or changing wrapper scope **hot-updates without further DSH restarts**.
84
+
85
+ ### 2. Switch to a “+ Auto Vision” model group in chat
86
+
87
+ Once loaded, the plugin discovers the model groups enabled under **Settings → Models** and creates same-name auto-vision entries. For example:
88
+
89
+ ```text
90
+ opencode-go ← original model group, unchanged
91
+ opencode-go + Auto Vision ← choose this when sending images
64
92
  ```
65
93
 
66
- Restart `dsh web` — done. Zero configuration:
94
+ > [!IMPORTANT]
95
+ > **Before sending an image, open the model selector in the lower-right corner of the chat composer and choose a group marked “+ Auto Vision”.**
96
+ >
97
+ > Vision Router deliberately **does not modify the original model group**. If the conversation still uses the original text-only opencode / DeepSeek route, DSH can reject the image with “the current model does not support images” *before Vision Router gets a chance to handle it*. That is a model-entry selection issue, not a broken vision backend.
98
+
99
+ The auto-vision group follows the live DSH model catalog. Adding models or changing wrapper scope does not require a restart.
67
100
 
68
- - the plugin's bundle patch mounts the row, adds the admission wrapper and relaxes attachment limits to 20 MB / 100 MP — pure-additive, it never touches the core rows; whether the official DeepSeek route is taken over is decided by the optional stealth setting (off by default);
69
- - the default vision chain is the built-in free endpoint;
70
- - custom/third-party routes (e.g. opencode) gain image input through **Extra vision wrappers**;
71
- - every setting is editable live in **Settings → Plugins → Plugin config → 视觉路由(自动识图)**.
101
+ ### 3. Paste or upload the image
72
102
 
73
- Then just paste an image into a conversation. The agent mounts the vision tools automatically and looks at it through `vision_describe` (and friends) — multi-step if needed.
103
+ After choosing the “+ Auto Vision” group, paste or upload an image normally. The agent auto-mounts the vision tools and can use `vision_describe`, `vision_ground`, `vision_crop`, and the rest across multiple steps when needed.
104
+
105
+ The built-in anonymous OVH vision fallback is already configured, so normal image use needs no signup or API key. **The lower-right chat picker selects only the brain/conversation model**; vision backends do not belong there. Advanced options live under **Settings → Plugins → Plugin config → 视觉路由(自动识图)**: each vision-backend row selects one image-capable user model already configured under **Settings → Models**. Leaving every user row empty is valid; the OVH chain remains the final fallback. `Vision HTTP` is an internal transport route, not a model group users should select.
74
106
 
75
107
  ### See it in action
76
108
 
@@ -148,11 +180,14 @@ vision_long_screenshot_ocr image="chat-log.png" chunkHeight=1200 overlap=120
148
180
 
149
181
  ## Provider fallback chain
150
182
 
151
- The vision chain walks providers in order and only surfaces an error after every one failed:
183
+ The vision tools try backends in order and surface an error only after all of them fail:
184
+
185
+ 1. **User vision models**: one per settings row, top to bottom; only models under **Settings → Models** that explicitly declare image input are shown;
186
+ 2. **Advanced custom HTTP vision endpoints**: legacy/advanced `httpProviders`, when present, run after the user models;
187
+ 3. **Built-in anonymous OVH fallback**: always last and never exposed in a model picker. The current quality-first chain is `Qwen3.5-397B-A17B` → `Qwen2.5-VL-72B-Instruct` → `Qwen3.6-27B` → `Mistral-Small-3.2-24B-Instruct-2506` → `Qwen3.5-9B`. OVH anonymous limits are **2 requests/minute per IP per model**. The five models have independent buckets, so spreading requests across them is about **10 RPM in theory**, subject to OVH's actual rate limiting. No signup or API key is required.
152
188
 
153
- 1. the **built-in free endpoint** (`vision-http` → `ovh/Qwen2.5-VL-72B-Instruct`) — no key, best-effort, 2 req/min per IP;
154
- 2. configured `httpProviders` (direct OpenAI-compatible endpoints with optional `apiKeyEnv`);
155
- 3. configured `providers` / `provider` + `fallbacks` (any adapter-backed provider, e.g. a Pi-AI profile like OpenRouter or Zhipu).
189
+ > [!IMPORTANT]
190
+ > This “vision chain” is the **eyes** used by Vision Router: each settings row selects one user vision model, while the lower-right chat picker selects the **brain/conversation model**. The two are deliberately separate. Text-only DeepSeek/opencode models are filtered out of the vision-backend dropdown, and the internal `Vision HTTP` transport route is no longer exposed to users.
156
191
 
157
192
  > In the legacy `routing: true` mode, the whole-turn chain walks only `provider + fallbacks` — `httpProviders` (including the free fallback) do not participate there. The default `routing: false` (tools-first) tries everything.
158
193
 
@@ -172,21 +207,29 @@ With stealth on, the plugin takes over the official `deepseek-official` route: t
172
207
 
173
208
  With the stock row present, the plugin falls back to the visible wrapper entry. Conversely, with stealth off but the stock row still disabled, the plugin performs a keep-alive takeover so the DeepSeek models don't vanish (the settings card explains this); to restore the fully official route, flip the `disabled` above back to `false` and restart.
174
209
 
175
- > Stealth mode **only affects the official DeepSeek route**. Custom/third-party routes like opencode are unrelated — use **Extra vision wrappers** below to give them image input.
210
+ > Stealth mode **only affects the official DeepSeek route**. Custom/third-party routes such as opencode are auto-wrapped into “+ Auto Vision” groups by default.
211
+
212
+ ## Auto-vision model groups and manual wrappers
176
213
 
177
- ## Extra vision wrappers
214
+ `autoWrapProviders` is on by default. The plugin discovers the provider/model entries currently enabled under **Settings → Models** and registers a same-name “+ Auto Vision” model group for them. **The original group is never changed**: choose the auto-vision group for images, or keep using the original group for plain text. DSH `llm/adapters-updated` events are synced live, so adding/removing models does not require a restart.
178
215
 
179
- `wrappedProviders` registers an auto-vision twin for any third-party/custom text route: pick the twin in the model selector and send images; text turns delegate to the original route unchanged. The typical use case is a custom interface such as opencode — it only declares text input, and one wrapper row makes it image-ready. In the settings card, configure it with two dropdowns (provider + model); leaving the model empty wraps every model of that route.
216
+ `wrappedProviders` is an **optional manual scope control**, not a required setup step. Use it only when:
217
+
218
+ 1. automatic wrapping is off and you want to pick which provider/models receive an auto-vision entry; or
219
+ 2. automatic wrapping remains on but one provider should expose only selected models in its “+ Auto Vision” group.
220
+
221
+ The settings card uses provider + model dropdowns; an empty model means every model on that route. Add multiple rows to select multiple models. Changes apply immediately with no restart.
180
222
 
181
223
  ## Web settings
182
224
 
183
- The Web profile registers a **视觉路由(自动识图)** card under **Settings → Plugins → Plugin config**, styled like the built-in cards. It live-edits:
225
+ The Web profile registers a **视觉路由(自动识图)** card under **Settings → Plugins → Plugin config**. Its top callout spells out the only step most users need: **return to chat → lower-right model selector → choose a “+ Auto Vision” model group → send the image**. The remaining controls are advanced customization:
184
226
 
185
- - switches: whole-turn legacy routing, vision tools, image-block rewriting, stealth (official DeepSeek route only);
186
- - **extra vision wrappers**: provider + model dropdowns that register image-capable twin entries for custom routes such as opencode;
187
- - vision request timeout, wrapper/chain route names;
188
- - the **vision chain** (one `provider/model` per line, top-down fallback) and the text model;
189
- - every field shows an "overridden" badge with a one-click reset to the composition default, plus discard/save;
227
+ - **Auto-create “+ Auto Vision” model groups**: enabled by default; follows the live model catalog with no restart;
228
+ - **Manual auto-vision scope (optional)**: only for disabling auto-wrap or limiting selected models;
229
+ - **Vision backend chain**: the real image-capable models used by `vision_describe` and friends; the built-in free Qwen is normally enough, and text-only models should not be placed here;
230
+ - switches for legacy whole-turn routing, vision tools, image-block rewriting and stealth mode (official DeepSeek route only);
231
+ - timeout, wrapper/chain route names, proxy and other advanced parameters;
232
+ - every field shows an overridden badge with one-click reset plus discard/save;
190
233
  - a **Test connection** button probes the first vision provider and reports latency inline;
191
234
  - artifact-producing tools render dedicated call cards with result facts and open-file buttons.
192
235
 
@@ -202,15 +245,16 @@ Everything is optional; defaults work out of the box. Edit via the Web card or a
202
245
 
203
246
  | Field | Default | Meaning |
204
247
  |---|---|---|
205
- | `provider` / `model` | `vision-http` / `ovh/Qwen2.5-VL-72B-Instruct` | shorthand chain (adapter-backed provider + model) |
206
- | `fallbacks` | `[]` | backup models for the shorthand provider |
207
- | `providers` | built-in free `vision-http` pair | multi-provider chain `{ provider, model, fallbacks[] }`, tried in order; wins over the shorthand. The first row ships as the built-in free model |
248
+ | `provider` / `model` | `vision-http` / `ovh/Qwen2.5-VL-72B-Instruct` | shorthand **vision backend** route (adapter-backed provider + model that genuinely accepts images) |
249
+ | `fallbacks` | `[]` | backup image models for the shorthand vision provider |
250
+ | `providers` | built-in free `vision-http` pair | multi-provider **vision backend** chain `{ provider, model, fallbacks[] }`, tried in order; do not put text-only models here |
208
251
  | `httpProviders` | built-in OVH entry | direct OpenAI-compatible endpoints `{ name, baseURL, model, apiKeyEnv, maxTokens }` |
209
- | `wrappedProviders` | `[{ provider: 'deepseek-official', models: [] }]` | extra text routes to wrap as image-capable twins: `{ provider, models[] }` — registers an auto-vision twin for any custom/third-party route (e.g. opencode); in the card, provider + model dropdowns, empty model = wrap all. The pre-filled deepseek-official row marks the built-in wrapper and is a no-op; changes apply live |
252
+ | `autoWrapProviders` | `true` | discover enabled provider/models and live-sync same-name “+ Auto Vision” groups; original groups stay unchanged |
253
+ | `wrappedProviders` | `[{ provider: 'deepseek-official', models: [] }]` | optional manual wrapper scope `{ provider, models[] }`, used after disabling auto-wrap or to restrict one provider to selected models; changes apply live, no restart |
210
254
  | `routing` | `false` | legacy whole-turn chain routing (one-shot answer). `false` = tools-first flow (recommended) |
211
255
  | `reverseRouting` | `true` | with `routing: true`, route text turns back to `textProvider` |
212
256
  | `wrapperRoute` / `chainRoute` | `deepseek-vision` / `vision-chain` | admission wrapper route name / fallback chain route name (empty disables) |
213
- | `stealth` | `false` | take over the official `deepseek-official` route (official row only; custom routes use `wrappedProviders`) |
257
+ | `stealth` | `false` | take over the official `deepseek-official` route (official row only; custom routes are auto-wrapped by default) |
214
258
  | `textProvider` | `deepseek-official` / `deepseek-v4-pro` | the model that reasons (your daily model) |
215
259
  | `tool` / `progressiveTools` / `autoActivateOnImage` | `true` ×3 | vision tools on / progressive mounting / auto-mount on image turns |
216
260
  | `rewriteImages` | `true` | rewrite image blocks in the model input (cached description or tool-hint marker); the UI log keeps images |
@@ -222,7 +266,7 @@ Everything is optional; defaults work out of the box. Edit via the Web card or a
222
266
 
223
267
  ## Requirements
224
268
 
225
- - DeepSeek Harness with a Web profile and `pnpm` available to `dsh plugin`.
269
+ - DeepSeek Harness Web profile. Normal installs can use `npx @deepseek-ai/dsh ...`; source checkouts use `pnpm dsh ...`. A bare `dsh ...` command only works when the CLI is already on your shell `PATH`.
226
270
  - Node ≥ 22 (host side).
227
271
  - No API key for the default free chain; a credential reference (`apiKeyEnv`) only for paid `httpProviders`.
228
272
  - Chrome / Chromium / Edge only for `vision_html_screenshot`; every other tool works without a browser.
@@ -232,12 +276,21 @@ Everything is optional; defaults work out of the box. Edit via the Web card or a
232
276
 
233
277
  ### Install
234
278
 
279
+ Normal npm/npx install:
280
+
281
+ ```sh
282
+ npx @deepseek-ai/dsh plugin --profile web add dsh-vision-router
283
+ npx @deepseek-ai/dsh --profile web --dump-config | grep vision-router
284
+ ```
285
+
286
+ From a DeepSeek Harness source checkout:
287
+
235
288
  ```sh
236
- dsh plugin --profile web add dsh-vision-router
237
- dsh --profile web --dump-config | grep vision-router # one row, mounted by the bundle patch
289
+ pnpm dsh plugin --profile web add dsh-vision-router
290
+ pnpm dsh --profile web --dump-config | grep vision-router
238
291
  ```
239
292
 
240
- Restart a long-lived Web profile. The host discovers the browser bundle through `dsh.client` at startup.
293
+ When first adding the plugin to an already long-lived Web profile, let that Web process reload the plugin bundle; the host discovers the browser bundle through `dsh.client` at startup. **After the plugin is loaded, model-catalog and wrapper-scope changes hot-update and do not require a restart.**
241
294
 
242
295
  ### Disable / re-enable
243
296
 
@@ -251,19 +304,79 @@ Set it back to `false` to re-enable. Unloading removes the wrapper routes, tools
251
304
  ### Upgrade
252
305
 
253
306
  ```sh
254
- dsh plugin --profile web update dsh-vision-router
307
+ # normal npm/npx install
308
+ npx @deepseek-ai/dsh plugin --profile web update dsh-vision-router
309
+
310
+ # DeepSeek Harness source checkout
311
+ pnpm dsh plugin --profile web update dsh-vision-router
255
312
  ```
256
313
 
257
314
  Settings live in the profile's settings provider and survive upgrades.
258
315
 
316
+ > **Upgrading from a pre-bundle-patch install (v0.x):** the package now mounts
317
+ > itself through its own bundle patch, so a leftover manual row in
318
+ > `~/.dsh/profiles/<profile>/cordis.patch.yml` duplicates it and `dsh web`
319
+ > fails at startup with `duplicate loader entry id: vision-router`. Delete the
320
+ > old block:
321
+ >
322
+ > ```yaml
323
+ > - insert: # remove this whole block
324
+ > - id: vision-router
325
+ > name: dsh-vision-router
326
+ > ```
327
+ >
328
+ > To keep custom settings, replace it with a plain by-id override (no
329
+ > `insert`):
330
+ >
331
+ > ```yaml
332
+ > - id: vision-router
333
+ > config:
334
+ > # your overrides …
335
+ > ```
336
+
259
337
  ### Uninstall
260
338
 
261
339
  ```sh
262
- dsh plugin --profile web remove dsh-vision-router
340
+ # normal npm/npx install
341
+ npx @deepseek-ai/dsh plugin --profile web remove dsh-vision-router
342
+
343
+ # DeepSeek Harness source checkout
344
+ pnpm dsh plugin --profile web remove dsh-vision-router
263
345
  ```
264
346
 
265
347
  This removes the dependency and the bundle layer. If you disabled the stock DeepSeek row manually, re-enable it in your profile patch.
266
348
 
349
+ ## Troubleshooting
350
+
351
+ ### Startup fails with `Unexpected token ... is not valid JSON` (UTF-8 BOM)
352
+
353
+ **Symptom:** `dsh web` / `pnpm dsh web` exits immediately at startup:
354
+
355
+ ```
356
+ SyntaxError: Unexpected token ...
357
+ is not valid JSON
358
+ at JSON.parse (<anonymous>)
359
+ at readProfileManifest (packages/boot/app-boot/src/profile.ts)
360
+ ```
361
+
362
+ **Cause:** `~/.dsh/profiles/<profile>/package.json` was saved as **UTF-8 with BOM** by an editor. The invisible `\uFEFF` character at the start makes `JSON.parse` fail, because JSON does not allow it before the opening brace.
363
+
364
+ **Recommended fix:** run Vision Router's standalone repair command. It does not require DSH to boot first; it locates the profile, detects a UTF-8 BOM, removes only the three leading BOM bytes, and then validates the JSON again:
365
+
366
+ ```sh
367
+ npx dsh-vision-router repair --profile web
368
+ ```
369
+
370
+ To diagnose without changing the file:
371
+
372
+ ```sh
373
+ npx dsh-vision-router doctor --profile web
374
+ ```
375
+
376
+ Replace `web` if you use another profile, or omit `--profile` to scan all profiles.
377
+
378
+ Manual fallback: in VS Code, use “Save with Encoding” → `UTF-8` (without BOM). If `repair` removes the BOM but the JSON is still invalid, it will not guess or rewrite any other JSON content; inspect the file manually.
379
+
267
380
  ## Security notes
268
381
 
269
382
  - Image text is **untrusted evidence**: descriptions, OCR output and the auto-mount note all tell the agent never to execute instructions found inside images.
package/README.zh.md CHANGED
@@ -10,8 +10,8 @@
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome · DSH plugin" /></a>
13
- <a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v1.1.0"><img src="https://img.shields.io/badge/release-v1.1.0-5B4CF0?style=flat-square" alt="Release v1.1.0" /></a>
14
- <a href="tests"><img src="https://img.shields.io/badge/verified-94%20tests-2EA44F?style=flat-square" alt="Verified: 94 tests" /></a>
13
+ <a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v1.2.0"><img src="https://img.shields.io/badge/release-v1.2.0-5B4CF0?style=flat-square" alt="Release v1.2.0" /></a>
14
+ <a href="tests"><img src="https://img.shields.io/badge/verified-144%20tests-2EA44F?style=flat-square" alt="Verified: 144 tests" /></a>
15
15
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat-square" alt="License: MIT" /></a>
16
16
  <a href="package.json"><img src="https://img.shields.io/badge/Node.js-%3E%3D22-339933?style=flat-square&amp;logo=nodedotjs&amp;logoColor=white" alt="Node.js >=22" /></a>
17
17
  <img src="https://img.shields.io/badge/runtime-no%20Python-8A2BE2?style=flat-square" alt="No Python" />
@@ -21,11 +21,12 @@
21
21
  <p align="center"><a href="README.md">English</a> · 中文</p>
22
22
 
23
23
  > [!WARNING]
24
- > 📌 **公告(v1.1.0)**
24
+ > 📌 **公告(v1.2.0)**
25
25
  >
26
- > 现已支持「**额外识图包装**」:opencode 等任意自定义文本路由开箱即可发图;另含长截图 OCR、设置卡片「测试连接」与视觉产物预览卡。隐身模式默认关闭,官方 DeepSeek 行不再被默认接管。
26
+ > v1.2.0 把“开箱即用”这条链路完整收口:聊天页右下角只选会话模型,设置页只选用户视觉模型,内部 `Vision HTTP` 不再暴露;内置 5 模型 OVH 匿名免费链固定在最后兜底,免注册、免 Key。新增三步模型引导、`vision_present` 持久内联图片、BOM doctor/repair、版本检查/安全更新,并修复图片工具结果污染纯文本 DeepSeek 历史导致后续对话崩溃的问题。
27
27
 
28
28
  <p align="center">
29
+ <img src="assets/vision-demo.gif"<p align="center">
29
30
  <img src="assets/vision-demo.gif" width="640" alt="演示:粘贴图片,Agent 用 vision_ground / vision_crop / vision_pixel_diff 定位发送按钮并给出坐标" />
30
31
  </p>
31
32
 
@@ -34,7 +35,7 @@
34
35
  大多数 DSH 视觉插件把图片“翻译”成一段文字描述再喂给 DeepSeek——有损、一次性、看不见像素。本插件把**原图像素留在视觉模型侧**、把推理留在 DeepSeek 侧,并把“看图”变成一次**普通的工具调用**:
35
36
 
36
37
  - **一条命令安装。** 包自带组合补丁(`dsh.bundle.patch`):`dsh plugin add` 自动完成插件行挂载、准入包装与附件限制放宽——不用手改任何文件。是否接管官方 DeepSeek 路由由「隐身模式」开关决定(默认关)。
37
- - **默认免费。** 视觉链内置 OVHcloud 匿名端点(`Qwen2.5-VL-72B-Instruct`,免注册、免 Key,每 IP 2 次/分钟)。付费链路(OpenRouter、Pi-AI 供应商、任意 OpenAI 兼容直连端点)是可选升级。
38
+ - **默认免费。** 视觉工具最终兜底为 5 个 OVHcloud 匿名视觉模型:免注册、免 Key,每 IP、每模型 2 次/分钟,独立限额理论合计约 10 次/分钟;用户自备视觉模型会优先调用。
38
39
  - **无 Python。** 整条管线——缩放、定位、裁剪、像素对比、取色、OCR、SVG 矢量化、抠图、HTML 截图——全部基于 sharp / potrace / tesseract / 系统 Chrome。
39
40
  - **可连续多步看图。** 图片轮 = 调用工具的文本轮:`vision_ground` → `vision_crop` → `vision_describe` → `vision_pixel_diff` → 修复 → 再截图,Agent 可以一直迭代到任务完成。
40
41
  - **DeepSeek 始终是大脑。** 文字轮在模型、成本、上下文上完全不动;视觉模型只当“眼睛”、按需调用,答案按图片内容缓存。
@@ -48,29 +49,60 @@
48
49
  |---|---|---|
49
50
  | 开箱图片问答 | ✅ 内置免费视觉链(OVHcloud 匿名端点),免注册免 Key | 远程工具需自备视觉 API Key(本地像素工具免 Key) |
50
51
  | 运行时 | ✅ 纯 Node,无需 Python | 需要 Python 3.11+ 受管运行时 |
51
- | 图片怎么进来 | ✅ 直接粘贴——轮次自动切视觉链并自动挂载工具 | 工作区路径 + `/vision-tools` 命令,再显式调用工具 |
52
+ | 图片怎么进来 | ✅ 选一次「+ 自动识图」模型组后直接粘贴 | 工作区路径 + `/vision-tools` 命令,再显式调用工具 |
52
53
  | 轮次路由 | ✅ 图片轮切视觉、文本轮切回 DeepSeek——可选隐身接管,模型选择器与官方一致 | 工具驱动,无整轮自动路由 |
53
54
  | 支持 profile | Web | Web + Headless |
54
55
  | 玩法库 | 像素循环:定位 → 裁剪 → 对比 → 修复 → 再截图 | 更丰富的案例库(长截图 OCR、UI 还原、GUI 自动化) |
55
- | 测试 | 86 | 162 |
56
+ | 测试 | 144 | 162 |
56
57
  | 安装 | 一条命令 | 一条命令(npm) |
57
58
 
58
59
  两者都是 MIT 许可、一条命令安装。想要图片**粘贴即用**、零配置就选本插件;需要 Headless 部署或更丰富的案例库,可以看 @anionex/dsh-vision-toolkit。(功能对比以其 README 2026-08 状态为准。)
59
60
 
60
61
  ## 快速开始
61
62
 
63
+ ### 1. 安装并让插件加载
64
+
65
+ 普通 npm / npx 安装方式推荐这样用(与 DSH 官方 README 的启动方式一致):
66
+
67
+ ```sh
68
+ npx @deepseek-ai/dsh plugin --profile web add dsh-vision-router
69
+ npx @deepseek-ai/dsh web
70
+ ```
71
+
72
+ 如果你是从 DeepSeek Harness 源码仓库通过 pnpm 运行,`dsh` 不一定在系统 `PATH` 里,请改用工作区脚本:
73
+
62
74
  ```sh
63
- dsh plugin --profile web add dsh-vision-router
75
+ cd deepseek-harness
76
+ pnpm dsh plugin --profile web add dsh-vision-router
77
+ pnpm dsh web
78
+ ```
79
+
80
+ 如果你已经全局安装 DSH CLI,并且终端里能直接执行 `dsh`,也可以继续使用较短的 `dsh ...` 写法。
81
+
82
+ > [!NOTE]
83
+ > 如果你是把插件**首次安装进一个已经长期运行的 Web 进程**,需要让 DSH Web 进程重新加载一次插件本体。插件加载完成后,新增/删除模型、修改自动识图包装范围都会**热更新,无需再重启 DSH**。
84
+
85
+ ### 2. 在聊天页切换到「+ 自动识图」模型组
86
+
87
+ 插件加载后会自动发现 **设置 → 模型** 里已启用的模型组,并为它们额外创建同名的自动识图入口。例如:
88
+
89
+ ```text
90
+ opencode-go ← 原模型组,保持不变
91
+ opencode-go + 自动识图 ← 发图片时选这个
64
92
  ```
65
93
 
66
- 重启 `dsh web`——完成,零配置:
94
+ > [!IMPORTANT]
95
+ > **发图前,请点击聊天页输入区右下角的模型选择器,选择带「+ 自动识图」的模型组。**
96
+ >
97
+ > Vision Router 故意**不修改原模型组**。因此如果当前仍选着原来的纯文本 `opencode-go` / DeepSeek 路由,DSH 会在插件处理图片之前先提示“当前模型不支持图片”。这不是视觉后端配置失败,只是还没有切到自动识图入口。
98
+
99
+ 这个模型组的模型列表会跟随 DSH 的模型目录实时同步;新增模型或修改包装范围后无需重启。
67
100
 
68
- - 插件的 bundle 补丁自动挂载插件行、挂上准入包装并放宽附件限制到 20MB / 1 亿像素——纯增量、不碰核心行;是否接管官方 DeepSeek 路由由「隐身模式」开关决定(默认关);
69
- - 默认视觉链就是内置免费端点;
70
- - opencode 等自定义/第三方路由用「额外识图包装」获得发图能力;
71
- - 全部配置可在 **设置 → 插件 → 插件配置 → 视觉路由(自动识图)** 实时修改。
101
+ ### 3. 直接粘贴或上传图片
72
102
 
73
- 然后直接往对话里贴一张图。Agent 自动挂载视觉工具,通过 `vision_describe`(以及其余 8 个工具)看图,需要时连续多步。
103
+ 选好「+ 自动识图」模型组后,直接往对话里贴图即可。Agent 会自动挂载视觉工具,通过 `vision_describe`、`vision_ground`、`vision_crop` 等工具看图,需要时连续多步操作。
104
+
105
+ 默认已经有内置 OVH 匿名视觉兜底,无需注册、无需 Key。**聊天页右下角只选择“脑子/会话模型”**;视觉模型不要在那里选。高级配置在 **设置 → 插件 → 插件配置 → 视觉路由(自动识图)**:视觉后端链每一行只选择一个你在 **设置 → 模型** 中已经配置且支持图片输入的用户模型;一行都不填也可以,OVH 免费链会固定在最后兜底。插件内部的 `Vision HTTP` 只是传输实现,不是用户需要选择的模型组。
74
106
 
75
107
  ### 实际效果
76
108
 
@@ -148,11 +180,14 @@ vision_long_screenshot_ocr image="chat-log.png" chunkHeight=1200 overlap=120
148
180
 
149
181
  ## 供应商降级链
150
182
 
151
- 视觉链按顺序逐个尝试,全部失败才报错:
183
+ 视觉工具按顺序逐个尝试,全部失败才报错:
184
+
185
+ 1. **用户视觉模型**:设置卡里一行一个,从上到下;只显示 **设置 → 模型** 中明确声明支持 image 输入的模型;
186
+ 2. **高级自定义 HTTP 视觉端点**:如果旧配置/高级配置中存在 `httpProviders`,在用户模型之后尝试;
187
+ 3. **内置 OVH 匿名免费兜底**:固定最后尝试,不需要出现在任何模型选择器里。当前内置链按质量优先为 `Qwen3.5-397B-A17B` → `Qwen2.5-VL-72B-Instruct` → `Qwen3.6-27B` → `Mistral-Small-3.2-24B-Instruct-2506` → `Qwen3.5-9B`。OVH 匿名限额为 **每 IP、每模型 2 次/分钟**;5 个模型是独立限额,因此理论上分散请求可到约 **10 次/分钟**,实际仍以 OVH 当时的限流为准。免注册、免 Key。
152
188
 
153
- 1. **内置免费端点**(`vision-http` → `ovh/Qwen2.5-VL-72B-Instruct`)——免 Key、尽力而为、每 IP 2 次/分钟;
154
- 2. 配置的 `httpProviders`(OpenAI 兼容直连端点,可选 `apiKeyEnv`);
155
- 3. 配置的 `providers` / `provider` + `fallbacks`(任何有适配器的供应商,例如 Pi-AI 配置的 OpenRouter 或智谱)。
189
+ > [!IMPORTANT]
190
+ > 这里的“视觉链”是 Vision Router 调用的**眼睛**:设置页里每一行只选一个用户视觉模型;聊天页右下角选择的是**脑子/会话模型**,两者完全分开。纯文本 DeepSeek / opencode 不会出现在视觉后端下拉里;内部 `Vision HTTP` 也不会再暴露给用户。
156
191
 
157
192
  > 在旧版 `routing: true` 模式下,整轮链只走 `provider + fallbacks`——`httpProviders`(含免费兜底)不参与。默认的 `routing: false`(工具优先)会尝试全部。
158
193
 
@@ -172,20 +207,28 @@ vision_long_screenshot_ocr image="chat-log.png" chunkHeight=1200 overlap=120
172
207
 
173
208
  官方行在场时,插件自动回退为可见包装入口。反过来,隐身模式关闭但官方行仍被禁用时,插件会做 keep-alive 兜底接管,保住 DeepSeek 模型(设置卡片会给出提示);想完全恢复官方原生行,把上面的 `disabled` 改回 `false` 再重启即可。
174
209
 
175
- > 隐身模式**只作用于官方 DeepSeek 路由**。opencode 等自定义/第三方文本路由与隐身模式无关——用「额外识图包装」让它们支持发图。
210
+ > 隐身模式**只作用于官方 DeepSeek 路由**。opencode 等自定义/第三方文本路由与隐身模式无关——默认会被自动包装成「+ 自动识图」模型组。
211
+
212
+ ## 自动识图模型组与手动包装
176
213
 
177
- ## 额外识图包装
214
+ 默认开启 `autoWrapProviders`:插件会自动发现 **设置 → 模型** 中当前已启用的 provider / model,并额外注册同名的「+ 自动识图」模型组。**原模型组完全不变**;发图片时选自动识图组,纯文字仍可继续用原组。DSH 的 `llm/adapters-updated` 变化会触发同步,所以新增/删除模型后无需重启。
178
215
 
179
- `wrappedProviders` 给任意第三方/自定义文本路由注册「自动识图」孪生条目:模型选择器里选中它就能发图,文字轮原样交给原路由处理。典型用法是 opencode 等接入的自定义接口——它们默认只声明文本输入,加一行包装即可直接发图。设置卡片里用两个下拉(provider + 模型)配置;模型留空 = 包装该路由的全部模型,同一 provider 要包装多个模型就添加多行。
216
+ `wrappedProviders` 是**可选的手动范围控制**,不是普通用户必须配置的步骤。只有两种情况需要它:
217
+
218
+ 1. 关闭了自动包装,想手动指定哪些 provider / model 获得自动识图入口;
219
+ 2. 自动包装保持开启,但只想让某个 provider 的部分模型出现在「+ 自动识图」组。
220
+
221
+ 设置卡片里用两个下拉(provider + 模型)配置;模型留空 = 包装该路由的全部模型,同一 provider 要限定多个模型就添加多行。修改即时生效,无需重启。
180
222
 
181
223
  ## Web 设置
182
224
 
183
- Web 配置页在 **设置 → 插件 → 插件配置** 下注册「视觉路由(自动识图)」卡片,样式与内置卡片一致,可实时修改:
225
+ Web 配置页在 **设置 → 插件 → 插件配置** 下注册「视觉路由(自动识图)」卡片,顶部会直接提示最重要的使用步骤:**回到聊天页 → 右下角模型选择器 → 选择「+ 自动识图」模型组 → 发图**。其余设置主要用于高级定制:
184
226
 
227
+ - **自动创建「+ 自动识图」模型组**:默认开启,自动发现已有模型;模型目录变化热更新,无需重启;
228
+ - **手动限定自动识图范围(可选)**:仅在需要关闭自动包装或限制部分模型时使用;
229
+ - **视觉后端链**:给 `vision_describe` 等视觉工具调用的真正图片模型,默认内置免费 Qwen 即可;不要填纯文本模型;
185
230
  - 开关:整轮自动路由(旧模式)、识图工具、图片块改写、隐身模式(仅官方 DeepSeek 路由);
186
- - **额外识图包装**:provider + 模型双下拉,给 opencode 等自定义路由注册可发图的孪生条目;
187
- - 视觉请求超时、包装/链路由名;
188
- - **视觉模型链**(每行一个 `provider/model`,自上而下降级)与文本模型;
231
+ - 视觉请求超时、包装/链路由名、代理等高级参数;
189
232
  - 每个字段都有「已覆盖」徽标与一键恢复组合默认,以及放弃/保存;
190
233
  - 「测试连接」按钮探测第一个视觉提供方并内联显示延迟/失败原因;
191
234
  - 产出制品的工具在对话里渲染专用调用卡(关键字段 + 打开文件按钮)。
@@ -202,15 +245,16 @@ Web 配置页在 **设置 → 插件 → 插件配置** 下注册「视觉路由
202
245
 
203
246
  | 字段 | 默认值 | 含义 |
204
247
  |---|---|---|
205
- | `provider` / `model` | `vision-http` / `ovh/Qwen2.5-VL-72B-Instruct` | 简写链路(有适配器的供应商 + 模型) |
206
- | `fallbacks` | `[]` | 简写供应商的备用模型 |
207
- | `providers` | 内置免费 `vision-http` 条目 | 多供应商链路 `{ provider, model, fallbacks[] }`,按序尝试;优先于简写形式。第一行开箱预置内置免费模型 |
248
+ | `provider` / `model` | `vision-http` / `ovh/Qwen2.5-VL-72B-Instruct` | 简写视觉后端链路(有适配器且真正支持图片输入的供应商 + 模型) |
249
+ | `fallbacks` | `[]` | 简写视觉供应商的备用图片模型 |
250
+ | `providers` | 内置免费 `vision-http` 条目 | 多供应商视觉后端链 `{ provider, model, fallbacks[] }`,按序尝试;优先于简写形式。不要填写纯文本模型 |
208
251
  | `httpProviders` | 内置 OVH 条目 | OpenAI 兼容直连端点 `{ name, baseURL, model, apiKeyEnv, maxTokens }` |
209
- | `wrappedProviders` | `[{ provider: 'deepseek-official', models: [] }]` | 额外识图包装:`{ provider, models[] }`,给 opencode 等任意第三方/自定义文本路由注册可发图的孪生条目(卡片里 provider + 模型双下拉;模型留空 = 包装全部)。预置的 deepseek-official 条目标记内置包装、无副作用;改动即时生效 |
252
+ | `autoWrapProviders` | `true` | 自动发现当前已启用 provider / model,并热更新同名「+ 自动识图」模型组;原模型组不变 |
253
+ | `wrappedProviders` | `[{ provider: 'deepseek-official', models: [] }]` | 可选的手动包装范围 `{ provider, models[] }`;用于关闭自动包装后手动指定,或限制某个 provider 只包装部分模型。改动即时生效,无需重启 |
210
254
  | `routing` | `false` | 旧版整轮链路由(一次性整轮回答)。`false` = 工具优先流程(推荐) |
211
255
  | `reverseRouting` | `true` | 开启 `routing` 时,文字轮路由回 `textProvider` |
212
256
  | `wrapperRoute` / `chainRoute` | `deepseek-vision` / `vision-chain` | 准入包装路由名 / 降级链路由名(置空关闭) |
213
- | `stealth` | `false` | 接管官方 `deepseek-official` 路由(仅官方行;自定义路由用 `wrappedProviders`) |
257
+ | `stealth` | `false` | 接管官方 `deepseek-official` 路由(仅官方行;自定义路由默认由自动包装处理) |
214
258
  | `textProvider` | `deepseek-official` / `deepseek-v4-pro` | 负责思考的模型(你的日常模型) |
215
259
  | `tool` / `progressiveTools` / `autoActivateOnImage` | `true` ×3 | 视觉工具开关 / 渐进式挂载 / 图片轮自动挂载 |
216
260
  | `rewriteImages` | `true` | 模型输入层改写图片块(缓存描述或工具提示标记);界面日志保留图片 |
@@ -222,7 +266,7 @@ Web 配置页在 **设置 → 插件 → 插件配置** 下注册「视觉路由
222
266
 
223
267
  ## 环境要求
224
268
 
225
- - DeepSeek Harness 的 Web profile,且 `dsh plugin` 可用 `pnpm`。
269
+ - DeepSeek Harness 的 Web profile。普通安装可用 `npx @deepseek-ai/dsh ...`;从源码仓库运行时用 `pnpm dsh ...`。只有 CLI 已经进入系统 `PATH` 时才能直接写 `dsh ...`。
226
270
  - Node ≥ 22(宿主侧)。
227
271
  - 默认免费链路无需 API Key;付费 `httpProviders` 只需一个凭据引用(`apiKeyEnv`)。
228
272
  - `vision_html_screenshot` 才需要 Chrome / Chromium / Edge;其余工具无浏览器也能用。
@@ -232,12 +276,21 @@ Web 配置页在 **设置 → 插件 → 插件配置** 下注册「视觉路由
232
276
 
233
277
  ### 安装
234
278
 
279
+ 普通 npm / npx 安装:
280
+
281
+ ```sh
282
+ npx @deepseek-ai/dsh plugin --profile web add dsh-vision-router
283
+ npx @deepseek-ai/dsh --profile web --dump-config | grep vision-router
284
+ ```
285
+
286
+ 从 DeepSeek Harness 源码仓库运行:
287
+
235
288
  ```sh
236
- dsh plugin --profile web add dsh-vision-router
237
- dsh --profile web --dump-config | grep vision-router # 一行,由 bundle 补丁挂载
289
+ pnpm dsh plugin --profile web add dsh-vision-router
290
+ pnpm dsh --profile web --dump-config | grep vision-router
238
291
  ```
239
292
 
240
- 长期运行的 Web profile 需重启。宿主在启动时通过 `dsh.client` 声明发现浏览器端包。
293
+ 首次把插件装进已经长期运行的 Web profile 时,需要让 Web 进程重新加载插件本体;宿主在启动时通过 `dsh.client` 声明发现浏览器端包。**插件加载完成后,模型目录与包装范围的变化会热更新,不需要为这些变化重启。**
241
294
 
242
295
  ### 禁用 / 恢复
243
296
 
@@ -251,19 +304,77 @@ dsh --profile web --dump-config | grep vision-router # 一行,由 bundle 补
251
304
  ### 升级
252
305
 
253
306
  ```sh
254
- dsh plugin --profile web update dsh-vision-router
307
+ # 普通 npm / npx 安装
308
+ npx @deepseek-ai/dsh plugin --profile web update dsh-vision-router
309
+
310
+ # DeepSeek Harness 源码仓库
311
+ pnpm dsh plugin --profile web update dsh-vision-router
255
312
  ```
256
313
 
257
314
  设置存放在 profile 的设置提供方里,升级不丢失。
258
315
 
316
+ > **从 bundle 补丁之前(v0.x)升级:** 现在插件由自带的 bundle 补丁自动挂载,
317
+ > 若 `~/.dsh/profiles/<profile>/cordis.patch.yml` 里还残留旧版手动行,会与之
318
+ > 重复,`dsh web` 启动即报 `duplicate loader entry id: vision-router`。删除
319
+ > 旧块:
320
+ >
321
+ > ```yaml
322
+ > - insert: # 删除整块
323
+ > - id: vision-router
324
+ > name: dsh-vision-router
325
+ > ```
326
+ >
327
+ > 若要保留自定义配置,改为不带 insert 的按 id 覆盖行:
328
+ >
329
+ > ```yaml
330
+ > - id: vision-router
331
+ > config:
332
+ > # 你的配置…
333
+ > ```
334
+
259
335
  ### 卸载
260
336
 
261
337
  ```sh
262
- dsh plugin --profile web remove dsh-vision-router
338
+ # 普通 npm / npx 安装
339
+ npx @deepseek-ai/dsh plugin --profile web remove dsh-vision-router
340
+
341
+ # DeepSeek Harness 源码仓库
342
+ pnpm dsh plugin --profile web remove dsh-vision-router
263
343
  ```
264
344
 
265
345
  同时移除依赖与 bundle 层。若你曾手动禁用官方 DeepSeek 行,记得在 profile 补丁里恢复。
266
346
 
347
+ ## 故障排查
348
+
349
+ ### 启动报错 `Unexpected token ... is not valid JSON`(UTF-8 BOM)
350
+
351
+ **现象**:`dsh web` / `pnpm dsh web` 启动时直接退出:
352
+
353
+ ```
354
+ SyntaxError: Unexpected token ...
355
+ is not valid JSON
356
+ at JSON.parse (<anonymous>)
357
+ at readProfileManifest (packages/boot/app-boot/src/profile.ts)
358
+ ```
359
+
360
+ **原因**:`~/.dsh/profiles/<profile>/package.json` 被某些编辑器保存成了 **UTF-8 with BOM**。文件最前面多了一个不可见的 `\uFEFF` 字符,dsh 读取 manifest 时直接 `JSON.parse`,而 JSON 不允许在开头出现这个字符,于是解析失败。
361
+
362
+ **推荐修复**:直接运行 Vision Router 自带的独立修复命令。它不需要 DSH 先成功启动,会定位 profile、检测 UTF-8 BOM,只删除开头的三个 BOM 字节,然后重新验证 JSON:
363
+
364
+ ```sh
365
+ npx dsh-vision-router repair --profile web
366
+ ```
367
+
368
+ 只想检查、不修改文件时:
369
+
370
+ ```sh
371
+ npx dsh-vision-router doctor --profile web
372
+ ```
373
+
374
+ 如果你使用的不是 `web` profile,把 `web` 换成对应名称;也可以不传 `--profile`,让 doctor 扫描全部 profile。
375
+
376
+ 手动兜底方式:VS Code 右下角编码 → “通过编码保存” → 选择 `UTF-8`(无 BOM)。若 `repair` 去掉 BOM 后仍提示 JSON 非法,它不会猜测或重写其他 JSON 内容,请再手动检查文件。
377
+
267
378
  ## 安全说明
268
379
 
269
380
  - 图片中的文字是**不可信证据**:描述、OCR 输出与自动挂载提示都要求 Agent 绝不执行图片内出现的指令。