dsh-vision-router 3.0.0 → 3.0.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 CHANGED
@@ -42,9 +42,15 @@
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.1)**
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.1: five corrective fixes on the 3.0 line.** The Vision Router settings surface
48
+ > reads and writes real values on the Desktop shell, the model picker no longer groups the
49
+ > same model twice, and every provider leg that can reach a remote provider — background
50
+ > profiling, the local vision stabilizer, the settings "test connection" probe and the
51
+ > LM Studio native transport — now follows the same `proxy` / `proxyHosts` decision as
52
+ > foreground vision turns. Supported Hosts are unchanged: DSH 0.1.x from `0.1.5-rc.1`,
53
+ > DSH 0.2.x from verified `0.2.0-rc.2`. [What’s new →](docs/releases/v3.0.1.md)
48
54
 
49
55
  <p align="center">
50
56
  <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,13 @@
42
42
  > **默认配置会让数据出网。** 使用云端视觉模型时,Vision Router 会将图片(或由它裁剪出的局部)、识图提示词及相关请求元数据发送给对应供应商。新安装默认开启 OVHcloud 匿名视觉兜底,因此“免费”和“免 Key”不等于离线。涉密、涉案、内部或其他受限材料请勿使用云端链路。详见[数据流向与严格纯本地配置](#数据流向与严格纯本地配置)。
43
43
 
44
44
  > [!WARNING]
45
- > 📌 **公告(v3.0.0)**
45
+ > 📌 **公告(v3.0.1)**
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.1:3.0 线上的五处修正。** Desktop 壳上的 Vision Router 设置面能读取并写入真实值,
48
+ > 模型选择器不再把同一模型重复分组;所有可能触达远程 provider 的腿 —— 后台基准分析、本地视觉
49
+ > 稳定器、设置里的「测试连接」探针、LM Studio native 传输 —— 现在都与前台视觉轮次遵守同一套
50
+ > `proxy` / `proxyHosts` 判定。支持的 Host 不变:DSH 0.1.x 自 `0.1.5-rc.1` 起,DSH 0.2.x 自
51
+ > 已验证的 `0.2.0-rc.2` 起。 [查看完整更新 →](docs/releases/v3.0.1.md)
48
52
 
49
53
  <p align="center">
50
54
  <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
@@ -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.
package/index.js CHANGED
@@ -4849,6 +4849,8 @@ ctx.logger?.info(
4849
4849
  // only the three coherent domain faces it already computes; route lifecycle,
4850
4850
  // bounded /models probing and JSON response semantics live outside Core.
4851
4851
  installVisionDiagnosticsRoutes(ctx, {
4852
+ // Same egress as real vision turns: the Router-owned provider transport.
4853
+ transport: runtime?.providerTransport,
4852
4854
  connection: {
4853
4855
  candidatePairs: pairs,
4854
4856
  localBackends: () => localProvidersOf(current()),
@@ -1937,7 +1937,11 @@ async function callLmStudioNative(provider, messages, options = {}) {
1937
1937
  ...(typeof options.temperature === 'number' ? { temperature: options.temperature } : {}),
1938
1938
  ...(typeof options.top_p === 'number' ? { top_p: options.top_p } : {}),
1939
1939
  }
1940
- const response = await fetch(`${apiRoot}/api/v1/chat`, {
1940
+ const transport = options.providerTransport
1941
+ const requestFetch = typeof transport?.fetch === 'function'
1942
+ ? (input, init) => transport.fetch(input, init, { providerName: provider.name, allowProxy: true })
1943
+ : fetch
1944
+ const response = await requestFetch(`${apiRoot}/api/v1/chat`, {
1941
1945
  method: 'POST',
1942
1946
  headers: { 'content-type': 'application/json' },
1943
1947
  body: JSON.stringify(body),
@@ -1984,6 +1988,9 @@ export async function callLocalBackend(provider, messages, options = {}) {
1984
1988
  return callLmStudioNative(provider, messages, {
1985
1989
  maxTokens,
1986
1990
  signal: options.signal,
1991
+ // The native LM Studio transport is a provider leg like the openai/anthropic
1992
+ // ones: it must use the Router-owned transport so proxy/proxyHosts apply.
1993
+ providerTransport: options.providerTransport,
1987
1994
  ...(typeof provider.reasoningEffort === 'string' ? { reasoningEffort: provider.reasoningEffort } : {}),
1988
1995
  ...sampling,
1989
1996
  })
@@ -43,7 +43,7 @@ export async function triggerDesktopScreenshotPermission({
43
43
  }
44
44
  }
45
45
 
46
- export function installLocalVisionStabilizer(ctx, config = {}, core) {
46
+ export function installLocalVisionStabilizer(ctx, config = {}, core, installOptions = {}) {
47
47
  config = normalizeRuntimeVisionConfig(config)
48
48
  if (!ctx || typeof ctx !== 'object') return { ctx, bootConfig: config }
49
49
 
@@ -270,6 +270,7 @@ export function installLocalVisionStabilizer(ctx, config = {}, core) {
270
270
  const text = await core.callLocalBackend(local, await localMessages(options), {
271
271
  maxTokens: local.maxTokens ?? 4096,
272
272
  signal: options.signal,
273
+ providerTransport: installOptions?.providerTransport,
273
274
  resolveCredential,
274
275
  })
275
276
  if (text !== '') {
@@ -98,7 +98,7 @@ function legacyCoreVisionSettings(ctx, fallback) {
98
98
  return { ...current, autoActivateOnImage: false }
99
99
  }
100
100
 
101
- function installHostAndSecurityBoundaries(ctx, config, core) {
101
+ function installHostAndSecurityBoundaries(ctx, config, core, providerTransport) {
102
102
  // SessionVisionIndex calls planGuardStopShadows before Core.apply. Keep that
103
103
  // one helper behind the live-locale facade; the final Core itself still gets
104
104
  // the ordinary composition identity and is localized by the scoped context.
@@ -144,6 +144,7 @@ function installHostAndSecurityBoundaries(ctx, config, core) {
144
144
  ollamaColdStartCtx,
145
145
  hardenedConfig,
146
146
  core,
147
+ { providerTransport },
147
148
  )
148
149
 
149
150
  const routingProduct = resolveVisionRoutingProduct(bootConfig)
@@ -260,7 +261,7 @@ function createRuntimeOwners(host, core) {
260
261
  }
261
262
  }
262
263
 
263
- function installRoutingAndHostProducts(host, owners, core) {
264
+ function installRoutingAndHostProducts(host, owners, core, providerTransport) {
264
265
  const { capabilityStore, logging, runtimeConfig, runtimePerformanceStore } = host
265
266
  const { sessionVisionModeCompat } = owners
266
267
  // Final structured-flow guard sits closest to core.apply so it sees the
@@ -281,7 +282,7 @@ function installRoutingAndHostProducts(host, owners, core) {
281
282
  runtimeConfig,
282
283
  core,
283
284
  capabilityStore,
284
- { logger: logging.logger },
285
+ { logger: logging.logger, providerTransport },
285
286
  )
286
287
  const sessionTurnResolver = createSessionTurnResolver(backgroundProfiling.ctx)
287
288
  const sessionEventTailReader = createSessionEventTailReader(backgroundProfiling.ctx)
@@ -479,9 +480,10 @@ function applyMatureCoreBridge(host, owners, routing, execution, core, runtime)
479
480
  }
480
481
 
481
482
  export function applyVisionRuntimeComposition(ctx, config = {}, core, runtime = {}) {
482
- const host = installHostAndSecurityBoundaries(ctx, config, core)
483
+ const providerTransport = runtime?.providerTransport
484
+ const host = installHostAndSecurityBoundaries(ctx, config, core, providerTransport)
483
485
  const owners = createRuntimeOwners(host, core)
484
- const routing = installRoutingAndHostProducts(host, owners, core)
485
- const execution = installExecutionBoundaries(host, owners, routing, core, runtime?.providerTransport)
486
+ const routing = installRoutingAndHostProducts(host, owners, core, providerTransport)
487
+ const execution = installExecutionBoundaries(host, owners, routing, core, providerTransport)
486
488
  return applyMatureCoreBridge(host, owners, routing, execution, core, runtime)
487
489
  }
@@ -197,13 +197,17 @@ export const SETTINGS_017_CLIENT_PRELUDE = String.raw`(function(){
197
197
  get: function(target, property) {
198
198
  if (property === 'settingsScope') {
199
199
  var original = safeGet(target, 'settingsScope');
200
- if (original && typeof original.bind === 'function') return original;
201
200
  // DSH 0.1.7 configForms only mirrors Config fields declared volatile.
202
201
  // DVR must keep its public Config ordinary for the older supported Host
203
202
  // window, so its namespace intentionally uses the local-only ConfigEditor
204
- // bridge while other namespaces may still delegate to native configForms.
203
+ // bridge. Even when a Host exposes a legacy-shaped settingsScope, its
204
+ // vision-router view may be empty; keep it only as the delegate for
205
+ // every other namespace instead of letting it bypass the local bridge.
205
206
  var official = configFormsBinder(target);
206
- return syntheticBinder(ctx, official);
207
+ return syntheticBinder(
208
+ ctx,
209
+ original && typeof original.bind === 'function' ? original : official
210
+ );
207
211
  }
208
212
  if (property === 'get') {
209
213
  var getter = Reflect.get(target, property, target);
@@ -758,7 +758,7 @@ export function createBackgroundCapabilityProfiler({
758
758
  }, interval)
759
759
  try { policyTimer?.unref?.() } catch {}
760
760
 
761
- const runner = runAxisBenchmark ?? ((args) => runAxis({ ...args, options: invokerOptions }))
761
+ const runner = runAxisBenchmark ?? runAxis
762
762
  await runner({
763
763
  ctx,
764
764
  config: current,
@@ -768,6 +768,7 @@ export function createBackgroundCapabilityProfiler({
768
768
  axis: work.axis,
769
769
  signal: controller.signal,
770
770
  logger,
771
+ options: invokerOptions,
771
772
  onProgress(progress = {}) {
772
773
  if (!runningWork || runningWork.candidate.key !== work.candidate.key || runningWork.axis !== work.axis) return
773
774
  const completed = Math.max(0, Number(progress.completed) || 0)
@@ -999,6 +1000,16 @@ export function installBackgroundCapabilityProfiling(ctx, config, core, store, o
999
1000
  const logger = options.logger ?? ctx?.logger
1000
1001
  const imageVerdictStore = options.imageVerdictStore ?? createImageInputVerdictStore({ logger })
1001
1002
  const backgroundStopStore = options.backgroundStopStore ?? createBackgroundBenchmarkStopStore({ logger })
1003
+ // Provider transport is one explicit runtime capability shared by automatic
1004
+ // profiling and the user-triggered exact check. Top-level ownership wins so
1005
+ // a stale nested test/customization bag cannot silently bypass live proxy
1006
+ // policy after composition or HMR changes.
1007
+ const invokerOptions = {
1008
+ ...(options.invokerOptions ?? {}),
1009
+ ...(options.providerTransport === undefined
1010
+ ? {}
1011
+ : { providerTransport: options.providerTransport }),
1012
+ }
1002
1013
  const profiler = createBackgroundCapabilityProfiler({
1003
1014
  ctx,
1004
1015
  config,
@@ -1006,6 +1017,7 @@ export function installBackgroundCapabilityProfiling(ctx, config, core, store, o
1006
1017
  store,
1007
1018
  logger,
1008
1019
  ...options,
1020
+ invokerOptions,
1009
1021
  imageVerdictStore,
1010
1022
  backgroundStopStore,
1011
1023
  })
@@ -1120,7 +1132,7 @@ export function installBackgroundCapabilityProfiling(ctx, config, core, store, o
1120
1132
  provider: body.provider,
1121
1133
  model: body.model,
1122
1134
  signal: AbortSignal.timeout(VISION_CHECK_TIMEOUT_MS),
1123
- invokerOptions: options.invokerOptions ?? {},
1135
+ invokerOptions,
1124
1136
  })
1125
1137
  await profiler.recordImageSupported(body.provider, body.model)
1126
1138
  sendJson(res, 200, result)
@@ -106,7 +106,15 @@ export function installVisionDiagnosticsRoutes(ctx, options = {}) {
106
106
  // inside the adapter's proxy scope) do not have. Use the fetch the boundary
107
107
  // currently owns, with the module-load capture as the fallback.
108
108
  const injectedFetch = typeof options.fetchImpl === 'function' ? options.fetchImpl : undefined
109
- const resolveFetch = () => injectedFetch ?? currentLegacyGlobalProxyFetch() ?? moduleFetch
109
+ // Probes must describe the egress real vision turns use. That egress is the
110
+ // Router-owned provider transport, which also owns the proxy/proxyHosts decision;
111
+ // the legacy pair scope below only covers non-router-owned providers, so a
112
+ // vision-http direct backend would otherwise probe with ambient fetch.
113
+ const transport = options.transport
114
+ const transportFetch = typeof transport?.fetch === 'function'
115
+ ? (input, init) => transport.fetch(input, init, { allowProxy: true })
116
+ : undefined
117
+ const resolveFetch = () => injectedFetch ?? transportFetch ?? currentLegacyGlobalProxyFetch() ?? moduleFetch
110
118
  const connectionConfig = () => {
111
119
  try {
112
120
  const live = ctx?.get?.('settings')?.get?.('vision-router')
@@ -419,6 +419,7 @@ export const VISION_MODEL_VISIBILITY_PRELUDE = String.raw`(function(){
419
419
  function create() {
420
420
  var result = originalCreate.apply(this, arguments);
421
421
  patchLoader(loader);
422
+ if (result && result !== loader) patchLoader(result);
422
423
  return result;
423
424
  }
424
425
  try { Object.defineProperty(create, '__visionRouterModelVisibility', { value: true }); } catch (_) {}
@@ -427,31 +428,29 @@ export const VISION_MODEL_VISIBILITY_PRELUDE = String.raw`(function(){
427
428
  }
428
429
 
429
430
  function install() {
430
- if (window.__ModuleLoader__) {
431
- patchLoader(window.__ModuleLoader__);
432
- return;
433
- }
434
431
  var descriptor = Object.getOwnPropertyDescriptor(window, '__ModuleLoader__');
432
+ var current;
433
+ try { current = window.__ModuleLoader__; } catch (_) { current = undefined; }
434
+ if (current) patchLoader(current);
435
435
  if (descriptor && descriptor.configurable === false) return;
436
- var stored;
436
+ var previousGet = descriptor && descriptor.get;
437
+ var previousSet = descriptor && descriptor.set;
438
+ var stored = descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value')
439
+ ? descriptor.value
440
+ : current;
437
441
  Object.defineProperty(window, '__ModuleLoader__', {
438
442
  configurable: true,
439
- enumerable: true,
440
- get: function(){ return stored; },
443
+ enumerable: !descriptor || descriptor.enumerable !== false,
444
+ get: function(){ return previousGet ? previousGet.call(window) : stored; },
441
445
  set: function(value) {
442
- stored = value;
443
- patchLoader(value);
444
- Object.defineProperty(window, '__ModuleLoader__', {
445
- configurable: true,
446
- enumerable: true,
447
- writable: true,
448
- value: stored
449
- });
446
+ if (previousSet) previousSet.call(window, value); else stored = value;
447
+ try { patchLoader(previousGet ? previousGet.call(window) : value); } catch (_) {}
450
448
  }
451
449
  });
450
+ if (stored) patchLoader(stored);
452
451
  }
453
452
 
454
- install();
453
+ try { install(); } catch (_) {}
455
454
  })();`
456
455
 
457
456
  export function injectVisionModelVisibilityBoundary(html) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-vision-router",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Eyes for text-only DeepSeek Harness agents: built-in free vision chain + pixel-level tools. Remote vision providers, including the default free fallback, receive image content unless strict local-only use is configured.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -89,7 +89,7 @@
89
89
  "@deepseek-ai/dsh-storage-domain": "0.2.0-rc.2"
90
90
  },
91
91
  "scripts": {
92
- "test": "node --test tests/index-modularization.test.js tests/adapter-prepare-call-compat.test.js tests/core.test.js tests/issue-374-delegated-call-config.test.js tests/grounding-coordinate-frame.test.js tests/grounding-coordinate-runtime.test.js tests/capability-advisory.test.js tests/vision-execution-policy.test.js tests/vision-backend-diagnostics.test.js tests/live-model-discovery.test.js tests/cache-atomic-write-concurrency.test.js tests/live-model-credential-resolution.test.js tests/vision-model-registry.test.js tests/live-model-client-stability.test.js tests/providers-persistence.test.js tests/provider-directory-fallback.test.js tests/model-catalog-policy.test.js tests/settings-ia-client-prelude.test.js tests/settings-ia-fj.test.js tests/settings-ia-acceptance-regression.test.js tests/settings-migration.test.js tests/settings-section-order.test.js tests/attachment-admission-policy.test.js tests/vision-resilience.test.js tests/degraded-local-evidence.test.js tests/vision-resilience-lifecycle.test.js tests/vision-breaker-readonly.test.js tests/alpha1-browser-lifecycle-integration.test.js tests/issue-367-remote-session-inject.test.js tests/vision-mode-toggle-injection.test.js tests/client.test.js tests/issue-307-regression.test.js tests/issue-284-vision-mode-toggle.test.js tests/issue-284-vision-mode-hardening.test.js tests/issue-284-attachment-id-hint.test.js tests/issue-284-model-visibility.test.js tests/issue-284-vision-selection-effort.test.js tests/client-presentation-boundary.test.js tests/clipboard-image-paste-compat.test.js tests/remote-settings-bridge.test.js tests/remote-settings-v2-contract.test.js tests/remote-settings-risk-confirmation.test.js tests/local-remote-settings-permission.test.js tests/artifact-path-security.test.js tests/http-compat.test.js tests/catalog-corrections.test.js tests/release-registry-identity.test.js tests/update-check.test.js tests/update-check-signal-lifecycle.test.js tests/self-update.test.js tests/doctor.test.js tests/doctor-cli.test.js tests/legacy-session-repair.test.js tests/profile-pnpm-diagnostics.test.js tests/doctor-v2.test.js tests/doctor-cli-v2.test.js tests/doctor-runtime.test.js tests/dsh-host-capabilities.test.js tests/doctor-host-capabilities.test.js tests/session-repair-v2.test.js tests/activation-shim-contract.test.js tests/bundle-defaults.test.js tests/file-logger.test.js tests/official-deepseek-catalog.test.js tests/replay-delegation.test.js tests/twin-image-capability-fallback.test.js tests/twin-image-custom-wrapper.test.js tests/adversarial-hardening.test.js tests/vision-adversarial-hardening-v2.test.js tests/vision-capability-benchmark-visual-proof.test.js tests/wrapper-directory.test.js tests/logging-ui.test.js tests/manifest-dependencies.test.js tests/architecture-2.3-gate.test.js tests/esm-cycle-audit.test.js tests/structured-bootstrap.test.js tests/structured-bootstrap-gate.test.js tests/vision-model-guidance-contract.test.js tests/local-ollama.test.js tests/local-only-vision.test.js tests/ollama-cold-start.test.js tests/ollama-cold-start-loaded.test.js tests/zero-regression-gate.test.js tests/runtime-e2e.test.js tests/local-vision-stabilizer.test.js tests/local-connection-probe.test.js tests/vision-diagnostics-routes.test.js tests/repetition-guard.test.js tests/guide-scroll-gate.test.js tests/settings-scroll-jank-regression.test.js tests/free-cloud-first.test.js tests/dsh-host-compat.test.js tests/native-image-coexistence.test.js tests/issue-276-session-image-ownership.test.js tests/issue-276-entry-policy-bridge.test.js tests/issue-276-policy-hardening.test.js tests/issue-289-native-nonintervention.test.js tests/issue-512-tool-restriction.test.js tests/issue-606-session-source-ownership.test.js tests/pi-ai-bridge-wire-compat.test.js tests/fetch-wrapper-lifecycle.test.js tests/fetch-wrapper-composition.test.js tests/provider-probe-proxy-scope.test.js tests/lifecycle-invariants.test.js tests/vision-tool-settings-generation.test.js tests/release-package-content-identity.test.js tests/release-state-machine.test.js tests/mixed-router.test.js tests/depth-tier.test.js tests/depth-quota-behavior.test.js tests/structured-flow-hardening.test.js tests/structured-flow-adversarial.test.js tests/vision-budget-activation.test.js tests/runtime-boundary-fixes.test.js tests/security-resource-boundaries.test.js tests/security-property-fuzz.test.js tests/security-adversarial-fuzz-contract.test.js tests/runtime-i18n.test.js tests/long-ocr-empty-retry.test.js tests/tesseract-adaptive-ocr.test.js tests/tesseract-node24-boot.test.js tests/session-vision-state.test.js tests/session-vision-state-integration.test.js tests/session-vision-event-feed.test.js tests/session-vision-policy.test.js tests/session-vision-policy-domain.test.js tests/session-vision-delegation.test.js tests/session-vision-delegation-boundary.test.js tests/subagent-vision-inheritance-characterization.test.js tests/subagent-vision-runtime-integration.test.js tests/subagent-vision-cold-resume.test.js tests/image-resource-governor.test.js tests/resource-retention-lifecycle.test.js tests/pixel-diff-stream.test.js tests/large-image-resource-integration.test.js tests/structured-guard-idempotency.test.js tests/vision-routing-product.test.js tests/vision-runtime-performance.test.js tests/vision-routing-settings-prelude.test.js tests/v2-settings-ia-integration.test.js tests/vision-capability-router.test.js tests/vision-capability-reference.test.js tests/vision-capability-benchmark.test.js tests/vision-capability-grounding-proof-contract.test.js tests/vision-capability-probe.test.js tests/vision-capability-axis-freshness.test.js tests/vision-capability-shadow-health.test.js tests/vision-capability-benchmark-service.test.js tests/vision-capability-preflight-hardening.test.js tests/vision-capability-benchmark-contract.test.js tests/vision-capability-benchmark-client.test.js tests/vision-capability-benchmark-client-productization.test.js tests/vision-capability-adapter-route.test.js tests/vision-background-benchmark.test.js tests/vision-background-lifecycle.test.js tests/vision-background-benchmark-productization.test.js tests/vision-capability-benchmark-budget.test.js tests/vision-image-input-verdict.test.js tests/vision-capability-failure-classification.test.js tests/vision-capability-gate-client.test.js tests/v2-product-review-fixes.test.js tests/qa-top5-reliability.test.js tests/qa-screenshot-runtime.test.js tests/qa-runtime-identity.test.js tests/qa-turn-budget-cancellation.test.js tests/qa-cancellation-publication.test.js tests/qa-reliability-tail.test.js tests/qa-endpoint-route-alias.test.js tests/settings-native-card-semantics.test.js tests/settings-native-card-lazy-data.test.js tests/settings-invalid-staged-dirty.test.js tests/vision-toggle-root-hardening.test.js && pnpm test:regression-closure && node --test tests/vision-backend-runtime-policy.test.js",
92
+ "test": "node --test tests/index-modularization.test.js tests/adapter-prepare-call-compat.test.js tests/core.test.js tests/issue-374-delegated-call-config.test.js tests/grounding-coordinate-frame.test.js tests/grounding-coordinate-runtime.test.js tests/capability-advisory.test.js tests/vision-execution-policy.test.js tests/vision-backend-diagnostics.test.js tests/live-model-discovery.test.js tests/cache-atomic-write-concurrency.test.js tests/live-model-credential-resolution.test.js tests/vision-model-registry.test.js tests/live-model-client-stability.test.js tests/providers-persistence.test.js tests/provider-directory-fallback.test.js tests/model-catalog-policy.test.js tests/settings-ia-client-prelude.test.js tests/settings-ia-fj.test.js tests/settings-ia-acceptance-regression.test.js tests/settings-migration.test.js tests/settings-section-order.test.js tests/attachment-admission-policy.test.js tests/vision-resilience.test.js tests/degraded-local-evidence.test.js tests/vision-resilience-lifecycle.test.js tests/vision-breaker-readonly.test.js tests/alpha1-browser-lifecycle-integration.test.js tests/issue-367-remote-session-inject.test.js tests/vision-mode-toggle-injection.test.js tests/client.test.js tests/issue-307-regression.test.js tests/issue-284-vision-mode-toggle.test.js tests/issue-284-vision-mode-hardening.test.js tests/issue-284-attachment-id-hint.test.js tests/issue-284-model-visibility.test.js tests/issue-284-vision-selection-effort.test.js tests/client-presentation-boundary.test.js tests/clipboard-image-paste-compat.test.js tests/remote-settings-bridge.test.js tests/remote-settings-v2-contract.test.js tests/remote-settings-risk-confirmation.test.js tests/local-remote-settings-permission.test.js tests/artifact-path-security.test.js tests/http-compat.test.js tests/catalog-corrections.test.js tests/release-registry-identity.test.js tests/update-check.test.js tests/update-check-signal-lifecycle.test.js tests/self-update.test.js tests/doctor.test.js tests/doctor-cli.test.js tests/legacy-session-repair.test.js tests/profile-pnpm-diagnostics.test.js tests/doctor-v2.test.js tests/doctor-cli-v2.test.js tests/doctor-runtime.test.js tests/dsh-host-capabilities.test.js tests/doctor-host-capabilities.test.js tests/session-repair-v2.test.js tests/activation-shim-contract.test.js tests/bundle-defaults.test.js tests/file-logger.test.js tests/official-deepseek-catalog.test.js tests/replay-delegation.test.js tests/twin-image-capability-fallback.test.js tests/twin-image-custom-wrapper.test.js tests/adversarial-hardening.test.js tests/vision-adversarial-hardening-v2.test.js tests/vision-capability-benchmark-visual-proof.test.js tests/wrapper-directory.test.js tests/logging-ui.test.js tests/manifest-dependencies.test.js tests/architecture-2.3-gate.test.js tests/host-settings-seam.test.js tests/drift-canary-settings-seam.test.js tests/esm-cycle-audit.test.js tests/structured-bootstrap.test.js tests/structured-bootstrap-gate.test.js tests/vision-model-guidance-contract.test.js tests/local-ollama.test.js tests/local-only-vision.test.js tests/ollama-cold-start.test.js tests/ollama-cold-start-loaded.test.js tests/zero-regression-gate.test.js tests/runtime-e2e.test.js tests/local-vision-stabilizer.test.js tests/local-connection-probe.test.js tests/vision-diagnostics-routes.test.js tests/repetition-guard.test.js tests/guide-scroll-gate.test.js tests/settings-scroll-jank-regression.test.js tests/free-cloud-first.test.js tests/dsh-host-compat.test.js tests/native-image-coexistence.test.js tests/issue-276-session-image-ownership.test.js tests/issue-276-entry-policy-bridge.test.js tests/issue-276-policy-hardening.test.js tests/issue-289-native-nonintervention.test.js tests/issue-512-tool-restriction.test.js tests/issue-606-session-source-ownership.test.js tests/pi-ai-bridge-wire-compat.test.js tests/fetch-wrapper-lifecycle.test.js tests/fetch-wrapper-composition.test.js tests/provider-probe-proxy-scope.test.js tests/lifecycle-invariants.test.js tests/vision-tool-settings-generation.test.js tests/release-package-content-identity.test.js tests/release-state-machine.test.js tests/mixed-router.test.js tests/depth-tier.test.js tests/depth-quota-behavior.test.js tests/structured-flow-hardening.test.js tests/structured-flow-adversarial.test.js tests/vision-budget-activation.test.js tests/runtime-boundary-fixes.test.js tests/security-resource-boundaries.test.js tests/security-property-fuzz.test.js tests/security-adversarial-fuzz-contract.test.js tests/runtime-i18n.test.js tests/long-ocr-empty-retry.test.js tests/tesseract-adaptive-ocr.test.js tests/tesseract-node24-boot.test.js tests/session-vision-state.test.js tests/session-vision-state-integration.test.js tests/session-vision-event-feed.test.js tests/session-vision-policy.test.js tests/session-vision-policy-domain.test.js tests/session-vision-delegation.test.js tests/session-vision-delegation-boundary.test.js tests/subagent-vision-inheritance-characterization.test.js tests/subagent-vision-runtime-integration.test.js tests/subagent-vision-cold-resume.test.js tests/image-resource-governor.test.js tests/resource-retention-lifecycle.test.js tests/pixel-diff-stream.test.js tests/large-image-resource-integration.test.js tests/structured-guard-idempotency.test.js tests/vision-routing-product.test.js tests/vision-runtime-performance.test.js tests/vision-routing-settings-prelude.test.js tests/v2-settings-ia-integration.test.js tests/vision-capability-router.test.js tests/vision-capability-reference.test.js tests/vision-capability-benchmark.test.js tests/vision-capability-grounding-proof-contract.test.js tests/vision-capability-probe.test.js tests/vision-capability-axis-freshness.test.js tests/vision-capability-shadow-health.test.js tests/vision-capability-benchmark-service.test.js tests/vision-capability-preflight-hardening.test.js tests/vision-capability-benchmark-contract.test.js tests/vision-capability-benchmark-client.test.js tests/vision-capability-benchmark-client-productization.test.js tests/vision-capability-adapter-route.test.js tests/vision-background-benchmark.test.js tests/vision-background-lifecycle.test.js tests/vision-background-benchmark-productization.test.js tests/vision-capability-benchmark-budget.test.js tests/vision-image-input-verdict.test.js tests/vision-capability-failure-classification.test.js tests/vision-capability-gate-client.test.js tests/v2-product-review-fixes.test.js tests/qa-top5-reliability.test.js tests/qa-screenshot-runtime.test.js tests/qa-runtime-identity.test.js tests/qa-turn-budget-cancellation.test.js tests/qa-cancellation-publication.test.js tests/qa-reliability-tail.test.js tests/qa-endpoint-route-alias.test.js tests/settings-native-card-semantics.test.js tests/settings-native-card-lazy-data.test.js tests/settings-invalid-staged-dirty.test.js tests/vision-toggle-root-hardening.test.js && pnpm test:regression-closure && node --test tests/vision-backend-runtime-policy.test.js",
93
93
  "test:core": "node --test tests/core.test.js tests/degraded-local-evidence.test.js tests/runtime-e2e.test.js tests/structured-bootstrap.test.js tests/structured-flow-hardening.test.js tests/structured-flow-adversarial.test.js tests/vision-execution-policy.test.js tests/vision-backend-runtime-policy.test.js",
94
94
  "test:routing": "node --test tests/issue-374-delegated-call-config.test.js tests/twin-image-capability-fallback.test.js tests/twin-image-custom-wrapper.test.js tests/vision-routing-product.test.js tests/vision-capability-router.test.js tests/vision-capability-probe.test.js tests/vision-capability-shadow-health.test.js tests/vision-runtime-performance.test.js tests/vision-background-benchmark.test.js tests/vision-image-input-verdict.test.js",
95
95
  "test:session": "node --test tests/session-vision-state.test.js tests/session-vision-state-integration.test.js tests/session-vision-policy.test.js tests/session-vision-delegation.test.js tests/session-vision-delegation-boundary.test.js tests/subagent-vision-inheritance-characterization.test.js tests/subagent-vision-runtime-integration.test.js tests/subagent-vision-cold-resume.test.js tests/session-vision-policy-domain.test.js tests/replay-delegation.test.js tests/legacy-session-repair.test.js tests/session-repair-v2.test.js tests/issue-276-session-image-ownership.test.js",