dsh-vision-router 1.6.2 → 1.7.1
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 +7 -6
- package/README.zh.md +8 -7
- package/assets/sponsor/alipay.png +0 -0
- package/assets/sponsor/wechat.png +0 -0
- package/docs/doctor.md +27 -1
- package/docs/remote-settings.md +9 -0
- package/docs/update-check.md +1 -1
- package/entry.js +108 -9
- package/index.js +164 -38
- package/lib/adapter-update-coalescer.js +51 -6
- package/lib/adversarial-hardening.js +211 -52
- package/lib/android-attachment-compat.js +99 -17
- package/lib/artifact-boundary.js +145 -0
- package/lib/catalog-corrections.js +16 -2
- package/lib/client.js +387 -45
- package/lib/depth-guidance.js +54 -8
- package/lib/doctor-cli.js +46 -2
- package/lib/file-logger.js +100 -10
- package/lib/http-body-limit.js +94 -0
- package/lib/http-compat.js +57 -6
- package/lib/image-resource-governor.js +17 -3
- package/lib/legacy-session-repair.js +384 -0
- package/lib/live-model-client-prelude.js +588 -0
- package/lib/live-model-discovery.js +596 -0
- package/lib/local-remote-settings-permission.js +463 -0
- package/lib/local-vision-stabilizer.js +3 -1
- package/lib/ollama-cold-start.js +497 -0
- package/lib/remote-settings-bridge.js +218 -0
- package/lib/remote-settings-risk-confirmation.js +195 -0
- package/lib/replay-delegation.js +14 -4
- package/lib/replay-envelope-v2-compat.js +102 -0
- package/lib/runtime-config-normalizer.js +54 -0
- package/lib/screenshot-source-boundary.js +124 -0
- package/lib/structured-flow-hardening.js +308 -97
- package/lib/tesseract-exec-compat.js +246 -91
- package/lib/trusted-vision-hints.js +71 -0
- package/lib/turn-budget-context.js +24 -0
- package/lib/update-check.js +11 -2
- package/lib/vision-execution-policy.js +531 -0
- package/lib/vision-model-registry.js +274 -0
- package/lib/vision-resilience.js +98 -45
- package/lib/web-capability-boundary.js +249 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
|
-
<a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v1.
|
|
20
|
-
<a href="tests"><img src="https://img.shields.io/badge/verified-
|
|
19
|
+
<a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v1.7.1"><img src="https://img.shields.io/badge/release-v1.7.1-5B4CF0?style=flat-square" alt="Release v1.7.1" /></a>
|
|
20
|
+
<a href="tests"><img src="https://img.shields.io/badge/verified-657%20tests-2EA44F?style=flat-square" alt="Verified: 657 tests" /></a>
|
|
21
21
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat-square" alt="License: MIT" /></a>
|
|
22
22
|
<a href="package.json"><img src="https://img.shields.io/badge/Node.js-%3E%3D22-339933?style=flat-square&logo=nodedotjs&logoColor=white" alt="Node.js >=22" /></a>
|
|
23
23
|
<img src="https://img.shields.io/badge/runtime-no%20Python-8A2BE2?style=flat-square" alt="No Python" />
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
<p align="center">💬 <strong>QQ community group: 1105463028</strong></p>
|
|
30
30
|
|
|
31
31
|
> [!WARNING]
|
|
32
|
-
> 📌 **Announcement (v1.
|
|
32
|
+
> 📌 **Announcement (v1.7.1)**
|
|
33
33
|
>
|
|
34
|
-
> **v1.
|
|
34
|
+
> **v1.7.1:** Remote settings can now be enabled after an explicit risk confirmation.
|
|
35
35
|
|
|
36
36
|
<p align="center">
|
|
37
37
|
<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" />
|
|
@@ -265,7 +265,7 @@ vision_long_screenshot_ocr image="chat-log.png" chunkHeight=1200 overlap=120
|
|
|
265
265
|
|
|
266
266
|
The vision tools try backends in order and surface an error only after all of them fail:
|
|
267
267
|
|
|
268
|
-
1. **User vision models**: one per settings row, top to bottom;
|
|
268
|
+
1. **User vision models**: one per settings row, top to bottom; active providers remain visible even when model enumeration is partial, callable generative models stay selectable, image metadata is advisory, and actual support is verified at runtime;
|
|
269
269
|
2. **Local Ollama (optional, off by default)**: `localOllama.enabled` adds keyless, offline recognition through your local Ollama (for example qwen2.5vl);
|
|
270
270
|
3. **Local LM Studio (optional, off by default)**: `localLmStudio.enabled` follows Ollama and requires the real model identifier shown in LM Studio Developer or returned by `/v1/models`;
|
|
271
271
|
4. **Advanced custom HTTP vision endpoints**: legacy/advanced `httpProviders`, when present, run after the local backends;
|
|
@@ -345,7 +345,7 @@ Everything is optional; defaults work out of the box. Edit via the Web card or a
|
|
|
345
345
|
| `rewriteImages` | `true` | rewrite image blocks in the model input (cached description or tool-hint marker); the UI log keeps images |
|
|
346
346
|
| `desktopScreenshot` | `false` | privacy opt-in for the model-callable `vision_screenshot` desktop-capture tool; checked live before every capture |
|
|
347
347
|
| `freeFallback` | `true` | append the anonymous OVH models after explicit local/custom HTTP backends; turning this off never disables an explicitly configured local backend |
|
|
348
|
-
| `localOllama` | `{ enabled: false, baseURL: 'http://127.0.0.1:11434/v1', model: 'qwen2.5vl', format: 'openai' }` | **Local vision backend (merged from dsh-vision)**: when enabled, `local-ollama` leads the HTTP vision chain; skipped automatically when Ollama is down; `format` selects `openai` (`/chat/completions`) or `anthropic` (`/messages`); optional `temperature` / `top_p` are sent only when explicitly set
|
|
348
|
+
| `localOllama` | `{ enabled: false, baseURL: 'http://127.0.0.1:11434/v1', model: 'qwen2.5vl', format: 'openai' }` | **Local vision backend (merged from dsh-vision)**: when enabled, `local-ollama` leads the HTTP vision chain; skipped automatically when Ollama is down; `format` selects `openai` (`/chat/completions`) or `anthropic` (`/messages`); optional `temperature` / `top_p` are sent only when explicitly set. v1.7 prewarms loopback models and renews a 30-minute residency so cold loading is not charged to the normal inference deadline |
|
|
349
349
|
| `localLmStudio` | `{ enabled: false, baseURL: 'http://localhost:1234/v1', model: '', format: 'openai' }` | **Local LM Studio backend (merged from dsh-vision)**: follows Ollama and precedes custom/cloud HTTP backends; enabling it requires the real model identifier shown in LM Studio Developer or returned by `/v1/models`; supports the same optional sampling fields, while `format: 'anthropic'` requires LM Studio 0.4.1+ |
|
|
350
350
|
| `instantDescribe` | `false` | **Instant local translation (merged from dsh-vision)**: when on and at least one local backend is usable, uncached image blocks are recognized before the first model step; Ollama is tried before LM Studio with a shared timeout budget, multi-image batches run concurrently (up to 3), and failures fall back to the static tool-hint marker |
|
|
351
351
|
| `localDescribeStyle` | `plain` | **Local recognition output style (merged from dsh-vision)**: `plain` = flat description; `structured` = structured recognition (【初步判断】/【细节】/【空间结构】/【原图尺寸】), better for screenshot analysis |
|
|
@@ -393,6 +393,7 @@ ollama pull qwen2.5vl
|
|
|
393
393
|
**3. What happens**
|
|
394
394
|
|
|
395
395
|
- When enabled, `local-ollama` heads the HTTP vision chain. For a strict local-only setup, remove cloud vision rows/custom HTTP endpoints and turn off `freeFallback`.
|
|
396
|
+
- **v1.7 cold-start handling:** the selected loopback Ollama model is prewarmed through Ollama's native API and kept resident for 30 minutes. If it is cold when Ollama is the primary image backend, loading completes before the normal vision-task budget starts; a short `/api/ps` probe keeps a dead service on the fast fallback path. Remote Ollama URLs are never auto-warmed.
|
|
396
397
|
- **LM Studio works the same way** — enable `localLmStudio` in the same "Local vision" group with its OpenAI-compatible endpoint (default `http://localhost:1234/v1`) and enter the exact model identifier shown in Developer or `/v1/models`. It sits after `local-ollama` and before custom/cloud HTTP backends.
|
|
397
398
|
- Each local backend can speak **OpenAI or Anthropic format** via `format` (default `openai`). Anthropic mode routes to `/v1/messages` with `anthropic-version` and base64 image sources; `x-api-key` is sent only when a key is configured. LM Studio needs version 0.4.1 or newer for this endpoint.
|
|
398
399
|
- If a local backend is down or the call times out, its entry is skipped automatically and the chain falls through to the cloud backends — no call breaks.
|
package/README.zh.md
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
|
-
<a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v1.
|
|
20
|
-
<a href="tests"><img src="https://img.shields.io/badge/verified-
|
|
19
|
+
<a href="https://github.com/ysr666/dsh-vision-router/releases/tag/v1.7.1"><img src="https://img.shields.io/badge/release-v1.7.1-5B4CF0?style=flat-square" alt="Release v1.7.1" /></a>
|
|
20
|
+
<a href="tests"><img src="https://img.shields.io/badge/verified-657%20tests-2EA44F?style=flat-square" alt="Verified: 657 tests" /></a>
|
|
21
21
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat-square" alt="License: MIT" /></a>
|
|
22
22
|
<a href="package.json"><img src="https://img.shields.io/badge/Node.js-%3E%3D22-339933?style=flat-square&logo=nodedotjs&logoColor=white" alt="Node.js >=22" /></a>
|
|
23
23
|
<img src="https://img.shields.io/badge/runtime-no%20Python-8A2BE2?style=flat-square" alt="No Python" />
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
<p align="center">💬 <strong>QQ 用户交流群:1105463028</strong></p>
|
|
30
30
|
|
|
31
31
|
> [!WARNING]
|
|
32
|
-
> 📌 **公告(v1.
|
|
32
|
+
> 📌 **公告(v1.7.1)**
|
|
33
33
|
>
|
|
34
|
-
> **v1.
|
|
34
|
+
> **v1.7.1:远程设置现可在风险确认后开启。**
|
|
35
35
|
|
|
36
36
|
<p align="center">
|
|
37
37
|
<img src="assets/vision-demo.gif" width="640" alt="演示:粘贴图片,Agent 用 vision_ground / vision_crop / vision_pixel_diff 定位发送按钮并给出坐标" />
|
|
@@ -178,7 +178,7 @@ opencode-go + 自动识图 ← 发图片时选这个
|
|
|
178
178
|
| Intern AI(上海AI实验室) | `internvl-latest` · `internvl3.5-latest` | 30 RPM,**9000 万 token/月** | ✅ | chat.intern-ai.org.cn |
|
|
179
179
|
| Groq | `meta-llama/llama-4-scout-17b-16e-instruct`(原生多模态,最多 5 张图) | 30 RPM / 14,400 次/天,免卡 | ❌ 需代理 | console.groq.com |
|
|
180
180
|
| Google AI Studio | `gemini-2.5-flash` · `gemini-2.5-flash-lite` | 10–30 RPM / 500–1,500 次/天 | ❌ 需代理 | aistudio.google.com |
|
|
181
|
-
| NVIDIA NIM | `meta/llama-3.2-11b-vision-instruct` · `nvidia/nemotron-nano-12b-v2-vl` | 40 RPM,免卡 | ⚠️ | build.nvidia.com |
|
|
181
|
+
| NVIDIA NIM | `meta-llama/llama-3.2-11b-vision-instruct` · `nvidia/nemotron-nano-12b-v2-vl` | 40 RPM,免卡 | ⚠️ | build.nvidia.com |
|
|
182
182
|
| OpenCode Zen | `mimo-v2.5-free`(视觉 + 代码) | 30 RPM / 500 次/天 | ⚠️ | opencode.ai/zen |
|
|
183
183
|
| OpenRouter | `google/gemma-4-26b-a4b-it:free` · `google/gemma-4-31b-it:free` | 未充值账户 50 次/天 | ❌ 需代理 | openrouter.ai |
|
|
184
184
|
|
|
@@ -264,7 +264,7 @@ vision_long_screenshot_ocr image="chat-log.png" chunkHeight=1200 overlap=120
|
|
|
264
264
|
|
|
265
265
|
视觉工具按顺序逐个尝试,全部失败才报错:
|
|
266
266
|
|
|
267
|
-
1.
|
|
267
|
+
1. **用户视觉模型**:设置卡里一行一个,从上到下;已启用供应商即使模型枚举部分失败也会保留在下拉中,可调用的生成式模型继续可选,图片能力声明只作提示,最终以运行时实际调用为准;
|
|
268
268
|
2. **本地 Ollama(可选,默认关)**:`localOllama.enabled` 开启后,通过本机 Ollama 做免 Key、离线识别(例如 qwen2.5vl);
|
|
269
269
|
3. **本地 LM Studio(可选,默认关)**:`localLmStudio.enabled` 排在 Ollama 之后,模型名必须填写 LM Studio Developer 页或 `/v1/models` 返回的真实标识;
|
|
270
270
|
4. **高级自定义 HTTP 视觉端点**:旧配置/高级配置中的 `httpProviders` 排在本地后端之后;
|
|
@@ -344,7 +344,7 @@ Web 配置页在 **设置 → 插件 → 插件配置** 下注册「视觉路由
|
|
|
344
344
|
| `rewriteImages` | `true` | 模型输入层改写图片块(缓存描述或工具提示标记);界面日志保留图片 |
|
|
345
345
|
| `desktopScreenshot` | `false` | 模型可调用的 `vision_screenshot` 桌面截屏隐私开关;每次截屏前实时检查 |
|
|
346
346
|
| `freeFallback` | `true` | 在显式本地/自定义 HTTP 后端之后追加匿名 OVH 模型;关闭它不会停用用户明确配置的本地后端 |
|
|
347
|
-
| `localOllama` | `{ enabled: false, baseURL: 'http://127.0.0.1:11434/v1', model: 'qwen2.5vl', format: 'openai' }` | **本地视觉后端(并入自 dsh-vision)**:开启后 local-ollama 排在 HTTP 视觉链最前;Ollama 未运行会自动跳过;`format` 可选 `openai`(`/chat/completions`)或 `anthropic`(`/messages`);可选的 `temperature` / `top_p`
|
|
347
|
+
| `localOllama` | `{ enabled: false, baseURL: 'http://127.0.0.1:11434/v1', model: 'qwen2.5vl', format: 'openai' }` | **本地视觉后端(并入自 dsh-vision)**:开启后 local-ollama 排在 HTTP 视觉链最前;Ollama 未运行会自动跳过;`format` 可选 `openai`(`/chat/completions`)或 `anthropic`(`/messages`);可选的 `temperature` / `top_p` 只在显式填写时发送。v1.7 会预热本机 loopback 模型并续期 30 分钟驻留,冷加载时间不再计入正常识图截止时间 |
|
|
348
348
|
| `localLmStudio` | `{ enabled: false, baseURL: 'http://localhost:1234/v1', model: '', format: 'openai' }` | **本地 LM Studio 后端(并入自 dsh-vision)**:排在 Ollama 之后、自定义/云 HTTP 后端之前;开启时必须填写 LM Studio Developer 页或 `/v1/models` 返回的真实模型标识;可选采样参数同 Ollama,`format: 'anthropic'` 需 LM Studio 0.4.1+ |
|
|
349
349
|
| `instantDescribe` | `false` | **即时本地翻译(并入自 dsh-vision)**:开启且至少一个本地后端可用时,在第一模型步之前识别无缓存图片块;Ollama → LM Studio 共用总超时预算,多图并发上限 3,失败则回退静态工具标记 |
|
|
350
350
|
| `localDescribeStyle` | `plain` | **本地识别输出风格(并入自 dsh-vision)**:`plain` = 平铺描述;`structured` = 结构化识别(【初步判断】/【细节】/【空间结构】/【原图尺寸】),截图分析质量更高 |
|
|
@@ -388,6 +388,7 @@ ollama pull qwen2.5vl
|
|
|
388
388
|
**3. 行为说明**
|
|
389
389
|
|
|
390
390
|
- 开启后 `local-ollama` 排在 HTTP 视觉链最前。若要严格纯本地,请移除云视觉行/自定义 HTTP 端点,并关闭 `freeFallback`。
|
|
391
|
+
- **v1.7 冷启动处理:**选中的本机 loopback Ollama 模型会通过原生 API 预热并保持 30 分钟驻留。如果模型在 Ollama 作为首个图片后端时已经冷却,加载会在正常视觉任务预算开始之前完成;短 `/api/ps` 探测保证服务未运行/挂死时仍快速进入 fallback。远程 Ollama URL 不会自动预热。
|
|
391
392
|
- **LM Studio 同理**——同一「本地视觉」组里开启 `localLmStudio`,填 OpenAI 兼容端点(默认 `http://localhost:1234/v1`),并使用 Developer 页或 `/v1/models` 返回的真实模型标识。它排在 `local-ollama` 之后、自定义/云 HTTP 后端之前。
|
|
392
393
|
- 每个本地后端可通过 `format` 选择 **OpenAI 或 Anthropic 格式**(默认 `openai`)。Anthropic 模式走 `/v1/messages`,带 `anthropic-version` 并把图片转为 base64 source;只有配置了 Key 才发送 `x-api-key`。LM Studio 需 0.4.1 或更高版本才提供该端点。
|
|
393
394
|
- 任一本地后端未运行或调用超时时自动跳过,继续降级到云链——任何调用都不受影响。
|
|
Binary file
|
|
Binary file
|
package/docs/doctor.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vision Router doctor / repair
|
|
2
2
|
|
|
3
|
-
Vision Router ships a small standalone diagnostic CLI. It does not need DSH to boot first, so it can still run when DSH exits while parsing a broken profile manifest.
|
|
3
|
+
Vision Router ships a small standalone diagnostic CLI. It does not need DSH to boot first, so it can still run when DSH exits while parsing a broken profile manifest or when an older Vision Router build left one conversation unable to cold-resume.
|
|
4
4
|
|
|
5
5
|
## Normal installation stays unchanged
|
|
6
6
|
|
|
@@ -68,3 +68,29 @@ npx dsh-vision-router repair --profile web
|
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
to rewrite them to bare names (`dsh-vision-router`, `@deepseek-ai/*`), which exempt every future version, so upgrades take effect immediately again. Unrelated entries and the rest of the file are left untouched.
|
|
71
|
+
|
|
72
|
+
## Repair a conversation that only breaks after restarting DSH
|
|
73
|
+
|
|
74
|
+
A very early Vision Router build briefly persisted the automatic vision-tool mount reminder as a `user/message` without a message `id`. The conversation could keep working in the live process, but after DSH restarted the stricter cold-resume validator could reject that stored event with an error containing:
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
lacks an identified message
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Current Vision Router builds no longer create that malformed event. To recover an already-affected conversation, **stop DSH first**, then run:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
npx dsh-vision-router repair-sessions
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The repair is intentionally narrow and fail-closed:
|
|
87
|
+
|
|
88
|
+
- it scans `$DSH_HOME/sessions` for the exact historical Vision Router auto-mount reminder signature only;
|
|
89
|
+
- unrelated malformed messages are not changed;
|
|
90
|
+
- both raw `session.jsonl` and DSH's default checksummed `session.jsonl.zstd` format are supported;
|
|
91
|
+
- unchanged Zstandard frames stay byte-for-byte identical;
|
|
92
|
+
- torn/incomplete logs are refused so DSH can perform its own crash recovery first;
|
|
93
|
+
- the source file identity is checked again immediately before replacement, so a live writer causes the operation to abort instead of racing;
|
|
94
|
+
- every changed log receives a byte-for-byte backup next to the original before replacement, and the repaired log is re-read and verified before success is reported.
|
|
95
|
+
|
|
96
|
+
After the command reports a repaired session, restart DSH and reopen the conversation. Running `repair-sessions` again is idempotent: already-repaired logs are left untouched.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Remote Vision Router settings
|
|
2
|
+
|
|
3
|
+
Vision Router uses a dedicated DSH Connection RPC channel at `/vision-router-settings/*` because the global DSH settings/credentials plane is intentionally loopback-only.
|
|
4
|
+
|
|
5
|
+
Remote editing is off by default. When a Vision Router settings page is opened through a DSH trusted host while the permission is still disabled, the page shows an explicit risk confirmation. Cancelling leaves remote editing disabled and writes nothing. Confirming enables only `allowRemoteSettings`, then reloads the scoped Vision Router settings immediately. The same permission can still be enabled or revoked from the loopback settings page.
|
|
6
|
+
|
|
7
|
+
DSH `trustedHosts` protects the browser carrier against Host/Origin/DNS-rebinding attacks; it is **not user authentication**. Enable remote editing only when you trust the clients that can reach the DSH instance. Ordinary remote mutations remain restricted to the explicit Vision Router allow-list. Credential-bearing HTTP providers, local Ollama/LM Studio configuration, artifact paths and every other field not present in that allow-list stay unavailable remotely. `allowRemoteSettings` itself is not added to the ordinary mutation allow-list: it can be enabled only through the explicit risk-confirmation flow (or from the loopback settings page).
|
|
8
|
+
|
|
9
|
+
If DSH is behind Nginx, Caddy or another reverse proxy, forward `/vision-router-settings/*` to the same DSH origin in addition to the normal DSH routes. The UI detects a 404 on this channel and shows this requirement directly.
|
package/docs/update-check.md
CHANGED
|
@@ -23,7 +23,7 @@ dsh plugin --profile <current-profile> add dsh-vision-router@<latest>
|
|
|
23
23
|
|
|
24
24
|
The registry-confirmed target version is installed explicitly rather than through a bare `update`. This matters because pnpm 11 applies `minimumReleaseAge` (default 1440 minutes): a plain `pnpm update` silently keeps the current version while every candidate release is younger than 24h and still exits 0 ("Already up to date") — a false-success update that leaves the old version running after restart. `add <name>@<version>` installs the confirmed release (pnpm auto-exempts the requested version from the policy when needed). For non-registry installs (git/file/link/workspace specs) the updater keeps `update` semantics and only verifies the result.
|
|
25
25
|
|
|
26
|
-
The subprocess uses `execFile` with `shell: false`; no shell command is constructed from browser input. The update endpoint also requires a process-local token
|
|
26
|
+
The subprocess uses `execFile` with `shell: false`; no shell command is constructed from browser input. The update endpoint also requires a process-local bearer token. That token is issued only to the local DSH UI (loopback transport plus a localhost/loopback Host); a remotely exposed settings page can still perform the read-only version check, but its response does not contain the mutation token and therefore cannot trigger the local updater. The POST update route independently enforces the same local-UI boundary before validating the token. After the updater exits, the plugin reads the `dsh-vision-router` manifest under the profile's `node_modules` and verifies that the installed version actually reached the target — **a zero exit code alone is never reported as success**. Only a verified update asks the user to restart DSH so the new plugin bundle is loaded.
|
|
27
27
|
|
|
28
28
|
If the CLI cannot be verified — for example a raw TypeScript source entry that needs a workspace-specific loader — the one-click button is not offered. This commonly applies to a DSH source checkout launched with `pnpm dsh`: version checking still works, but updating remains under the source workspace's own pnpm workflow. The card keeps the version information and release-notes link and tells the user to update through their original DSH installation path instead.
|
|
29
29
|
|
package/entry.js
CHANGED
|
@@ -10,12 +10,21 @@ import z from '@deepseek-ai/schemastery'
|
|
|
10
10
|
import * as core from './index.js'
|
|
11
11
|
import { installVisionRouterFileLogging } from './lib/file-logger.js'
|
|
12
12
|
import { contextWithDelegatedReplay } from './lib/replay-delegation.js'
|
|
13
|
+
import { contextWithReplayEnvelopeV2Compat } from './lib/replay-envelope-v2-compat.js'
|
|
14
|
+
import { contextWithVisionExecutionPolicy } from './lib/vision-execution-policy.js'
|
|
15
|
+
import { installLiveModelDiscovery } from './lib/live-model-discovery.js'
|
|
16
|
+
import { installVisionModelRegistry } from './lib/vision-model-registry.js'
|
|
17
|
+
import { installLiveModelClientPrelude } from './lib/live-model-client-prelude.js'
|
|
13
18
|
import { installAdversarialHardening } from './lib/adversarial-hardening.js'
|
|
19
|
+
import { installOllamaColdStartGuard } from './lib/ollama-cold-start.js'
|
|
14
20
|
import { installLocalVisionStabilizer } from './lib/local-vision-stabilizer.js'
|
|
15
21
|
import { installWrapperDirectoryAlias } from './lib/wrapper-directory.js'
|
|
16
22
|
import { installAndroidAttachmentCompat } from './lib/android-attachment-compat.js'
|
|
17
23
|
import { contextWithCoalescedAdapterUpdates } from './lib/adapter-update-coalescer.js'
|
|
18
24
|
import { installTesseractExecFileCompat } from './lib/tesseract-exec-compat.js'
|
|
25
|
+
import { installLocalMutationRouteBoundary } from './lib/web-capability-boundary.js'
|
|
26
|
+
import { installScreenshotSourceBoundary } from './lib/screenshot-source-boundary.js'
|
|
27
|
+
import { installVisionRouterRemoteSettingsBridge } from './lib/remote-settings-bridge.js'
|
|
19
28
|
import {
|
|
20
29
|
installStructuredFlowHardening,
|
|
21
30
|
normalizeGuidanceOverrides,
|
|
@@ -27,16 +36,46 @@ import {
|
|
|
27
36
|
protectRc7ProviderOwnership,
|
|
28
37
|
} from './lib/dsh-contract-compat.js'
|
|
29
38
|
|
|
39
|
+
// Increment whenever the browser-visible settings contract gains a field whose
|
|
40
|
+
// absence changes write semantics. This revision is also exposed as a resolved
|
|
41
|
+
// schema default so a newer browser client can distinguish a genuinely updated
|
|
42
|
+
// Host from a stale in-process plugin module instead of reporting a generic
|
|
43
|
+
// readback mismatch after the old Host rejects a newly visible field.
|
|
44
|
+
export const SETTINGS_CONTRACT_REVISION = 4
|
|
45
|
+
|
|
30
46
|
// Schemastery object schemas expose set() as the supported way to replace a
|
|
31
47
|
// field schema. This mutates the Config object that index.js itself later uses
|
|
32
48
|
// for the settings namespace, so composition config and settings validation
|
|
33
49
|
// agree on the same default.
|
|
34
50
|
core.Config.set('progressiveTools', z.boolean().default(false))
|
|
35
|
-
// Structured 1+x
|
|
51
|
+
// Structured 1+x also has a turn-level wall-clock budget. Individual
|
|
36
52
|
// visionTaskTimeoutMs budgets remain unchanged; this one prevents a deep turn
|
|
37
53
|
// from multiplying them into several minutes of serial waiting.
|
|
38
54
|
core.Config.set('visionTurnBudgetMs', z.number().step(1000).min(10000).max(600000).default(90000))
|
|
39
55
|
|
|
56
|
+
// Both visible entry points — Settings > Vision Router and the legacy
|
|
57
|
+
// Settings > Plugins compatibility card — edit the same Host-owned namespace.
|
|
58
|
+
// Keep the depth enum and custom cap on this final public contract so either
|
|
59
|
+
// entry serializes exactly the same shape and rc.7 Host persistence accepts it.
|
|
60
|
+
core.Config.set('visionDepth', z.union(['fast', 'standard', 'deep', 'custom']).default('standard'))
|
|
61
|
+
core.Config.set('visionDepthMaxCalls', z.number().step(1).min(0).max(100).default(0))
|
|
62
|
+
|
|
63
|
+
// Settings surfaces and Host persistence must agree on this field. Keep the
|
|
64
|
+
// permission on the public entry contract as well as index.js so a packaged
|
|
65
|
+
// build cannot expose the new client toggle while registering an older Host
|
|
66
|
+
// schema that silently recovers from the rejected settings.mutate call. This
|
|
67
|
+
// deliberately repeats the default: entry.js is the final schema authority
|
|
68
|
+
// loaded by Cordis and therefore the right place to close client/Host drift.
|
|
69
|
+
core.Config.set('allowRemoteSettings', z.boolean().default(false))
|
|
70
|
+
|
|
71
|
+
// Internal read-only-by-convention handshake. It is not rendered by Vision
|
|
72
|
+
// Router's settings UI and is not in the remote mutable allow-list; its resolved
|
|
73
|
+
// default lets diagnostics prove which schema the running Host actually loaded.
|
|
74
|
+
core.Config.set(
|
|
75
|
+
'settingsContractRevision',
|
|
76
|
+
z.number().step(1).min(1).max(SETTINGS_CONTRACT_REVISION).default(SETTINGS_CONTRACT_REVISION),
|
|
77
|
+
)
|
|
78
|
+
|
|
40
79
|
export * from './index.js'
|
|
41
80
|
export {
|
|
42
81
|
attachmentContextForContract,
|
|
@@ -53,30 +92,52 @@ export const Config = core.Config
|
|
|
53
92
|
// preserve replay identity, while vision-tool network calls are constrained to
|
|
54
93
|
// the exact backends the user selected in Vision Router.
|
|
55
94
|
export function apply(ctx, config = {}) {
|
|
56
|
-
|
|
57
|
-
|
|
95
|
+
// DSH's browser WebServer can intentionally bind 0.0.0.0 and carries no
|
|
96
|
+
// authentication layer of its own. Same-origin headers are only a CSRF
|
|
97
|
+
// signal, so install a transport-level loopback fence before ANY plugin-owned
|
|
98
|
+
// route is registered. The wrapper patches only webServer.register and hands
|
|
99
|
+
// every injection callback the original child context identity unchanged.
|
|
100
|
+
const localMutationCtx = installLocalMutationRouteBoundary(ctx)
|
|
101
|
+
const logging = installVisionRouterFileLogging(localMutationCtx)
|
|
102
|
+
const delegatedReplayCtx = contextWithDelegatedReplay(logging.ctx, {
|
|
58
103
|
wrapperRoute:
|
|
59
104
|
typeof config.wrapperRoute === 'string' && config.wrapperRoute !== ''
|
|
60
105
|
? config.wrapperRoute
|
|
61
106
|
: 'deepseek-vision',
|
|
62
107
|
visionConfig: config,
|
|
63
108
|
})
|
|
109
|
+
// DSH rc.7 pi-ai replay v2 stores the real producer under
|
|
110
|
+
// replayState.response.{provider,model}; the older delegated-replay shim
|
|
111
|
+
// recognizes the pre-v2 top-level shape. Layer a narrow private compatibility
|
|
112
|
+
// view so resumed wrapper history keeps provider-native replay metadata rather
|
|
113
|
+
// than degrading to foreign history at the delegate boundary.
|
|
114
|
+
const runtimeCtx = contextWithReplayEnvelopeV2Compat(delegatedReplayCtx)
|
|
115
|
+
// Filesystem authority is separate from browser rendering safety. Put this
|
|
116
|
+
// boundary INSIDE adversarial hardening so the secure HTML renderer that the
|
|
117
|
+
// outer layer installs is itself wrapped by canonical workspace containment.
|
|
118
|
+
const screenshotSourceCtx = installScreenshotSourceBoundary(runtimeCtx, core)
|
|
64
119
|
// Security/runtime boundary shared by the core and the local-vision shim:
|
|
65
120
|
// keep artifacts inside the session workspace, make HTML screenshots truly
|
|
66
121
|
// offline + sandboxed, protect the screenshot-permission side effect, and
|
|
67
122
|
// make the process-wide fetch cleanup coexist with later plugin patches.
|
|
68
123
|
const { ctx: hardenedCtx, config: hardenedConfig } = installAdversarialHardening(
|
|
69
|
-
|
|
124
|
+
screenshotSourceCtx,
|
|
70
125
|
config,
|
|
71
126
|
core,
|
|
72
127
|
)
|
|
128
|
+
// Ollama's native API can preload a model independently of an actual vision
|
|
129
|
+
// inference. Install this boundary before the local-vision stabilizer so the
|
|
130
|
+
// final local vision-http adapter is observed after stabilization. Primary
|
|
131
|
+
// local-Ollama image turns finish a cold model load in pre-step, before the
|
|
132
|
+
// 45s vision-task budget begins; fallback Ollama warms in the background.
|
|
133
|
+
const ollamaColdStartCtx = installOllamaColdStartGuard(hardenedCtx, hardenedConfig, core)
|
|
73
134
|
// #141 stabilization boundary: keep the recently merged local-vision
|
|
74
135
|
// behavior isolated from main's existing provider/router semantics. It
|
|
75
136
|
// normalizes only the local settings/runtime seams before core.apply sees
|
|
76
137
|
// the context (desktop screenshot exposure, instant-local budget/one-pass,
|
|
77
138
|
// local vision-http transport and connection-probe fallback).
|
|
78
139
|
const { ctx: stabilizedCtx, bootConfig } = installLocalVisionStabilizer(
|
|
79
|
-
|
|
140
|
+
ollamaColdStartCtx,
|
|
80
141
|
hardenedConfig,
|
|
81
142
|
core,
|
|
82
143
|
)
|
|
@@ -90,6 +151,10 @@ export function apply(ctx, config = {}) {
|
|
|
90
151
|
: 90000,
|
|
91
152
|
}
|
|
92
153
|
const rc7 = isRc7ContractRuntime(stabilizedCtx)
|
|
154
|
+
// The remote settings bridge uses DSH Connection's trusted-host carrier
|
|
155
|
+
// fence and its own safe-field capability allow-list. Main's local Web
|
|
156
|
+
// mutation boundary continues to protect the independent /_dsh write routes.
|
|
157
|
+
installVisionRouterRemoteSettingsBridge(stabilizedCtx, logging.logger)
|
|
93
158
|
const ownershipCtx = rc7 ? protectRc7ProviderOwnership(stabilizedCtx) : stabilizedCtx
|
|
94
159
|
const settingsCtx = rc7
|
|
95
160
|
? installRc7SettingsCompatibility(ownershipCtx, { ...runtimeConfig, stealth: false }, {
|
|
@@ -106,20 +171,54 @@ export function apply(ctx, config = {}) {
|
|
|
106
171
|
})
|
|
107
172
|
// Final structured-flow guard sits closest to core.apply so it sees the
|
|
108
173
|
// actual tool registrations and pre-step listener. It makes bootstrap
|
|
109
|
-
// one-shot, enforces fast/standard/deep quotas, tracks mixed branches,
|
|
110
|
-
// rejects empty/non-evidence results, and applies one shared
|
|
174
|
+
// one-shot, enforces fast/standard/deep/custom quotas, tracks mixed branches,
|
|
175
|
+
// rejects empty/non-evidence results, and applies one shared visual deadline.
|
|
111
176
|
const structuredCtx = installStructuredFlowHardening(attachmentCompatCtx, runtimeConfig)
|
|
112
177
|
// DSH rc.7 publishes llm/adapters-updated synchronously from inside
|
|
113
178
|
// registerAdapter(). Coalesce only Vision Router's listener: nested events
|
|
114
179
|
// mark the topology dirty and the outer pass reruns to a fixed point, so we
|
|
115
180
|
// neither double-register a twin nor lose a provider added mid-pass.
|
|
116
181
|
const reconciledCtx = contextWithCoalescedAdapterUpdates(structuredCtx)
|
|
182
|
+
// Discover the provider's actual /models list independently of DSH's static
|
|
183
|
+
// catalog. The Host owns credentials/networking/cache; the browser receives
|
|
184
|
+
// model ids only. A live hit is also the evidence required before an
|
|
185
|
+
// UNKNOWN_MODEL catalog miss may enter the compatibility bridge.
|
|
186
|
+
const liveDiscovery = installLiveModelDiscovery(reconciledCtx, {
|
|
187
|
+
config: runtimeConfig,
|
|
188
|
+
logger: logging.logger,
|
|
189
|
+
})
|
|
190
|
+
// Consolidate the private picker registry without weakening execution
|
|
191
|
+
// admission. Fresh/stale endpoint models get explicit source labels, while a
|
|
192
|
+
// model already saved under an active provider stays visible as [saved] even
|
|
193
|
+
// when that provider does not enumerate every accepted id. Saved-only rows do
|
|
194
|
+
// NOT alter liveDiscovery.hasModel(), so they cannot authorize a direct
|
|
195
|
+
// UNKNOWN_MODEL bridge by themselves. The registry also exposes the evidence
|
|
196
|
+
// source strictly for diagnostics (`known` vs `live`) without changing the
|
|
197
|
+
// admission decision.
|
|
198
|
+
installVisionModelRegistry(reconciledCtx, liveDiscovery, { config: runtimeConfig })
|
|
199
|
+
// Keep endpoint-discovered ids private to Vision Router's settings client:
|
|
200
|
+
// the prelude wraps this package's browser context rather than changing the
|
|
201
|
+
// global llm.models response (which would expose UNKNOWN_MODEL entries in the
|
|
202
|
+
// ordinary chat model picker). The existing classic client bundle stays the
|
|
203
|
+
// DSH module-system artifact, including HMR/source-map behavior.
|
|
204
|
+
installLiveModelClientPrelude(reconciledCtx)
|
|
205
|
+
// Direct compatibility bridging is allowed only after DSH/pi-ai's exact
|
|
206
|
+
// pre-wire image-capability admission rejection, or a local UNKNOWN_MODEL
|
|
207
|
+
// backed by exact private-registry evidence. Record the same provenance in
|
|
208
|
+
// the persistent diagnostics log so one image turn clearly shows attempt ->
|
|
209
|
+
// adapter failure -> direct bridge -> success/fallback. Provider/network/auth
|
|
210
|
+
// failures remain authoritative and cannot be retried through a second path.
|
|
211
|
+
const executionCtx = contextWithVisionExecutionPolicy(reconciledCtx, {
|
|
212
|
+
isBridgeEvidence: (provider, model) => liveDiscovery.hasModel(provider, model),
|
|
213
|
+
evidenceSource: (provider, model) => liveDiscovery.evidenceSource?.(provider, model),
|
|
214
|
+
logger: logging.logger,
|
|
215
|
+
})
|
|
117
216
|
// index.js historically passes image bytes as `options.input` to the async
|
|
118
217
|
// execFile API. That option is not fed into child stdin, so Tesseract waits
|
|
119
218
|
// for data until the OCR slice expires. Materialize only this exact
|
|
120
219
|
// Tesseract-stdin call to a temporary image file; all other execFile calls
|
|
121
220
|
// keep their native behavior.
|
|
122
|
-
installTesseractExecFileCompat(
|
|
221
|
+
installTesseractExecFileCompat(executionCtx)
|
|
123
222
|
|
|
124
223
|
// 启动诊断摘要只描述 composition/apply 的基础配置。设置服务可能稍后
|
|
125
224
|
// 覆盖这些值;每个图片轮还会记录 current() 的实时决策,避免把这个
|
|
@@ -140,7 +239,7 @@ export function apply(ctx, config = {}) {
|
|
|
140
239
|
/* diagnostics must never break apply */
|
|
141
240
|
}
|
|
142
241
|
try {
|
|
143
|
-
const result = core.apply(
|
|
242
|
+
const result = core.apply(executionCtx, runtimeConfig)
|
|
144
243
|
// DSH rc.7's Settings -> Models surface is backed by the configurable
|
|
145
244
|
// provider directory, not by the live adapter registry alone. Publish the
|
|
146
245
|
// main DeepSeek + 自动识图 route as a derived alias of official DeepSeek so
|