dsh-vision-router 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +9 -2
  2. package/README.zh.md +7 -2
  3. package/docs/architecture/3.0-r11-adversarial-review.md +11 -5
  4. package/docs/architecture/ci-risk-posture.md +45 -0
  5. package/docs/architecture/dsh-021-alpha1-adaptation.md +46 -0
  6. package/docs/architecture/dsh-support-window.md +8 -0
  7. package/docs/releases/v3.0.1.md +77 -0
  8. package/docs/releases/v3.0.2.md +105 -0
  9. package/index.js +15 -3
  10. package/lib/adapter-ownership.js +51 -0
  11. package/lib/client-presentation-boundary-main.js +113 -50
  12. package/lib/client-presentation-boundary.js +36 -46
  13. package/lib/client.js +2 -1
  14. package/lib/core-primitives.js +29 -1
  15. package/lib/core-vision-surface.js +3 -1
  16. package/lib/desktop-screenshot-tool.js +11 -9
  17. package/lib/diagnostic-redaction.js +61 -12
  18. package/lib/doctor-cli-p0.js +36 -5
  19. package/lib/dsh-support-window.js +4 -1
  20. package/lib/file-logger.js +9 -9
  21. package/lib/image-offload-compat.js +10 -3
  22. package/lib/local-vision-stabilizer.js +10 -7
  23. package/lib/native-image-coexistence.js +11 -1
  24. package/lib/proxy-routing.js +18 -3
  25. package/lib/remote-settings-risk-client-boundary.js +6 -1
  26. package/lib/runtime-composition.js +8 -6
  27. package/lib/session-vision-mode-authority.js +2 -7
  28. package/lib/session-vision-policy.js +9 -4
  29. package/lib/settings-client-017-compat.js +7 -3
  30. package/lib/settings-number-contract.js +3 -1
  31. package/lib/vision-background-benchmark.js +14 -2
  32. package/lib/vision-background-stop-store-core.js +14 -2
  33. package/lib/vision-diagnostics-routes.js +9 -1
  34. package/lib/vision-model-visibility-boundary-main.js +15 -16
  35. package/lib/vision-resilience.js +31 -2
  36. package/lib/vision-tool-runtime-boundary.js +2 -7
  37. package/package.json +2 -2
package/README.md CHANGED
@@ -42,9 +42,16 @@
42
42
  > **Data leaves your machine by default.** When a cloud vision model is used, Vision Router sends the image (or a derived crop), the vision prompt and related request metadata to that provider. A fresh install has an anonymous OVHcloud vision fallback enabled, so "free" and "no key" do **not** mean offline. Do not use the cloud chain for confidential, regulated or classified material. See [Data flow and strict local-only use](#data-flow-and-strict-local-only-use).
43
43
 
44
44
  > [!WARNING]
45
- > 📌 **Announcement (v3.0.0)**
45
+ > 📌 **Announcement (v3.0.2)**
46
46
  >
47
- > **v3.0.0: Session Vision policy now has a durable owner.** The supported DSH 0.1.x window now starts at `0.1.5-rc.1`, while the 0.2.x train starts at verified `0.2.0-rc.2`; Vision authority lives in a scoped, Host-storage-backed policy store, hydrates before child sessions publish, and binds delegation to the Host Agent lifecycle. The package root is a deliberate, typed public API, the runtime installation is grouped into explicit composition phases with an explicitly passed provider transport, and a new required peer (`@deepseek-ai/dsh-storage-domain`) is added. This release also closes the R11 adversarial final audit. [What’s new →](docs/releases/v3.0.0.md)
47
+ > **v3.0.2: hardening from a full-repository adversarial review.** Instant local describe now
48
+ > follows the same `proxy` / `proxyHosts` decision as every other provider leg; credential
49
+ > redaction is one shared shape list for the logs and the failure diagnostics; adapter ownership
50
+ > no longer accepts a primitive marker; proxy host admission normalizes IPv6 spellings and makes
51
+ > `*.host` entries work; the background stop cache can neither go stale nor vanish; and failure
52
+ > classification, numeric settings and the retained-image walker are hardened against inherited
53
+ > keys, hostile accessors and pathological nesting. Supported Hosts are unchanged: DSH 0.1.x from
54
+ > `0.1.5-rc.1`, DSH 0.2.x from verified `0.2.0-rc.2`. [What’s new →](docs/releases/v3.0.2.md)
48
55
 
49
56
  <p align="center">
50
57
  <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" />
package/README.zh.md CHANGED
@@ -42,9 +42,14 @@
42
42
  > **默认配置会让数据出网。** 使用云端视觉模型时,Vision Router 会将图片(或由它裁剪出的局部)、识图提示词及相关请求元数据发送给对应供应商。新安装默认开启 OVHcloud 匿名视觉兜底,因此“免费”和“免 Key”不等于离线。涉密、涉案、内部或其他受限材料请勿使用云端链路。详见[数据流向与严格纯本地配置](#数据流向与严格纯本地配置)。
43
43
 
44
44
  > [!WARNING]
45
- > 📌 **公告(v3.0.0)**
45
+ > 📌 **公告(v3.0.2)**
46
46
  >
47
- > **v3.0.0:Session Vision policy 有了持久归属。DSH 0.1.x 正式支持线提高到 `0.1.5-rc.1` 起,0.2.x 从已验证的 `0.2.0-rc.2` 起;识图权威由作用域化的、Host storage 背书的策略存储持有,子会话发布前完成 hydrate,委派绑定 Host Agent 生命周期;包根改为显式声明且带类型的公共 API,运行时装配显式分阶段、provider transport 必须显式传入,并新增必需 peer `@deepseek-ai/dsh-storage-domain`。本版本同时完成 R11 对抗终审。** [查看完整更新 →](docs/releases/v3.0.0.md)
47
+ > **v3.0.2:一轮全仓对抗审查带来的加固。** instant 本地描述现在与其他 provider 腿遵守同一套
48
+ > `proxy` / `proxyHosts` 判定;日志与失败诊断的凭据脱敏合并为一份共享清单;适配器归属不再接受
49
+ > 基本类型标记;代理主机准入归一了 IPv6 写法并让 `*.host` 条目真正生效;后台停记录缓存既不残留
50
+ > 也不会消失;失败分类、数值设置与 retained-image 遍历也分别对继承键、恶意取值器和畸形嵌套做了
51
+ > 加固。支持的 Host 不变:DSH 0.1.x 自 `0.1.5-rc.1` 起,DSH 0.2.x 自已验证的 `0.2.0-rc.2` 起。
52
+ > [查看完整更新 →](docs/releases/v3.0.2.md)
48
53
 
49
54
  <p align="center">
50
55
  <img src="assets/vision-demo.gif" width="640" alt="演示:粘贴图片,Agent 用 vision_ground / vision_crop / vision_pixel_diff 定位发送按钮并给出坐标" />
@@ -425,11 +425,17 @@ keeps a guarding test here** except the two noted under residual risk.
425
425
  mechanism in turn (both edits fail the test, restoring passes). It is wired into
426
426
  `pnpm test`, so a regression now fails in the ordinary battery instead of only in
427
427
  a real-Host desktop gate.
428
- 2. ~~conditional proxy bypass in the provider probes~~ — **closed in this round**:
429
- the two probes now run inside the configured-pair proxy scope and take the
430
- transport the proxy boundary currently owns (with a module-load capture as the
431
- fallback), so a configured `proxy` reaches them exactly as it reaches vision
432
- turns. The first attempt resolved `globalThis.fetch` at call time and was
428
+ 2. ~~conditional proxy bypass in the provider probes~~ — **closed in this round,
429
+ and completed afterwards**: the probes take the Router-owned provider transport
430
+ (`runtime.providerTransport` handed to the diagnostics routes), which owns the
431
+ `proxy` / `proxyHosts` decision, so a configured `proxy` reaches them exactly as
432
+ it reaches vision turns — for every provider shape. The first shape of this fix
433
+ ran the probes inside the legacy configured-pair scope, which only covers
434
+ non-router-owned providers; a `vision-http` direct backend (the plugin's default)
435
+ therefore still probed with ambient fetch (#633). The legacy boundary fetch is
436
+ kept as the fallback for Hosts that only configure the legacy pair scope, and the
437
+ same transport now also reaches the LM Studio **native** transport leg, which had
438
+ dropped it (#634). The first attempt resolved `globalThis.fetch` at call time and was
433
439
  **rejected by the project's own `L1-sealed-process-global` rule** — the same
434
440
  class as findings 1 and #519 — so the fix instead has the boundary own and
435
441
  advertise its wrapper (`currentLegacyGlobalProxyFetch()`), keeping every
@@ -0,0 +1,45 @@
1
+ # CI risk posture
2
+
3
+ This file records security-posture decisions that are deliberate rather than pending, so a
4
+ later reader does not re-litigate them or mistake them for oversights.
5
+
6
+ ## Resolved: Scorecard `TokenPermissionsID` on workflows that publish or open pull requests
7
+
8
+ Three alerts in this class were open on 2026-10-04 and are now `fixed` (2026-10-04T14:17:06Z):
9
+
10
+ | Alert | Location | Grant then | Outcome |
11
+ | --- | --- | --- | --- |
12
+ | `#127` | `.github/workflows/dsh-alpha-pin-refresh.yml` | workflow-level `contents: write` + `pull-requests: write` | fixed by moving both grants onto the job that pushes the pin branch and opens its pull request |
13
+ | `#125` | `.github/workflows/release.yml` | job-level `contents: write` (materialize the release tag) | fixed on re-analysis |
14
+ | `#126` | `.github/workflows/release.yml` | job-level `contents: write` (publish the GitHub Release) | fixed on re-analysis |
15
+
16
+ Scorecard's rule asks for top-level permissions to be `read-all` or `contents: read`, and
17
+ job-level least-privilege grants are accepted. Every workflow here now has that shape:
18
+ `release.yml` declares top-level `permissions: {}`, the alpha gate and the pin refresh declare
19
+ `contents: read`, and each write grant lives on the single job that needs it.
20
+
21
+ A previous revision of this file claimed the opposite — that the rule reports the grants
22
+ themselves, so moving one between levels could not clear the alert. That claim was wrong. It
23
+ generalised from the presence of alerts on `release.yml`'s job-level grants instead of measuring
24
+ the level change itself; moving the pin refresh's grants to job level cleared its alert, and the
25
+ two `release.yml` alerts cleared on re-analysis. The measurement is recorded above so the next
26
+ reader does not repeat the inference.
27
+
28
+ What remains true regardless of the score:
29
+
30
+ - the pin refresh never checks out or executes upstream code: it reads npm and GitHub metadata
31
+ and rewrites one line, so the write token is never exposed to third-party code;
32
+ - it has no access to secrets beyond the default token, and its pull request is opened with
33
+ `GITHUB_TOKEN`, which by design starts no workflow runs;
34
+ - the gate that *does* execute upstream code keeps top-level `contents: read`, pins the upstream
35
+ commit literally, and neither writes nor restores any shared cache;
36
+ - the platform-side control (a protected environment with required reviewers on the publishing
37
+ jobs) is available but deliberately not adopted: it would break unattended pin discovery, and
38
+ every write path here is already a reviewed pull request or a dispatched release.
39
+
40
+ ## How to revisit
41
+
42
+ If the write tokens ever need to shrink, the order is: attach the publishing jobs to a protected
43
+ environment, then drop `pull-requests: write` from the pin refresh by opening the bump as an
44
+ issue instead of a pull request. Both trade automation for score, which is why they are not the
45
+ default.
@@ -0,0 +1,46 @@
1
+ # DSH 0.2.1-alpha.1 compatibility audit
2
+
3
+ ## Release identity and channel
4
+
5
+ - Upstream tag: `dsh-v0.2.1-alpha.1`
6
+ - Upstream commit: `5badb15009ae1756c3afe0ae0cef1faafc290ccc`
7
+ - npm publication: `2026-10-03T04:53:22.343Z`
8
+ - GitHub release: `2026-10-03T06:42:00Z`
9
+ - Channel: `alpha` only. At publication time both `latest` and `next` remain
10
+ `0.2.0-rc.2`.
11
+
12
+ This release is inside the already peer-admitted
13
+ `>=0.2.0-rc.2 <0.3.0-0` train. It does not move the exact supported evidence
14
+ boundary away from `0.2.0-rc.2`, and it does not raise the public DSH 0.1.x
15
+ minimum.
16
+
17
+ ## Compatibility review from 0.2.0-rc.2
18
+
19
+ | Surface | Upstream change | DVR conclusion |
20
+ | --- | --- | --- |
21
+ | Host / Desktop | Desktop defaults to a system-assigned port. Boot/profile resolution and plugin package metadata were reworked. | Keep capability-based startup and authenticated Desktop tests. No version branch is needed. |
22
+ | Session | Session list generation yields between work slices and honors cancellation. Core Session event signatures and the durable v4 admission surface remain compatible. | The existing post-commit `session/event(session, event)` observer and v4 admission contracts stay valid. |
23
+ | Settings | No breaking Settings controller API change. Client Settings and Agent preset composition changed, and a Host may expose a different reachable Settings seam. | DVR already derives the seam from the installed Host and verifies mount/save/reload/readback in a real browser. |
24
+ | Bundle / HMR | Web adds Schedule rows and `publicUrl`; HMR now reloads package entry/dependency maps and removes stale mappings. | Re-run real bundle recomposition and plugin lifecycle tests; do not pin private HMR helper names. |
25
+ | Web / client | Draft preservation, plugin creation, inspector/devtools, tool preparation, layout, and document frontmatter rendering changed. | DVR's client injection remains capability/slot based. Real Chromium cold Vision, mixed attachment, and Settings tests cover the integration points. |
26
+ | Model / LLM | The LLM base adapter exposes the synchronous `imageRequestPricing()` default used by the Host. | DVR's adapter registration boundary already supplies the inherited undefined default to duck-typed adapters. |
27
+ | Attachment | Published attachment API shapes remain compatible. The alpha line retains the extended image and normalization limits. | DVR's root repair reconciles stale whole-row Cordis configs to the full Host policy and exercises the Host-owned Sharp pipeline. |
28
+ | Service injection | Public `modelCatalog`, credential update, connection rejection, WebServer route, and Cordis lifecycle seams remain present. | Structured injection and authenticated Desktop RPC stay under exact-source and real-Host tests. |
29
+
30
+ ## Validation gate
31
+
32
+ `.github/workflows/dsh-alpha-settings-host-gate.yml` pins the immutable commit
33
+ above and runs:
34
+
35
+ - exact-source and Host-owned Sharp contracts on Ubuntu, macOS, and Windows;
36
+ - public 0.2.x peer admission and Host proxy ownership without changing the
37
+ minimum support window;
38
+ - real macOS Desktop renderer lifecycle;
39
+ - Windows Desktop authentication on Node 22 and 24, plus Node 24 renderer and
40
+ multi-plugin ordering adversaries;
41
+ - real DSH Web + Chromium cold Vision, mixed attachments, Settings
42
+ mount/save/reload/readback, and bundle recomposition.
43
+
44
+ The floating npm alpha canaries remain surveillance. Advancement of the
45
+ ordinary development pin or exact supported evidence boundary is a separate,
46
+ reviewed decision. This audit does not authorize a DVR package release.
@@ -81,6 +81,14 @@ Version labels describe support policy and CI evidence; runtime branching still
81
81
 
82
82
  DVR must not turn these tables into widespread version-string conditionals. Runtime compatibility continues to feature-detect the concrete Host seam it needs. If a capability cannot be proven safely, the compatibility path fails open or reports an explicit unsupported/unknown state according to that seam's contract.
83
83
 
84
+ The same rule binds the contract gate. `scripts/dsh-host-contract-smoke.mjs` derives the settings
85
+ seam from the Host it is pointed at (`EXPECT_SETTINGS_MODE=auto`): the `0.1.5` train exposes
86
+ `SettingsProvider.register()`, while the `0.1.7` release and every `0.2.x` Host since — `0.2.0-rc.2`
87
+ through the `0.2.1` alpha line — replace it with `SettingsForms.describe()` plus ConfigEditor
88
+ `configuration()`/`edit()`. A gate that pins one shape reports an unsupported Host for a Host DVR
89
+ supports, so the floating canaries derive the seam, the pinned legs keep naming theirs explicitly,
90
+ and a Host with neither reviewed seam fails the gate by name instead of passing silently.
91
+
84
92
  ## Compatibility-retirement rule
85
93
 
86
94
  The 2.3.x `0.1.5` train floor makes any compatibility path used only by pre-`0.1.5` Hosts eligible for a fresh deletion audit, but does not automatically authorize deletion. Durable session formats, replay envelopes, adapter wire shapes, and other historical inputs may outlive the Host version that originally produced them.
@@ -0,0 +1,77 @@
1
+ # v3.0.1
2
+
3
+ DSH Vision Router 3.0.1 is a corrective release on the 3.0 line. It ships the five
4
+ consumer-visible fixes that were merged after v3.0.0 was tagged: two in the Desktop
5
+ settings surface and three in the provider egress paths, where a configured `proxy` /
6
+ `proxyHosts` must reach every leg that talks to a provider. There are no new peers, no
7
+ settings migration, no Session format change and no public API change.
8
+
9
+ ## Highlights
10
+
11
+ - **Desktop settings values (#619).** A Host that exposes a legacy-shaped
12
+ `settingsScope` can return an empty `vision-router` namespace while its native config
13
+ forms keep working. That scope is now kept only as the delegate for every other
14
+ namespace instead of bypassing the local settings bridge, so the Vision Router
15
+ settings surface reads and writes real values on the Desktop shell.
16
+ - **Model picker catalog (#620).** The model-visibility boundary now patches every
17
+ loader instance it can reach — including one published after the boundary installs —
18
+ so the picker no longer groups the same model twice.
19
+ - **Background and local provider legs (#623).** The Router-owned provider transport is
20
+ threaded into the local vision stabilizer, the host/security boundaries and the
21
+ background benchmark, so those legs honor `proxy` / `proxyHosts` exactly like
22
+ foreground vision turns.
23
+ - **Connection probe egress (#633).** The settings "test connection" probe now takes the
24
+ Router-owned transport (`runtime.providerTransport`) instead of relying on the legacy
25
+ configured-pair scope, which by construction cannot cover a `vision-http` direct
26
+ backend — the plugin's default shape. The legacy boundary fetch stays as the fallback
27
+ for Hosts that only configure the legacy scope.
28
+ - **LM Studio native transport (#634).** `callLocalBackend`'s `format: 'lmstudio'`
29
+ branch no longer drops `providerTransport`, and the native `POST /api/v1/chat` goes
30
+ through `transport.fetch`, matching the openai and anthropic legs. A remote LM Studio
31
+ behind a proxy is reached like every other provider.
32
+ - **Internal hardening (no package behavior change).** Exact Node-runtime and Playwright
33
+ system-package caches, Desktop adversary sharding and scenario concurrency, a Host
34
+ contract gate that derives the settings seam from the Host it meets, an alpha canary
35
+ that runs every contract instead of stopping at the first failure, and completed e2e
36
+ workflow trigger coverage.
37
+
38
+ ## Host support window
39
+
40
+ Unchanged from 3.0.0:
41
+
42
+ - DSH 0.1.x: `>=0.1.5-rc.1 <0.2.0-0`
43
+ - DSH 0.2.x: `>=0.2.0-rc.2 <0.3.0-0`
44
+ - ordinary development Host: exact `0.2.0-rc.2`
45
+
46
+ The 0.2.x train is admitted from the verified `0.2.0-rc.2` boundary. The `0.2.1` alpha
47
+ line is inside that admitted range and is exercised by the scheduled drift canaries,
48
+ which read the settings seam from the Host they install; the exact-evidence boundary is
49
+ still `0.2.0-rc.2`.
50
+
51
+ ## Validation
52
+
53
+ - every fix carries a failing-first reproduction that fails on the pre-fix commit and
54
+ passes on this release commit — the Desktop settings and picker boundaries, the local
55
+ and background transport legs, the `vision-http` probe, and the LM Studio native
56
+ transport;
57
+ - the full gate battery on the exact release commit: install, build, `build:check`,
58
+ source-ownership lint, source→artifact evidence, reproducible clean build, typecheck,
59
+ the test suite, client-source synchronization, the Host support-window contract, and
60
+ the packed public-API contract;
61
+ - a real-Host pass on this exact build against an installed DSH checkout: the Host proxy
62
+ egress contract, the settings lifecycle in a real browser (mount, save, reload and
63
+ readback of the Vision Router section) and the Desktop Host injection contract
64
+ (structured injections, token exchange, authenticated index and DVR RPC);
65
+ - the real-Host matrix (Windows Node 22/24 Desktop, macOS renderer, Linux real Host +
66
+ Chromium), the adversarial compatibility/fuzz gates and the resource-stress gates;
67
+ - an adversarial review of this delta, including a provider-egress sweep over every
68
+ server-side entry point. The one candidate that looked like a further bypass — Ollama
69
+ cold start — is gated to loopback hosts and was recorded as falsified rather than
70
+ patched.
71
+
72
+ ## Upgrade
73
+
74
+ Drop-in replacement for 3.0.0: no settings migration, no Session format change and no
75
+ new peer dependency. Upgrading `dsh plugin --profile web add github:ysr666/dsh-vision-router`
76
+ (or the pinned `dsh-vision-router@3.0.1`) keeps existing Vision Router settings and
77
+ Session data readable as before.
@@ -0,0 +1,105 @@
1
+ # v3.0.2
2
+
3
+ DSH Vision Router 3.0.2 is a hardening release on the 3.0 line. It ships the fixes from a
4
+ full-repository adversarial review plus the follow-up pass over that work, the composer Vision
5
+ control's responsive-design fix (issue #654) and the presented-image delivery fix, together with
6
+ the one consumer-visible transport fix merged after v3.0.1 was tagged. There are no new peers, no
7
+ settings migration, no Session format change and no public API change.
8
+
9
+ ## Highlights
10
+
11
+ - **The composer Vision chip follows the shipped chip and adapts to narrow rows (#654).** It used
12
+ to draw its own 1px border, 12px type and a brand-coloured outline/fill while selected, and it
13
+ kept its label at every width, so on a narrow composer it took more room and read heavier than
14
+ its shipped neighbours. Its size, radius, hover, focus ring and selected state now come from the
15
+ shipped chip tokens (the same ones `ui-plan`'s PlanChip and the access-mode trigger use) and it
16
+ follows the composer row's size container: below the same 460px cut that chip uses, only the
17
+ 14px glyph remains. Measured in one window: 72px → 30px, label hidden, glyph kept.
18
+ - **The collapsed chip keeps the Vision eye; the check appears only while the label is there.** A
19
+ bare check says nothing about what the chip does, and on a narrow row it is the only thing left
20
+ on screen. The 14px slot now carries both glyphs and the same container query that collapses the
21
+ label swaps them: the enabled chip shows the check at `@container (min-width: 460.01px)`, and the
22
+ eye stays below that cut. Both states use the same fixed 14px slot, so the chip width never
23
+ changes with the state. The guide copy no longer teaches the check as the on-signal.
24
+ - **A presented image lands in the message body, not only in a tool card.** The card shows a 240px
25
+ thumbnail and every later message pushes it up (measured on a real host: the same image drifted
26
+ from inside the viewport to 1692px above it within minutes). `vision_present` now also returns a
27
+ paste-ready `replyImage` that the visible reply carries, and the host renders an absolute image
28
+ path inside a message at full size through its own `api/file` route (measured in the DOM at
29
+ 640×200 for the same image the card showed as a thumbnail). The tool card stays, and it now
30
+ centres itself when it mounts below the viewport — never when it is already visible, and never
31
+ for history above the reader.
32
+ - **Instant local describe keeps the Router transport (#647, issue #646).** The instant local
33
+ describe path now hands the Router-owned `providerTransport` to its adapter call, so a configured
34
+ `proxy` / `proxyHosts` covers it exactly like every other provider leg.
35
+ - **Credential redaction is one shared list (#653, #655).** The log sink and the benchmark-failure
36
+ diagnostic used to carry partial copies of the credential shapes, and each leaked what the other
37
+ covered. One shared list now covers `Authorization` with any scheme (including
38
+ `Proxy-Authorization`), `Cookie` / `Set-Cookie`, URL userinfo, `Bearer`, vendor keys (`sk-`,
39
+ `ghp_` / `github_pat_`, `AKIA` / `ASIA`, `xox…`, `AIza…`), JWTs, PEM blocks and `key=value`
40
+ assignments (including `#token=` fragments). A value that contains `;` is redacted whole, and a
41
+ PEM block truncated before its END marker is redacted to the end of the text.
42
+ - **Adapter ownership no longer accepts a primitive marker (#653).** Three modules used to read the
43
+ shared ownership symbol on their own, and two of them treated "the marker is not undefined" as
44
+ ownership. Ownership now means "an object marker is present", so a foreign adapter that writes
45
+ `'anything'`, `false`, `null` or `0` onto the symbol is no longer mistaken for the Router's own
46
+ adapter; delegation, Session Vision authority and the tool boundary all read the same rule.
47
+ - **Proxy host admission normalizes what it compares (#653, #655).** A bracketed IPv6 literal and
48
+ its bare spelling canonicalize to one host, and a `*.host` entry now matches the host it names
49
+ plus its subdomains instead of silently matching nothing.
50
+ - **The background stop cache can neither go stale nor vanish (#653, #655).** A load that drops a
51
+ record (expired, malformed or superseded) rewrites the file instead of leaving a stale row
52
+ behind, and a failed cleanup write no longer discards the records that were read.
53
+ - **Failure classification and numeric settings resolve own keys only (#653).** Error codes and
54
+ settings field names no longer resolve inherited values such as `constructor` or `__proto__`, and
55
+ a hostile `message` / `toString` / `code` / `status` accessor can no longer make the classifier
56
+ throw.
57
+ - **The retained-image walker is depth-bounded (#653).** It now shares the bound `convertBlocks`
58
+ already applies to the same block tree, so a pathological message tree returns `false` instead of
59
+ overflowing the stack.
60
+ - **Same-class hardening from the same review (#653).** Session Vision policy rows with
61
+ prototype-carried fields are treated as malformed; an explicit `null` options bag means "no
62
+ options" at four entry points instead of throwing from a parameter destructure; host capability
63
+ evidence is tri-state, so only an explicit `true` proves the support floor; the doctor host probe
64
+ is bounded even when the fetch ignores `AbortSignal`, and the error text it stores is redacted.
65
+
66
+ ## Host support window
67
+
68
+ Unchanged from 3.0.1:
69
+
70
+ - DSH 0.1.x: `>=0.1.5-rc.1 <0.2.0-0`
71
+ - DSH 0.2.x: `>=0.2.0-rc.2 <0.3.0-0`
72
+ - ordinary development Host: exact `0.2.0-rc.2`
73
+
74
+ The 0.2.1 alpha line stays inside the admitted 0.2.x range and is now covered by a dedicated
75
+ adaptation note (`docs/architecture/dsh-021-alpha1-adaptation.md`) in addition to the scheduled
76
+ drift canaries.
77
+
78
+ ## Validation
79
+
80
+ - every finding carries a failing-first reproduction that fails on the pre-fix commit and passes on
81
+ this release commit, and the four residual items found by the follow-up pass were reproduced
82
+ against the merged code before they were fixed;
83
+ - the full gate battery on the exact release commit: install, build, `build:check`, source-ownership
84
+ lint, source→artifact evidence, reproducible clean build, typecheck, the test suite, client-source
85
+ synchronization, the Host support-window contract and the packed public-API contract;
86
+ - a real-Host pass on this exact build against an installed DSH checkout: the Host proxy egress
87
+ contract, the settings lifecycle in a real browser (mount, save, reload and readback), the Desktop
88
+ Host injection contract (structured injections, token exchange, authenticated index and DVR RPC)
89
+ and the macOS Desktop renderer lifecycle with a functional vision turn;
90
+ - counter-probes independent of the test suite: the eight review points go from 7/8 to 8/8 and the
91
+ pre/post differential from 2/4 to 4/4, with the host-admission differential showing no widening
92
+ for non-wildcard lists;
93
+ - the real-Host matrix on the release pull request (Windows Node 22/24 Desktop, macOS renderer,
94
+ Linux real Host + Chromium), the adversarial compatibility/fuzz gates and the resource-stress
95
+ gates;
96
+ - a write-scoped CI pass: the accepted risk posture is recorded in
97
+ `docs/architecture/ci-risk-posture.md` and each workflow grant is narrowed to the job that needs
98
+ it.
99
+
100
+ ## Upgrade
101
+
102
+ Drop-in replacement for 3.0.1: no settings migration, no Session format change and no new peer
103
+ dependency. Upgrading `dsh plugin --profile web add github:ysr666/dsh-vision-router` (or the pinned
104
+ `dsh-vision-router@3.0.2`) keeps existing Vision Router settings and Session data readable as
105
+ before.
package/index.js CHANGED
@@ -2442,6 +2442,7 @@ export function apply(ctx, config = {}, runtime = {}) {
2442
2442
  memory: sessionImageMemory,
2443
2443
  timeoutMs: timeoutMs(),
2444
2444
  downscaleMaxPixels: instantLocalMaxPixels(),
2445
+ providerTransport,
2445
2446
  })
2446
2447
  if (instantMap.size > 0) {
2447
2448
  ctx.logger?.info(
@@ -3859,7 +3860,10 @@ ctx.logger?.info(
3859
3860
  'Present a generated local image directly to the user with the host-native image preview. ' +
3860
3861
  'MANDATORY PRESENTATION RULE: when you generate, edit, screenshot, or export an image and want the user to see it, ' +
3861
3862
  'you MUST call vision_present. read_image is only for model-side inspection; NEVER use read_image to present or send ' +
3862
- 'an image to the user. The image is retained in the session UI but sanitized out of later text-only model requests.',
3863
+ 'an image to the user. ALWAYS paste the returned replyImage into your visible reply too: the host renders an ' +
3864
+ 'absolute image path in a message as a full-size image where the reader is, while the tool card only shows a ' +
3865
+ 'thumbnail that later messages push away. The image is retained in the session UI but sanitized out of later ' +
3866
+ 'text-only model requests.',
3863
3867
  parameters: {
3864
3868
  type: 'object',
3865
3869
  properties: {
@@ -4770,9 +4774,15 @@ ctx.logger?.info(
4770
4774
  '2. 还原验证循环(本插件招牌流程):参考图 → 实现 → `vision_html_screenshot` 截图 → `vision_pixel_diff` 度量差异 → 修复 → 再截图,迭代到差异收敛(0% 是常见终点);长页面用 `fullPage: true` 一次截整页并拿到 `pageHeight`;\n' +
4771
4775
  '3. 其余按需取用:配色用 `vision_colors`,文字用 `vision_ocr`,图标矢量化用 `vision_trace`,纯色背景抠图用 `vision_extract_foreground`,本地 HTML 截图用 `vision_html_screenshot`(长页面加 `fullPage: true` 截整页);\n' +
4772
4776
  '4. 展示规则(必须遵守):当你生成、编辑、截图或导出一张图片,并希望用户看到它时,必须调用 `vision_present`。' +
4773
- '`read_image` 仅用于你自己读取或检查图片内容,绝不能把 `read_image` 当成向用户展示或发送图片的方法。\n' +
4777
+ '`read_image` 仅用于你自己读取或检查图片内容,绝不能把 `read_image` 当成向用户展示或发送图片的方法。' +
4778
+ '调用后必须把返回的 `replyImage` 原样放进你的可见回复:宿主会把消息里的绝对图片路径渲染成完整尺寸的图,' +
4779
+ '而工具卡片只显示缩略图、且会被后续消息顶出视野。\n' +
4780
+
4774
4781
  ' MANDATORY PRESENTATION RULE: when you generate, edit, screenshot, or export an image and want the user to see it, ' +
4775
- 'you MUST call `vision_present`. `read_image` is only for your own model-side inspection; NEVER use `read_image` to present or send an image to the user.\n' +
4782
+ 'you MUST call `vision_present`. `read_image` is only for your own model-side inspection; NEVER use `read_image` to present or send an image to the user. ' +
4783
+ 'Also paste the returned `replyImage` into your visible reply: the host renders an absolute image path in a ' +
4784
+ 'message at full size, while the tool card only shows a thumbnail that later messages push out of view.\n' +
4785
+
4776
4786
  '5. 所有坐标都是原图像素(x1/y1/x2/y2);上传的图片可以直接用其附件 ID(如 `sha256:…`)作为各工具的 image 参数,无需先找磁盘路径。' +
4777
4787
  'All coordinates are original pixels (x1/y1/x2/y2); uploaded images can be referenced directly by their attachment id (e.g. `sha256:…`) as the image argument. 产物写入工作区 `' +
4778
4788
  `${artifactsRel}` +
@@ -4849,6 +4859,8 @@ ctx.logger?.info(
4849
4859
  // only the three coherent domain faces it already computes; route lifecycle,
4850
4860
  // bounded /models probing and JSON response semantics live outside Core.
4851
4861
  installVisionDiagnosticsRoutes(ctx, {
4862
+ // Same egress as real vision turns: the Router-owned provider transport.
4863
+ transport: runtime?.providerTransport,
4852
4864
  connection: {
4853
4865
  candidatePairs: pairs,
4854
4866
  localBackends: () => localProvidersOf(current()),
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Single owner of the Vision Router adapter ownership marker.
3
+ *
4
+ * Three modules used to interpret the same process-wide symbol on their own, and
5
+ * two of them treated "the marker is not undefined" as ownership — so a foreign
6
+ * adapter that wrote `'anything'`, `false`, `null` or `0` onto the shared symbol
7
+ * was classified as Vision Router's own adapter. Ownership now means "a marker
8
+ * *object* is present"; the per-function notes below record what that does and
9
+ * does not prove.
10
+ *
11
+ * The symbol stays `Symbol.for(...)` so separately loaded copies of this package
12
+ * still recognize each other's adapters; the rule about its *value* lives here
13
+ * once.
14
+ */
15
+ export const VISION_ROUTER_ADAPTER_OWNER = Symbol.for('dsh-vision-router.adapter-owner');
16
+ /** Read the marker without letting a hostile proxy/getter escape. */
17
+ export function readVisionRouterAdapterOwner(adapter) {
18
+ if (adapter === null || (typeof adapter !== 'object' && typeof adapter !== 'function')) {
19
+ return undefined;
20
+ }
21
+ try {
22
+ return adapter[VISION_ROUTER_ADAPTER_OWNER];
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
28
+ /**
29
+ * True when the adapter carries an owner marker object.
30
+ *
31
+ * The marker's value has always been an object — the registration chain mints
32
+ * `Object.freeze({})`, and composition/tests mark adapters with plain records
33
+ * such as `{ route: 'deepseek-vision' }`. Requiring "an object" therefore keeps
34
+ * every genuine owner (including ones marked by another loaded copy) and rejects
35
+ * the values a foreign adapter can write by accident or in one line: `'anything'`,
36
+ * `false`, `null`, `0` and any other primitive.
37
+ *
38
+ * A first version of this fix also demanded `Object.isFrozen(owner)`. The
39
+ * repository's own issue-276/289/374/512 suites mark adapters with plain objects,
40
+ * so that extra requirement classified genuine owners as foreign and broke
41
+ * delegation; it was reverted here instead of being worked around in the tests.
42
+ *
43
+ * Residual risk, stated rather than hidden: in-process code able to run arbitrary
44
+ * JavaScript can still assign `{}` and be treated as an owner. Removing that needs
45
+ * one shared authority instead of a process-wide symbol, which is a larger design
46
+ * change than this fix.
47
+ */
48
+ export function isVisionRouterOwnedAdapter(adapter) {
49
+ const owner = readVisionRouterAdapterOwner(adapter);
50
+ return typeof owner === 'object' && owner !== null;
51
+ }