dsh-vision-router 2.2.7 → 2.2.8
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 +2 -2
- package/README.zh.md +2 -2
- package/docs/architecture/dsh-020-rc2-adaptation.md +23 -0
- package/docs/architecture/dsh-compatibility-matrix.md +1 -1
- package/docs/architecture/dsh-support-window.md +4 -2
- package/docs/architecture/host-first-proxy-convergence.md +1 -1
- package/docs/releases/v2.2.8.md +25 -0
- package/lib/client-host-compat-prelude.js +7 -12
- package/lib/client-presentation-boundary-main.js +7 -15
- package/lib/client-presentation-boundary.js +7 -12
- package/lib/client-script-carrier.js +90 -0
- package/lib/client.js +2 -2
- package/lib/dsh-support-window.js +1 -1
- package/lib/guide-vision-toggle-highlight.js +7 -10
- package/lib/live-model-client-prelude.js +7 -14
- package/lib/local-remote-settings-permission.js +8 -9
- package/lib/session-vision-mode-boundary.js +43 -8
- package/lib/settings-client-rc8-lifecycle.js +7 -12
- package/lib/settings-factory-lifecycle.js +25 -14
- package/lib/settings-ia-client-prelude.js +11 -11
- package/lib/settings-limit-client-prelude.js +7 -9
- package/lib/strict-live-model-client-prelude.js +7 -10
- package/lib/v2-settings-ia-integration.js +21 -6
- package/lib/vision-background-benchmark.js +3 -5
- package/lib/vision-capability-benchmark-client.js +7 -10
- package/lib/vision-exact-check-client.js +11 -6
- package/lib/vision-model-visibility-boundary-main.js +7 -11
- package/lib/vision-model-visibility-boundary.js +7 -12
- package/lib/vision-routing-settings-prelude.js +7 -11
- package/lib/vision-toggle-root-hardening.js +28 -3
- package/lib/vision-turn-budget-client-prelude.js +7 -10
- package/lib/web/remote-settings-client.js +7 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -375,7 +375,7 @@ Everything is optional; defaults work out of the box. Prefer **Settings → Visi
|
|
|
375
375
|
| `tool` / `progressiveTools` / `autoActivateOnImage` | `true` / `false` / `true` | vision tools on / progressive mounting (off by default for a stable tool schema) / image-turn auto-mount when progressive mode is enabled; `progressiveTools` is boot-time config |
|
|
376
376
|
| `rewriteImages` | `true` | rewrite image blocks in the model input (cached description or tool-hint marker); the UI log keeps images |
|
|
377
377
|
| `desktopScreenshot` | `false` | privacy opt-in for the model-callable `vision_screenshot` desktop-capture tool; checked live before every capture |
|
|
378
|
-
| `localOnlyVision` | `false` |
|
|
378
|
+
| `localOnlyVision` | `false` | visual-endpoint privacy policy: only loopback vision endpoints may execute, so Vision Router keeps image bytes/crops off cloud vision services; derived result text (descriptions, OCR, coordinates) still goes to the current chat model, so strict end-to-end local use also requires a local chat model |
|
|
379
379
|
| `freeFallback` | `true` | append the anonymous OVH models after explicit local/custom HTTP backends; ignored while `localOnlyVision` is enabled |
|
|
380
380
|
| `localOllama` | `{ enabled: false, baseURL: 'http://127.0.0.1:11434/v1', model: 'qwen2.5vl', format: 'openai', maxTokens: 4096, reasoningEffort: 'none' }` | local vision backend; OpenAI mode disables supported model reasoning by default so the output budget is spent on answer text |
|
|
381
381
|
| `localLmStudio` | `{ enabled: false, baseURL: 'http://localhost:1234/v1', model: '', format: 'openai', maxTokens: 4096, reasoningEffort: 'none' }` | local LM Studio backend after Ollama; LM Studio 0.4+ can use `format: 'lmstudio'` for documented native reasoning control |
|
|
@@ -436,7 +436,7 @@ ollama pull qwen2.5vl
|
|
|
436
436
|
## Requirements
|
|
437
437
|
|
|
438
438
|
- DeepSeek Harness Web profile. Normal installs can use `npx @deepseek-ai/dsh ...`; source checkouts use `pnpm dsh ...`. A bare `dsh ...` command only works when the CLI is already on your shell `PATH`.
|
|
439
|
-
- **DSH Host support policy:** DVR 2.2.x keeps DSH `0.1.0-rc.8` as the public minimum and currently supports the released stable channel through `0.1.5-rc.3`. The DSH `0.2.x` train is peer-admitted from the verified `0.2.0-rc.1` boundary (`>=0.2.0-rc.1 <0.3.0-0`); exact `0.2.0-rc.
|
|
439
|
+
- **DSH Host support policy:** DVR 2.2.x keeps DSH `0.1.0-rc.8` as the public minimum and currently supports the released stable channel through `0.1.5-rc.3`. The DSH `0.2.x` train is peer-admitted from the verified `0.2.0-rc.1` boundary (`>=0.2.0-rc.1 <0.3.0-0`); exact `0.2.0-rc.2` (`next/rc`) source, real Host, Windows Desktop, multi-plugin, Settings, mixed-attachment, bundle-recompose, and Host-Sharp gates are preview verification evidence, not a support-floor increase. The final stable `0.2.0` tag and signed Desktop artifact will still be revalidated when published. DVR 2.0.x was the final train with public support for rc.6/rc.7. See [DSH Host support window](docs/architecture/dsh-support-window.md).
|
|
440
440
|
- Node ≥ 22 (host side).
|
|
441
441
|
- No API key for the default free chain; a credential reference (`apiKeyEnv`) only for paid `httpProviders`.
|
|
442
442
|
- Chrome / Chromium / Edge is needed only for `vision_html_screenshot`; every other tool works without a browser.
|
package/README.zh.md
CHANGED
|
@@ -373,7 +373,7 @@ Web profile 现在提供一级 **设置 → Vision Router** 页面。常规页
|
|
|
373
373
|
| `tool` / `progressiveTools` / `autoActivateOnImage` | `true` / `false` / `true` | 视觉工具总开关 / 渐进式挂载(默认关闭以稳定工具 schema)/ 渐进模式下图片轮自动挂载;`progressiveTools` 为启动期配置 |
|
|
374
374
|
| `rewriteImages` | `true` | 模型输入层改写图片块(缓存描述或工具提示标记);界面日志保留图片 |
|
|
375
375
|
| `desktopScreenshot` | `false` | 模型可调用的 `vision_screenshot` 桌面截屏隐私开关;每次截屏前实时检查 |
|
|
376
|
-
| `localOnlyVision` | `false` |
|
|
376
|
+
| `localOnlyVision` | `false` | 视觉端点隐私策略:只有回环地址上的视觉端点可执行,Vision Router 不会把图片/裁剪发给云端视觉服务;描述、OCR、坐标等识图结果文本仍会交给当前聊天模型,因此严格端到端纯本地还需同时使用本地聊天模型 |
|
|
377
377
|
| `freeFallback` | `true` | 在显式本地/自定义 HTTP 后端之后追加匿名 OVH 模型;`localOnlyVision` 开启期间不会执行 |
|
|
378
378
|
| `localOllama` | `{ enabled: false, baseURL: 'http://127.0.0.1:11434/v1', model: 'qwen2.5vl', format: 'openai', maxTokens: 4096, reasoningEffort: 'none' }` | 本地视觉后端;OpenAI 模式默认关闭受支持模型的推理,把输出预算留给正文 |
|
|
379
379
|
| `localLmStudio` | `{ enabled: false, baseURL: 'http://localhost:1234/v1', model: '', format: 'openai', maxTokens: 4096, reasoningEffort: 'none' }` | Ollama 之后的本地 LM Studio 后端;LM Studio 0.4+ 可选 `format: 'lmstudio'` 使用官方原生推理控制 |
|
|
@@ -434,7 +434,7 @@ ollama pull qwen2.5vl
|
|
|
434
434
|
## 环境要求
|
|
435
435
|
|
|
436
436
|
- DeepSeek Harness 的 Web profile。普通安装可用 `npx @deepseek-ai/dsh ...`;从源码仓库运行时用 `pnpm dsh ...`。只有 CLI 已经进入系统 `PATH` 时才能直接写 `dsh ...`。
|
|
437
|
-
- **DSH Host 支持策略:** DVR 2.2.x 的公开最低 Host 仍为 DSH `0.1.0-rc.8`,当前稳定通道已验证并支持到 `0.1.5-rc.3`。DSH `0.2.x` 从已验证的 `0.2.0-rc.1` 边界开始准入(`>=0.2.0-rc.1 <0.3.0-0`);精确 `0.2.0-rc.
|
|
437
|
+
- **DSH Host 支持策略:** DVR 2.2.x 的公开最低 Host 仍为 DSH `0.1.0-rc.8`,当前稳定通道已验证并支持到 `0.1.5-rc.3`。DSH `0.2.x` 从已验证的 `0.2.0-rc.1` 边界开始准入(`>=0.2.0-rc.1 <0.3.0-0`);精确 `0.2.0-rc.2`(`next/rc`)源码、真实 Host、Windows Desktop、多插件、Settings、混合附件、bundle 重组及 Host-Sharp 门禁仅作为预览验证证据,不抬高公开支持下限。上游正式发布稳定版 `0.2.0` tag 和签名 Desktop 安装包后仍会立即复验。DVR 2.0.x 是最后公开支持 rc.6/rc.7 的版本线。详见 [DSH Host 支持窗口](docs/architecture/dsh-support-window.md)。
|
|
438
438
|
- Node ≥ 22(宿主侧)。
|
|
439
439
|
- 默认免费链路无需 API Key;付费 `httpProviders` 只需一个凭据引用(`apiKeyEnv`)。
|
|
440
440
|
- 只有 `vision_html_screenshot` 需要 Chrome / Chromium / Edge;其余工具无浏览器也能用。
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# DSH 0.2.0-rc.2 compatibility audit
|
|
2
|
+
|
|
3
|
+
- Channel: next/rc (kept distinct from stable and alpha/pre-release)
|
|
4
|
+
- Upstream tag: `dsh-v0.2.0-rc.2`
|
|
5
|
+
- Upstream commit: `639ed015397290b3745d163aafe02ffee4aa3f84`
|
|
6
|
+
- Published: 2026-09-29 09:21:31 UTC
|
|
7
|
+
- Comparison baseline: `dsh-v0.2.0-rc.1` at `4878cdabd87d4041bdaff61d04c966883b9fd07a`
|
|
8
|
+
|
|
9
|
+
## Seam review
|
|
10
|
+
|
|
11
|
+
| Area | rc.1 → rc.2 finding | DVR action |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Host / Desktop | Desktop now ships a bundled CLI entry, command installation management, login-shell environment import, and Windows console signal bridging. Existing Web profile plugin injection remains available. | Re-run exact-source on Windows/macOS/Linux plus Windows Desktop on Node 22 and 24. |
|
|
14
|
+
| Session | Session controller source is unchanged; its package version advances only. New timed user-question persistence is additive outside DVR's session image ownership seam. | Retain session/adversarial regressions and real Host lifecycle checks. |
|
|
15
|
+
| Settings | Settings controller source is unchanged. Web preset/default and Creator controls are no longer gated by Coding Tools. | Re-run Settings mount/save/reload/readback in real Chromium; no compatibility shim added. |
|
|
16
|
+
| bundle | Published bundle package sources are unchanged apart from versions. Desktop/Host bundles become more self-contained for CLI launch. | Re-run bundle recomposition and cache-manifest validation. |
|
|
17
|
+
| web/client | Model picker search, shortcut modal caching, preset Settings behavior, and long-session rendering changed. | Re-run Vision toggle, mixed attachments, Settings, and client/browser contract suites. |
|
|
18
|
+
| model / attachment | Attachment package sources are unchanged; model-facing work is UI/search and pi-ai/Mistral compatibility. | Re-run mixed-attachment and provider wire contracts. |
|
|
19
|
+
| service injection | API Remotes adds the user-question remote; Gateway adds additive `hasLiveClient()`. DVR's declared injected services and Host-owned proxy authority are unchanged. | Re-run injection, proxy-egress, multi-plugin ordering, and Host-Sharp contracts. |
|
|
20
|
+
|
|
21
|
+
## Policy
|
|
22
|
+
|
|
23
|
+
The public minimum Host remains DSH `0.1.0-rc.8`. The current stable evidence remains `0.1.5-rc.3`. The peer range remains `>=0.2.0-rc.1 <0.3.0-0`; rc.2 advances exact next/rc verification evidence and does not widen the range or turn preview evidence into a stable support claim.
|
|
@@ -12,7 +12,7 @@ The matrix is capability-based. Runtime code must feature-detect the seam it nee
|
|
|
12
12
|
| `legacy-contract` | `0.1.0-rc.8` | Public support-floor fixture carrying batch attachments and dimension policy. |
|
|
13
13
|
| `current-contract` | `0.1.5-rc.3` | Current stable Host contract baseline. Must remain green. |
|
|
14
14
|
| `preview-contract` | `0.1.7-rc.2` | Retained predecessor-preview regression fixture. |
|
|
15
|
-
| `DSH 0.2.0-rc.
|
|
15
|
+
| `DSH 0.2.0-rc.2 validation` | `0.2.0-rc.2` (`next/rc`) | Current exact next/rc evidence; not a public preview-support claim. |
|
|
16
16
|
| retained preview regression | `0.1.5-alpha.2` | Older preview regression fixture retained as an older preview regression while the 0.1.7 next line is qualified. |
|
|
17
17
|
| release-channel canary | npm `latest` / `next` / `alpha` | Separately labelled stable, next/rc, and alpha/pre-release drift surveillance. Never changes support policy by itself. |
|
|
18
18
|
|
|
@@ -19,7 +19,7 @@ No later support-floor increase is currently announced.
|
|
|
19
19
|
|
|
20
20
|
### DSH 0.2.x forward admission
|
|
21
21
|
|
|
22
|
-
DVR 2.2.x peer-admits the DSH `0.2.x` train from the verified rc.1 boundary (`>=0.2.0-rc.1 <0.3.0-0`) without raising the `0.1.0-rc.8` minimum Host floor. Initial forward admission was backed by public pre-0.2.0 master `21638c56315ae6a2b552d6091945d3144c9af32e`. Exact release evidence has now advanced to immutable `dsh-v0.2.0-rc.
|
|
22
|
+
DVR 2.2.x peer-admits the DSH `0.2.x` train from the verified rc.1 boundary (`>=0.2.0-rc.1 <0.3.0-0`) without raising the `0.1.0-rc.8` minimum Host floor. Initial forward admission was backed by public pre-0.2.0 master `21638c56315ae6a2b552d6091945d3144c9af32e`. Exact release evidence has now advanced to immutable `dsh-v0.2.0-rc.2` commit `639ed015397290b3745d163aafe02ffee4aa3f84`, published on 2026-09-29. Required gates cover three-OS source contracts, real Host + Chromium, Settings mount/save/reload/readback, mixed attachments, bundle recomposition, Windows Node 22/24 Desktop authentication, the Node 24 multi-plugin isolation adversary, and Host-owned Sharp resolution.
|
|
23
23
|
|
|
24
24
|
The final immutable upstream `0.2.0` stable tag and signed official Desktop installer remain release-time revalidation targets. The rc.1 evidence is next-channel verification, not a preview support promise and not a support-floor increase.
|
|
25
25
|
|
|
@@ -30,7 +30,7 @@ Compatibility evidence answers a different question: what exact upstream release
|
|
|
30
30
|
| Evidence role | DSH source | Meaning |
|
|
31
31
|
|---|---|---|
|
|
32
32
|
| Exact stable evidence | `0.1.5-rc.3` | Required Host/wire and real Host + Chromium coverage for the current stable release. |
|
|
33
|
-
| Exact next/rc evidence | `0.2.0-rc.
|
|
33
|
+
| Exact next/rc evidence | `0.2.0-rc.2` (`next/rc`) | Required Host/wire/lifecycle/browser evidence. This is not a preview support promise. |
|
|
34
34
|
| Stable drift canary | npm dist-tag `latest` | Scheduled, dynamically resolved surveillance. A failure starts compatibility investigation; it does not rewrite support policy. |
|
|
35
35
|
| Next/rc drift canary | npm dist-tag `next` | Scheduled, dynamically resolved surveillance for the rc channel. A failure does not rewrite support policy. |
|
|
36
36
|
| Alpha/pre-release drift canary | npm dist-tag `alpha` | Scheduled, dynamically resolved surveillance for the alpha channel. A failure does not rewrite support policy. |
|
|
@@ -76,3 +76,5 @@ DVR must not turn these tables into widespread version-string conditionals. Runt
|
|
|
76
76
|
## Compatibility-retirement rule
|
|
77
77
|
|
|
78
78
|
The 2.1.x floor makes rc.6-only compatibility candidates 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.
|
|
79
|
+
|
|
80
|
+
See [DSH 0.2.0-rc.2 compatibility audit](dsh-020-rc2-adaptation.md) for the upstream seam review and validation plan.
|
|
@@ -50,7 +50,7 @@ H1 is complete as an egress contract, not as a new production dependency. Exact
|
|
|
50
50
|
4. The same contract runs against the exact current stable Host and exact preview evidence on Linux, macOS, and Windows through the existing source-contract matrix.
|
|
51
51
|
5. Production code contains no `@deepseek-ai/dsh-http-proxy` dependency or import. `proxyRouteFor()` is used only by the exact-source test as an oracle for the Host decision.
|
|
52
52
|
|
|
53
|
-
The current stable evidence remains DSH `0.1.5-rc.3`; `0.1.5-rc.1` / `0.1.5-rc.2` remain explicitly peer-admitted, exact next/rc evidence is `0.2.0-rc.
|
|
53
|
+
The current stable evidence remains DSH `0.1.5-rc.3`; `0.1.5-rc.1` / `0.1.5-rc.2` remain explicitly peer-admitted, exact next/rc evidence is `0.2.0-rc.2`, and the public minimum remains `0.1.0-rc.8`.
|
|
54
54
|
|
|
55
55
|
## H2 — Scope Host-owned override authority
|
|
56
56
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# v2.2.8
|
|
2
|
+
|
|
3
|
+
DSH Vision Router 2.2.8 is a Desktop compatibility and lifecycle reliability patch. It restores conversation-side Vision UI injection in the official static-shell Desktop, prevents stale Vision tool restrictions from surviving plugin reloads, adds real Electron end-to-end coverage on Windows and macOS, hardens DSH build-cache completeness and test diagnostics, clarifies the `localOnlyVision` privacy boundary, and advances exact preview verification to DSH `0.2.0-rc.2` without changing the public Host support floor.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- **Official Desktop conversation injection (#597):** browser-side client preludes now use one capability-detected carrier that publishes structured `webserver/index-inject` rows for the official Desktop shell while retaining `tapIndex()` as the older/Web fallback. The Vision toggle and related client boundaries no longer depend on HTTP-served HTML to appear.
|
|
8
|
+
- **Vision tool lifecycle fix (#597):** Session tool restrictions installed while Vision mode is off are explicitly released when the plugin boundary is disposed. A surviving Agent can no longer inherit an orphaned restriction from an older plugin/HMR instance and keep `vision_describe` and the rest of the DVR tool surface hidden after Vision is re-enabled.
|
|
9
|
+
- **Real Desktop functional gates (#597):** Windows Node 24 and macOS now launch the actual Electron renderer and exercise Settings open/save/reload, workspace/session startup, image attachment, Vision toggle state, real `vision_describe` tool calling, image delivery to a controlled local vision backend, and the final rendered assistant response. Separate adversarial tests still cover duplicate/ordered carriers, failure recovery, OFF→ON→OFF authority, and plugin reload boundaries.
|
|
10
|
+
- **Complete DSH build caches (#597):** trusted exact-Host caches retain both native `packages/*/lib` JavaScript outputs and `packages/*/bin` artifacts under a new cache schema. A cache hit can no longer restore a half-built DSH runtime missing files such as `@deepseek-ai/node-addon-system/lib/flock.js`.
|
|
11
|
+
- **Safer Desktop diagnostics (#597):** failure artifacts persist fixed summaries of Host/network evidence rather than writing arbitrary response bodies to disk. CodeQL verified the previous network-data-to-file path as fixed.
|
|
12
|
+
- **`localOnlyVision` privacy wording (#598):** Settings and documentation now state the actual boundary precisely: raw image bytes stay on local vision backends, while derived OCR/description/tool results can still enter the active chat-model context. This is a disclosure clarification, not a routing-policy change.
|
|
13
|
+
- **DSH `0.2.0-rc.2` verification (#599):** the permanent next/rc compatibility gate now pins exact rc.2 source and covers Linux/macOS/Windows source contracts, Node 22/24 Windows Desktop Host, real Host + Chromium, multi-plugin ordering, Settings, mixed attachments, bundle recomposition, and real Windows/macOS Electron renderer flows. The peer-admission boundary and public minimum remain unchanged.
|
|
14
|
+
|
|
15
|
+
## Validation
|
|
16
|
+
|
|
17
|
+
- Final local regression on the rc.2-compatible tree: main suite `1534` passed, `1` intentional skip, `0` failed; backend runtime-policy suite passed `9/9`.
|
|
18
|
+
- DSH `0.2.0-rc.2` passed exact-source checks on Ubuntu, Windows, and macOS; real Host + Chromium; Windows Desktop Host on Node 22 and Node 24; Node 24 multi-plugin ordering; and real Windows/macOS Electron renderer lifecycle tests.
|
|
19
|
+
- The real Desktop Vision flow reached the controlled text model, issued `vision_describe`, delivered actual image content to the controlled vision backend, consumed the tool result, and rendered the expected final response.
|
|
20
|
+
- The post-merge `main` push gates for `f92a6e0` completed with 0 failures, including CI, HOL security, DSH exact-source, DSH 0.1.7 real Host, DSH `0.2.0-rc.2` validation, preview browser smoke, routing parity, data boundary, architecture closure, and compatibility convergence.
|
|
21
|
+
- The public minimum supported DSH Host remains `0.1.0-rc.8`; existing 2.2.x settings require no migration.
|
|
22
|
+
|
|
23
|
+
## Upgrade
|
|
24
|
+
|
|
25
|
+
2.2.7 users should upgrade to 2.2.8, especially when using the official Desktop app or frequently reloading/updating plugins in long-lived sessions. No configuration changes are required.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
|
|
2
2
|
|
|
3
3
|
const CLIENT_HOST_COMPAT_MARK = 'data-vision-router-host-compat'
|
|
4
4
|
|
|
@@ -243,19 +243,14 @@ export const CLIENT_HOST_COMPAT_PRELUDE = String.raw`(function(){
|
|
|
243
243
|
})();`
|
|
244
244
|
|
|
245
245
|
export function injectClientHostCompatibility(html) {
|
|
246
|
-
|
|
247
|
-
const safe = CLIENT_HOST_COMPAT_PRELUDE.replace(/<\/script/gi, '<\\/script')
|
|
248
|
-
const script = `<script ${CLIENT_HOST_COMPAT_MARK}>${safe}</script>`
|
|
249
|
-
const closeHead = html.indexOf('</head>')
|
|
250
|
-
if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
|
|
251
|
-
return `${html}${script}`
|
|
246
|
+
return injectClientCarrierHtml(html, CLIENT_HOST_COMPAT_MARK, CLIENT_HOST_COMPAT_PRELUDE)
|
|
252
247
|
}
|
|
253
248
|
|
|
254
249
|
export function installClientHostCompatibility(ctx) {
|
|
255
|
-
ctx
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
250
|
+
installClientScriptCarriers(ctx, {
|
|
251
|
+
marker: CLIENT_HOST_COMPAT_MARK,
|
|
252
|
+
prelude: CLIENT_HOST_COMPAT_PRELUDE,
|
|
253
|
+
label: 'vision-router: client Host compatibility',
|
|
254
|
+
injectHtml: injectClientHostCompatibility,
|
|
260
255
|
})
|
|
261
256
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
|
|
1
2
|
const CLIENT_PRESENTATION_MARK = 'data-vision-router-presentation-boundary'
|
|
2
3
|
const VISION_MODE_WRAPPER_SOURCE = 'deepseek-official'
|
|
3
4
|
|
|
@@ -1304,23 +1305,14 @@ export const CLIENT_PRESENTATION_PRELUDE = String.raw`(function(){
|
|
|
1304
1305
|
})();`
|
|
1305
1306
|
|
|
1306
1307
|
export function injectClientPresentationBoundary(html) {
|
|
1307
|
-
|
|
1308
|
-
const safe = CLIENT_PRESENTATION_PRELUDE.replace(/<\/script/gi, '<\\/script')
|
|
1309
|
-
const script = `<script ${CLIENT_PRESENTATION_MARK}>${safe}</script>`
|
|
1310
|
-
// DSH's client-modules index tap prepends the queue-mode loader bootstrap at
|
|
1311
|
-
// <head>. Run after that parser bootstrap so we can wrap both load() and the
|
|
1312
|
-
// later create() transition. The create wrapper re-applies the boundary when
|
|
1313
|
-
// rc.8 replaces loader.load while entering live mode.
|
|
1314
|
-
const closeHead = html.indexOf('</head>')
|
|
1315
|
-
if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
|
|
1316
|
-
return `${html}${script}`
|
|
1308
|
+
return injectClientCarrierHtml(html, CLIENT_PRESENTATION_MARK, CLIENT_PRESENTATION_PRELUDE)
|
|
1317
1309
|
}
|
|
1318
1310
|
|
|
1319
1311
|
export function installClientPresentationBoundary(ctx) {
|
|
1320
|
-
ctx
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1312
|
+
installClientScriptCarriers(ctx, {
|
|
1313
|
+
marker: CLIENT_PRESENTATION_MARK,
|
|
1314
|
+
prelude: CLIENT_PRESENTATION_PRELUDE,
|
|
1315
|
+
label: 'vision-router: client presentation boundary',
|
|
1316
|
+
injectHtml: injectClientPresentationBoundary,
|
|
1325
1317
|
})
|
|
1326
1318
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
|
|
2
2
|
import {
|
|
3
3
|
CLIENT_PRESENTATION_PRELUDE as BASE_CLIENT_PRESENTATION_PRELUDE,
|
|
4
4
|
resolveVisionModePair,
|
|
@@ -187,20 +187,15 @@ export {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
export function injectClientPresentationBoundary(html) {
|
|
190
|
-
|
|
191
|
-
const safe = CLIENT_PRESENTATION_PRELUDE.replace(/<\/script/gi, '<\\/script')
|
|
192
|
-
const script = `<script ${CLIENT_PRESENTATION_MARK}>${safe}</script>`
|
|
193
|
-
const closeHead = html.indexOf('</head>')
|
|
194
|
-
if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
|
|
195
|
-
return `${html}${script}`
|
|
190
|
+
return injectClientCarrierHtml(html, CLIENT_PRESENTATION_MARK, CLIENT_PRESENTATION_PRELUDE)
|
|
196
191
|
}
|
|
197
192
|
|
|
198
193
|
export function installClientPresentationBoundary(ctx) {
|
|
199
|
-
ctx
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
194
|
+
installClientScriptCarriers(ctx, {
|
|
195
|
+
marker: CLIENT_PRESENTATION_MARK,
|
|
196
|
+
prelude: CLIENT_PRESENTATION_PRELUDE,
|
|
197
|
+
label: 'vision-router: client presentation boundary',
|
|
198
|
+
injectHtml: injectClientPresentationBoundary,
|
|
204
199
|
})
|
|
205
200
|
// Keep Host-version compatibility in its own capability-detected prelude.
|
|
206
201
|
// This lets the large legacy client factory stay byte-stable while alpha/new
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { htmlHasScriptMarker } from './html-script-marker.js'
|
|
2
|
+
|
|
3
|
+
const CARRIER_REGISTRY = '__dshVisionRouterClientCarriers'
|
|
4
|
+
|
|
5
|
+
function carrierKey(marker) {
|
|
6
|
+
const value = String(marker || 'vision-router-client-script')
|
|
7
|
+
return `vr:${value.replace(/^data-vision-router-/, '')}`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Wrap one browser prelude with a shared one-shot fence. Modern served-Web
|
|
12
|
+
* pages can receive both structured index rows and tapIndex HTML transforms,
|
|
13
|
+
* while Desktop/static carriers receive only the structured row. The fence
|
|
14
|
+
* keeps both delivery paths safe without relying on each prelude to implement
|
|
15
|
+
* its own duplicate-execution semantics.
|
|
16
|
+
*/
|
|
17
|
+
export function clientCarrierSource(marker, prelude) {
|
|
18
|
+
const key = JSON.stringify(carrierKey(marker))
|
|
19
|
+
return `(function(){\n var root = window;\n var registry = root.${CARRIER_REGISTRY};\n if (!registry || typeof registry !== 'object') {\n registry = Object.create(null);\n try { Object.defineProperty(root, '${CARRIER_REGISTRY}', { value: registry, configurable: true }); }\n catch (_) { root.${CARRIER_REGISTRY} = registry; }\n }\n var key = ${key};\n if (registry[key]) return;\n registry[key] = true;\n try {\n${prelude}\n } catch (error) {\n try { delete registry[key]; } catch (_) { registry[key] = false; }\n throw error;\n }\n})();`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function injectClientCarrierHtml(html, marker, prelude) {
|
|
23
|
+
const structuredMark = `/* ${marker} */`
|
|
24
|
+
if (typeof html !== 'string' || htmlHasScriptMarker(html, marker) || html.includes(structuredMark)) return html
|
|
25
|
+
const safe = clientCarrierSource(marker, prelude).replace(/<\/script/gi, '<\\/script')
|
|
26
|
+
const script = `<script ${marker}>${safe}</script>`
|
|
27
|
+
const closeHead = html.indexOf('</head>')
|
|
28
|
+
return closeHead === -1 ? `${html}${script}` : `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
export function injectClientStyleHtml(html, marker, css) {
|
|
33
|
+
if (typeof html !== 'string' || typeof marker !== 'string' || marker === '') return html
|
|
34
|
+
if (html.includes(`<style ${marker}>`)) return html
|
|
35
|
+
const style = `<style ${marker}>${String(css || '')}</style>`
|
|
36
|
+
const closeHead = html.indexOf('</head>')
|
|
37
|
+
return closeHead === -1 ? `${html}${style}` : `${html.slice(0, closeHead)}${style}${html.slice(closeHead)}`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function appendStructuredClientStyle(table, marker, css) {
|
|
41
|
+
if (!Array.isArray(table) || typeof marker !== 'string' || marker === '') return false
|
|
42
|
+
const signature = `/* ${marker}:style */`
|
|
43
|
+
if (table.some((row) => row?.kind === 'style' && typeof row.text === 'string' && row.text.includes(signature))) return false
|
|
44
|
+
table.push({ kind: 'style', placement: 'head', text: `${signature}
|
|
45
|
+
${String(css || '')}` })
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function appendStructuredClientCarrier(table, marker, prelude) {
|
|
50
|
+
if (!Array.isArray(table)) return false
|
|
51
|
+
const signature = `/* ${marker} */`
|
|
52
|
+
if (table.some((row) => row?.kind === 'script' && typeof row.text === 'string' && row.text.includes(signature))) {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
table.push({
|
|
56
|
+
kind: 'script',
|
|
57
|
+
placement: 'head',
|
|
58
|
+
text: `${signature}\n${clientCarrierSource(marker, prelude)}`,
|
|
59
|
+
})
|
|
60
|
+
return true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Register the same client prelude on both supported browser carriers:
|
|
65
|
+
* - structured `webserver/index-inject` rows for Desktop/static shells;
|
|
66
|
+
* - `tapIndex()` HTML transforms for served-Web and older Hosts.
|
|
67
|
+
*/
|
|
68
|
+
export function installClientScriptCarriers(ctx, { marker, prelude, label, injectHtml, style }) {
|
|
69
|
+
if (!ctx || typeof ctx.inject !== 'function') return
|
|
70
|
+
ctx.inject(['webServer'], (webCtx) => {
|
|
71
|
+
if (typeof webCtx.on === 'function') {
|
|
72
|
+
webCtx.on('webserver/index-inject', (table) => {
|
|
73
|
+
if (style) appendStructuredClientStyle(table, style.marker || marker, style.text)
|
|
74
|
+
appendStructuredClientCarrier(table, marker, prelude)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
if (typeof webCtx.effect === 'function' && webCtx.webServer?.tapIndex) {
|
|
78
|
+
const baseTransform = typeof injectHtml === 'function'
|
|
79
|
+
? injectHtml
|
|
80
|
+
: (html) => injectClientCarrierHtml(html, marker, prelude)
|
|
81
|
+
const transform = style
|
|
82
|
+
? (html) => baseTransform(injectClientStyleHtml(html, style.marker || marker, style.text))
|
|
83
|
+
: baseTransform
|
|
84
|
+
webCtx.effect(
|
|
85
|
+
() => webCtx.webServer.tapIndex(transform),
|
|
86
|
+
label,
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -230,7 +230,7 @@ window.__ModuleLoader__.load({
|
|
|
230
230
|
hintRewriteImages: '避免把无法读取的原始图片直接发送给纯文字模型。推荐保持开启。',
|
|
231
231
|
hintDownscale: '超过像素预算的图片先缩放再送视觉模型,降低延迟与成本;默认开启。',
|
|
232
232
|
hintCache: '缓存识图答案(按图片内容 + 问题);默认开启。',
|
|
233
|
-
hintLocalOnlyVision: '开启后仅允许回环地址上的本地视觉后端;云端/DSH Provider 与 OVH
|
|
233
|
+
hintLocalOnlyVision: '开启后仅允许回环地址上的本地视觉后端;云端/DSH Provider 与 OVH 免费兜底会被运行时硬阻止,因此 Vision Router 不会把图片和裁剪发送到云端视觉服务。视觉后端返回的描述、OCR、坐标等识图结果文本仍会交给当前聊天模型;若聊天模型在云端,图片语义仍可能离开本机。严格纯本地还需同时选择本地聊天模型。',
|
|
234
234
|
hintFreeFallback: '当你选择的识图模型都不可用时,自动尝试内置 OVH 免费模型。免注册、免 API Key。',
|
|
235
235
|
hintFreeCloudFirst:
|
|
236
236
|
'默认关闭。开启后,云端后端先尝试内置 OVH 免费模型(免注册、免 API Key),' +
|
|
@@ -519,7 +519,7 @@ window.__ModuleLoader__.load({
|
|
|
519
519
|
hintRewriteImages: 'Prevents raw image content from being sent to a text-only model that cannot read it. Recommended on.',
|
|
520
520
|
hintDownscale: 'Images beyond the pixel budget are resized before the vision call, cutting latency and cost; on by default.',
|
|
521
521
|
hintCache: 'Caches vision answers (keyed by image content + question); on by default.',
|
|
522
|
-
hintLocalOnlyVision: 'When enabled, only visual backends on loopback addresses may run. Cloud/DSH providers and the OVH free fallback are blocked at runtime
|
|
522
|
+
hintLocalOnlyVision: 'When enabled, only visual backends on loopback addresses may run. Cloud/DSH providers and the OVH free fallback are blocked at runtime, so Vision Router does not send image bytes or crops to cloud vision services. Derived result text—descriptions, OCR, and coordinates—is still passed to the current chat model; if that model is remote, image semantics may still leave this machine. For a strict end-to-end local workflow, select a local chat model too.',
|
|
523
523
|
hintFreeFallback: 'If your selected vision models fail, automatically try the built-in OVH free models. No signup or API key required.',
|
|
524
524
|
hintFreeCloudFirst:
|
|
525
525
|
'Off by default. When enabled, cloud backends try the built-in OVH free models first (no signup, no API key), ' +
|
|
@@ -6,7 +6,7 @@ export const DSH_SUPPORT_WINDOW = Object.freeze({
|
|
|
6
6
|
|
|
7
7
|
export const DSH_VERIFICATION_EVIDENCE = Object.freeze({
|
|
8
8
|
exactStable: '0.1.5-rc.3',
|
|
9
|
-
exactPreview: '0.2.0-rc.
|
|
9
|
+
exactPreview: '0.2.0-rc.2',
|
|
10
10
|
stableCanaryDistTag: 'latest',
|
|
11
11
|
nextCanaryDistTag: 'next',
|
|
12
12
|
previewCanaryDistTag: 'alpha',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
|
|
1
2
|
const CLIENT_PRELUDE_MARK = 'data-vision-router-guide-toggle-highlight'
|
|
2
3
|
|
|
3
4
|
export function unionGuideRects(first, second) {
|
|
@@ -149,18 +150,14 @@ export const GUIDE_VISION_TOGGLE_HIGHLIGHT_PRELUDE = String.raw`(function(){
|
|
|
149
150
|
})();`
|
|
150
151
|
|
|
151
152
|
export function injectGuideVisionToggleHighlight(html) {
|
|
152
|
-
|
|
153
|
-
const script = `<script ${CLIENT_PRELUDE_MARK}>${GUIDE_VISION_TOGGLE_HIGHLIGHT_PRELUDE.replace(/<\/script/gi, '<\\/script')}</script>`
|
|
154
|
-
const closeHead = html.indexOf('</head>')
|
|
155
|
-
if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
|
|
156
|
-
return `${html}${script}`
|
|
153
|
+
return injectClientCarrierHtml(html, CLIENT_PRELUDE_MARK, GUIDE_VISION_TOGGLE_HIGHLIGHT_PRELUDE)
|
|
157
154
|
}
|
|
158
155
|
|
|
159
156
|
export function installGuideVisionToggleHighlight(ctx) {
|
|
160
|
-
ctx
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
157
|
+
installClientScriptCarriers(ctx, {
|
|
158
|
+
marker: CLIENT_PRELUDE_MARK,
|
|
159
|
+
prelude: GUIDE_VISION_TOGGLE_HIGHLIGHT_PRELUDE,
|
|
160
|
+
label: 'vision-router: guide Vision toggle highlight',
|
|
161
|
+
injectHtml: injectGuideVisionToggleHighlight,
|
|
165
162
|
})
|
|
166
163
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
|
|
1
2
|
import { htmlHasScriptMarker } from './html-script-marker.js'
|
|
2
3
|
|
|
3
4
|
const CLIENT_PRELUDE_MARK = 'data-vision-router-live-models'
|
|
@@ -588,22 +589,14 @@ export const LIVE_MODEL_CLIENT_PRELUDE = String.raw`(function(){
|
|
|
588
589
|
})();`
|
|
589
590
|
|
|
590
591
|
export function injectLiveModelClientPrelude(html) {
|
|
591
|
-
|
|
592
|
-
const script = `<script ${CLIENT_PRELUDE_MARK}>${LIVE_MODEL_CLIENT_PRELUDE.replace(/<\/script/gi, '<\\/script')}</script>`
|
|
593
|
-
// DSH's client-modules index tap prepends its queue-mode facade immediately
|
|
594
|
-
// after <head>. Run after that parser bootstrap so we can wrap both load()
|
|
595
|
-
// and create(); the create wrapper then re-attaches us after rc.8 swaps in
|
|
596
|
-
// its live registration sink, before lazy third-party bundles arrive.
|
|
597
|
-
const closeHead = html.indexOf('</head>')
|
|
598
|
-
if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
|
|
599
|
-
return `${html}${script}`
|
|
592
|
+
return injectClientCarrierHtml(html, CLIENT_PRELUDE_MARK, LIVE_MODEL_CLIENT_PRELUDE)
|
|
600
593
|
}
|
|
601
594
|
|
|
602
595
|
export function installLiveModelClientPrelude(ctx) {
|
|
603
|
-
ctx
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
596
|
+
installClientScriptCarriers(ctx, {
|
|
597
|
+
marker: CLIENT_PRELUDE_MARK,
|
|
598
|
+
prelude: LIVE_MODEL_CLIENT_PRELUDE,
|
|
599
|
+
label: 'vision-router: live model client prelude',
|
|
600
|
+
injectHtml: injectLiveModelClientPrelude,
|
|
608
601
|
})
|
|
609
602
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
|
|
2
2
|
import { isLocalUiRequest } from './web-capability-boundary.js'
|
|
3
3
|
|
|
4
4
|
const SETTINGS_NS = 'vision-router'
|
|
@@ -437,10 +437,7 @@ export const LOCAL_PERMISSION_CLIENT_PRELUDE = String.raw`(function(){
|
|
|
437
437
|
})();`
|
|
438
438
|
|
|
439
439
|
export function injectLocalPermissionClientPrelude(html) {
|
|
440
|
-
|
|
441
|
-
const script = `<script ${LOCAL_PERMISSION_PRELUDE_MARK}>${LOCAL_PERMISSION_CLIENT_PRELUDE.replace(/<\/script/gi, '<\\/script')}</script>`
|
|
442
|
-
const endHead = html.indexOf('</head>')
|
|
443
|
-
return endHead === -1 ? `${html}${script}` : `${html.slice(0, endHead)}${script}${html.slice(endHead)}`
|
|
440
|
+
return injectClientCarrierHtml(html, LOCAL_PERMISSION_PRELUDE_MARK, LOCAL_PERMISSION_CLIENT_PRELUDE)
|
|
444
441
|
}
|
|
445
442
|
|
|
446
443
|
export function installLocalRemoteSettingsPermissionBridge(ctx, logger) {
|
|
@@ -456,9 +453,11 @@ export function installLocalRemoteSettingsPermissionBridge(ctx, logger) {
|
|
|
456
453
|
}),
|
|
457
454
|
'vision-router: loopback remote-settings permission mutation',
|
|
458
455
|
)
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
456
|
+
})
|
|
457
|
+
installClientScriptCarriers(ctx, {
|
|
458
|
+
marker: LOCAL_PERMISSION_PRELUDE_MARK,
|
|
459
|
+
prelude: LOCAL_PERMISSION_CLIENT_PRELUDE,
|
|
460
|
+
label: 'vision-router: local settings permission client shim',
|
|
461
|
+
injectHtml: injectLocalPermissionClientPrelude,
|
|
463
462
|
})
|
|
464
463
|
}
|
|
@@ -134,14 +134,26 @@ function disposeHeldRestriction(held) {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
function releaseAgentToolRestriction(agent, restrictions) {
|
|
137
|
+
function releaseAgentToolRestriction(agent, restrictions, activeRestrictions) {
|
|
138
138
|
if (!isObject(agent)) return
|
|
139
139
|
const held = restrictions.get(agent)
|
|
140
140
|
if (!held) return
|
|
141
141
|
disposeHeldRestriction(held)
|
|
142
|
+
activeRestrictions?.delete(held)
|
|
142
143
|
restrictions.delete(agent)
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
function rememberAgentToolRestriction(agent, restrictions, activeRestrictions, held) {
|
|
147
|
+
restrictions.set(agent, held)
|
|
148
|
+
activeRestrictions?.add(held)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function releaseAllAgentToolRestrictions(activeRestrictions) {
|
|
152
|
+
if (!(activeRestrictions instanceof Set)) return
|
|
153
|
+
for (const held of activeRestrictions) disposeHeldRestriction(held)
|
|
154
|
+
activeRestrictions.clear()
|
|
155
|
+
}
|
|
156
|
+
|
|
145
157
|
function restrictionFailureLabel(error) {
|
|
146
158
|
const name =
|
|
147
159
|
typeof error?.name === 'string' && error.name !== ''
|
|
@@ -165,10 +177,10 @@ function warnRestrictionFailure(agent, error, deny, failed = deny) {
|
|
|
165
177
|
} catch {}
|
|
166
178
|
}
|
|
167
179
|
|
|
168
|
-
function syncAgentToolRestriction(agent, enabled, restrictions, owned, registryTools) {
|
|
180
|
+
function syncAgentToolRestriction(agent, enabled, restrictions, owned, registryTools, activeRestrictions) {
|
|
169
181
|
if (!isObject(agent)) return
|
|
170
182
|
if (enabled) {
|
|
171
|
-
releaseAgentToolRestriction(agent, restrictions)
|
|
183
|
+
releaseAgentToolRestriction(agent, restrictions, activeRestrictions)
|
|
172
184
|
return
|
|
173
185
|
}
|
|
174
186
|
|
|
@@ -181,19 +193,19 @@ function syncAgentToolRestriction(agent, enabled, restrictions, owned, registryT
|
|
|
181
193
|
const deny = restrictableOwnedVisionToolNames(registryTools, owned)
|
|
182
194
|
if (typeof restrict !== 'function') return
|
|
183
195
|
if (deny.length === 0) {
|
|
184
|
-
releaseAgentToolRestriction(agent, restrictions)
|
|
196
|
+
releaseAgentToolRestriction(agent, restrictions, activeRestrictions)
|
|
185
197
|
return
|
|
186
198
|
}
|
|
187
199
|
|
|
188
200
|
const held = restrictions.get(agent)
|
|
189
201
|
const signature = deny.join('\u0000')
|
|
190
202
|
if (held?.signature === signature) return
|
|
191
|
-
releaseAgentToolRestriction(agent, restrictions)
|
|
203
|
+
releaseAgentToolRestriction(agent, restrictions, activeRestrictions)
|
|
192
204
|
|
|
193
205
|
try {
|
|
194
206
|
// Current DSH can mask all inherited plugin tools in one scoped operation.
|
|
195
207
|
const dispose = restrict.call(tools, { deny })
|
|
196
|
-
|
|
208
|
+
rememberAgentToolRestriction(agent, restrictions, activeRestrictions, {
|
|
197
209
|
signature,
|
|
198
210
|
disposers: typeof dispose === 'function' ? [dispose] : [],
|
|
199
211
|
})
|
|
@@ -212,7 +224,9 @@ function syncAgentToolRestriction(agent, enabled, restrictions, owned, registryT
|
|
|
212
224
|
failed.push(name)
|
|
213
225
|
}
|
|
214
226
|
}
|
|
215
|
-
if (disposers.length > 0)
|
|
227
|
+
if (disposers.length > 0) {
|
|
228
|
+
rememberAgentToolRestriction(agent, restrictions, activeRestrictions, { signature, disposers })
|
|
229
|
+
}
|
|
216
230
|
if (failed.length > 0 || disposers.length === 0) {
|
|
217
231
|
warnRestrictionFailure(agent, combinedError, deny, failed.length > 0 ? failed : deny)
|
|
218
232
|
}
|
|
@@ -311,6 +325,7 @@ function wrapTools(tools, ctx, config, owned) {
|
|
|
311
325
|
export function installSessionVisionModeBoundary(ctx, config = {}) {
|
|
312
326
|
if (!isObject(ctx)) return { ctx, config }
|
|
313
327
|
const restrictions = new WeakMap()
|
|
328
|
+
const activeRestrictions = new Set()
|
|
314
329
|
const assemblySnapshots = new WeakMap()
|
|
315
330
|
const ownedVisionTools = new Map()
|
|
316
331
|
let toolsView
|
|
@@ -335,6 +350,7 @@ export function installSessionVisionModeBoundary(ctx, config = {}) {
|
|
|
335
350
|
restrictions,
|
|
336
351
|
ownedVisionTools,
|
|
337
352
|
ctx?.tools,
|
|
353
|
+
activeRestrictions,
|
|
338
354
|
)
|
|
339
355
|
}
|
|
340
356
|
|
|
@@ -354,6 +370,7 @@ export function installSessionVisionModeBoundary(ctx, config = {}) {
|
|
|
354
370
|
restrictions,
|
|
355
371
|
ownedVisionTools,
|
|
356
372
|
ctx?.tools,
|
|
373
|
+
activeRestrictions,
|
|
357
374
|
)
|
|
358
375
|
}
|
|
359
376
|
return authority
|
|
@@ -419,13 +436,30 @@ export function installSessionVisionModeBoundary(ctx, config = {}) {
|
|
|
419
436
|
})
|
|
420
437
|
ctx.on?.('agent/disposed', ({ agent }) => {
|
|
421
438
|
clearAgent(agent)
|
|
422
|
-
releaseAgentToolRestriction(agent, restrictions)
|
|
439
|
+
releaseAgentToolRestriction(agent, restrictions, activeRestrictions)
|
|
423
440
|
})
|
|
424
441
|
} catch {
|
|
425
442
|
// Minimum Hosts may not expose every lifecycle event. Prompt assembly and
|
|
426
443
|
// execution guards preserve the safety boundary without making apply fail.
|
|
427
444
|
}
|
|
428
445
|
|
|
446
|
+
// tools.restrict() is invoked through the long-lived Agent scope so its
|
|
447
|
+
// disposer belongs to that scope, not to this plugin fiber. A Settings/HMR
|
|
448
|
+
// recompose can therefore retire this boundary while the Agent survives. If
|
|
449
|
+
// we do not release those masks here, the next boundary generation cannot
|
|
450
|
+
// discover or dispose the old WeakMap entries and Vision tools stay hidden
|
|
451
|
+
// even after the Session selects a Router-owned route again.
|
|
452
|
+
try {
|
|
453
|
+
if (typeof ctx.effect === 'function') {
|
|
454
|
+
ctx.effect(
|
|
455
|
+
() => () => releaseAllAgentToolRestrictions(activeRestrictions),
|
|
456
|
+
'vision-router: Session Vision restriction lifecycle',
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
} catch {
|
|
460
|
+
// Agent disposal / live enable transitions still release their own masks.
|
|
461
|
+
}
|
|
462
|
+
|
|
429
463
|
const wrapped = new Proxy(ctx, {
|
|
430
464
|
get(target, property) {
|
|
431
465
|
if (property === 'tools') {
|
|
@@ -467,6 +501,7 @@ export function installSessionVisionModeBoundary(ctx, config = {}) {
|
|
|
467
501
|
restrictions,
|
|
468
502
|
ownedVisionTools,
|
|
469
503
|
target?.tools,
|
|
504
|
+
activeRestrictions,
|
|
470
505
|
)
|
|
471
506
|
return runWithSessionVisionModeAuthority(
|
|
472
507
|
authority,
|