dsh-vision-router 2.2.5 → 2.2.7
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 +3 -3
- package/README.zh.md +3 -3
- package/docs/architecture/dsh-compatibility-matrix.md +4 -3
- package/docs/architecture/dsh-support-window.md +9 -2
- package/docs/architecture/host-first-proxy-convergence.md +1 -1
- package/docs/releases/v2.2.6.md +24 -0
- package/docs/releases/v2.2.7.md +22 -0
- package/index.js +3 -2
- package/lib/artifact-io.js +11 -3
- package/lib/client.js +5 -0
- package/lib/doctor-cli-p0.js +4 -1
- package/lib/doctor-runtime.js +12 -11
- package/lib/dsh-settings-017-compat.js +81 -24
- package/lib/dsh-support-window.js +5 -3
- package/lib/http-body-limit.js +5 -1
- package/lib/live-model-discovery.js +2 -2
- package/lib/local-vision-stabilizer.js +17 -4
- package/lib/settings-client-017-compat.js +4 -0
- package/lib/settings-client-rc8-lifecycle.js +4 -0
- package/lib/settings-ia-client-prelude.js +1 -1
- package/lib/tesseract-exec-compat.js +28 -6
- package/lib/vision-background-stop-store.js +2 -1
- package/lib/vision-capability-probe.js +2 -1
- package/lib/vision-exact-check-client.js +2 -2
- package/lib/vision-image-input-verdict.js +2 -1
- package/lib/web/remote-settings-client.js +4 -0
- package/package.json +4 -7
package/README.md
CHANGED
|
@@ -42,9 +42,9 @@
|
|
|
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 (v2.2.
|
|
45
|
+
> 📌 **Announcement (v2.2.5)**
|
|
46
46
|
>
|
|
47
|
-
> **v2.2.
|
|
47
|
+
> **v2.2.5:** Adds fail-closed **Local-only vision** and fixes the remaining DSH `0.1.7-rc.2` Windows Desktop multi-plugin `Loading plugins…` hang by isolating Vision Router’s WebServer registrar from other plugins. The public Host floor remains `0.1.0-rc.8`. [What’s new →](docs/releases/v2.2.5.md)
|
|
48
48
|
|
|
49
49
|
<p align="center">
|
|
50
50
|
<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" />
|
|
@@ -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`.
|
|
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.1` (`next`) 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
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
> **默认配置会让数据出网。** 使用云端视觉模型时,Vision Router 会将图片(或由它裁剪出的局部)、识图提示词及相关请求元数据发送给对应供应商。新安装默认开启 OVHcloud 匿名视觉兜底,因此“免费”和“免 Key”不等于离线。涉密、涉案、内部或其他受限材料请勿使用云端链路。详见[数据流向与严格纯本地配置](#数据流向与严格纯本地配置)。
|
|
43
43
|
|
|
44
44
|
> [!WARNING]
|
|
45
|
-
> 📌 **公告(v2.2.
|
|
45
|
+
> 📌 **公告(v2.2.5)**
|
|
46
46
|
>
|
|
47
|
-
> **v2.2.1
|
|
47
|
+
> **v2.2.5:新增 fail-closed 的「仅本地视觉」,并根修 DSH `0.1.7-rc.2` Windows Desktop 多插件环境下残留的 `Loading plugins…` 卡死——Vision Router 的 WebServer registrar 不再污染其他插件。公开最低 Host 仍保持 `0.1.0-rc.8`。** [查看完整更新 →](docs/releases/v2.2.5.md)
|
|
48
48
|
|
|
49
49
|
<p align="center">
|
|
50
50
|
<img src="assets/vision-demo.gif" width="640" alt="演示:粘贴图片,Agent 用 vision_ground / vision_crop / vision_pixel_diff 定位发送按钮并给出坐标" />
|
|
@@ -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
|
|
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.1`(`next`)源码、真实 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;其余工具无浏览器也能用。
|
|
@@ -11,9 +11,10 @@ The matrix is capability-based. Runtime code must feature-detect the seam it nee
|
|
|
11
11
|
| `minimum-contract` | `0.1.0-rc.6` | Historical compatibility-retention fixture. The name is legacy; DVR 2.1.x and later 2.x public support starts at rc.8. |
|
|
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
|
-
| `preview-contract`
|
|
14
|
+
| `preview-contract` | `0.1.7-rc.2` | Retained predecessor-preview regression fixture. |
|
|
15
|
+
| `DSH 0.2.0-rc.1 validation` | `0.2.0-rc.1` (`next`) | Current exact next/rc evidence; not a public preview-support claim. |
|
|
15
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. |
|
|
16
|
-
| `latest
|
|
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. |
|
|
17
18
|
|
|
18
19
|
Node 22 and Node 24 remain the general required runtime matrix. The Host contract jobs are additive; they do not replace the normal test matrix.
|
|
19
20
|
|
|
@@ -38,7 +39,7 @@ Node 22 and Node 24 remain the general required runtime matrix. The Host contrac
|
|
|
38
39
|
| Public entry boot | yes | yes | yes | packed plugin public entry import in each Host contract fixture. |
|
|
39
40
|
| Packaged tarball install | yes | yes | yes | each Host contract fixture packs the plugin then installs the tarball into an isolated Host package. |
|
|
40
41
|
|
|
41
|
-
Preview-only evidence: the `0.1.7-rc.2`
|
|
42
|
+
Preview-only evidence: the retained `0.1.7-rc.2` contract fixture additionally mounts the Host-owned `@deepseek-ai/dsh-compaction-image-offload` projection and proves that one offloaded image occurrence stays offloaded across live requests, `SessionStore.fork()`, JSONL process restart, and cold resume. The exact `0.2.0-rc.1` gate separately proves immutable source contracts, official peer evaluation, Host proxy authority, WebServer overlay ownership, real Host + Chromium Vision/mixed-attachment/Settings/bundle lifecycles, and Windows Node 22/24 Desktop plus the Node 24 multi-plugin adversary. Both are verification evidence only; neither expands the public support window.
|
|
42
43
|
|
|
43
44
|
## Compatibility inventory and exit criteria
|
|
44
45
|
|
|
@@ -17,6 +17,12 @@ DSH `0.1.5-rc.1` / `0.1.5-rc.2` remain admitted for existing installations; curr
|
|
|
17
17
|
|
|
18
18
|
No later support-floor increase is currently announced.
|
|
19
19
|
|
|
20
|
+
### DSH 0.2.x forward admission
|
|
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.1` commit `4878cdabd87d4041bdaff61d04c966883b9fd07a`, published on 2026-09-28. 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
|
+
|
|
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
|
+
|
|
20
26
|
## Verification evidence — not support policy
|
|
21
27
|
|
|
22
28
|
Compatibility evidence answers a different question: what exact upstream releases and moving channels have current CI proof? It must never be interpreted as a public support-floor change.
|
|
@@ -24,9 +30,10 @@ Compatibility evidence answers a different question: what exact upstream release
|
|
|
24
30
|
| Evidence role | DSH source | Meaning |
|
|
25
31
|
|---|---|---|
|
|
26
32
|
| Exact stable evidence | `0.1.5-rc.3` | Required Host/wire and real Host + Chromium coverage for the current stable release. |
|
|
27
|
-
| Exact
|
|
33
|
+
| Exact next/rc evidence | `0.2.0-rc.1` (`next`) | Required Host/wire/lifecycle/browser evidence. This is not a preview support promise. |
|
|
28
34
|
| Stable drift canary | npm dist-tag `latest` | Scheduled, dynamically resolved surveillance. A failure starts compatibility investigation; it does not rewrite support policy. |
|
|
29
|
-
|
|
|
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
|
+
| Alpha/pre-release drift canary | npm dist-tag `alpha` | Scheduled, dynamically resolved surveillance for the alpha channel. A failure does not rewrite support policy. |
|
|
30
37
|
|
|
31
38
|
The exact evidence values may move in a patch-level maintenance PR when CI proof advances. The public minimum may move only under the support-floor protocol below.
|
|
32
39
|
|
|
@@ -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
|
|
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.1`, 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,24 @@
|
|
|
1
|
+
# v2.2.6
|
|
2
|
+
|
|
3
|
+
DSH Vision Router 2.2.6 is a DSH 0.2 compatibility and runtime-hardening update. It admits the validated DSH 0.2 release train without raising the public Host floor, reduces Settings startup amplification, adds the LM Studio native chat route, and bounds Doctor probe responses while streaming.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- **DSH 0.2 release-train compatibility:** both official DSH peers now admit `>=0.2.0-rc.1 <0.3.0-0`. This covers validated `0.2.0-rc.1` and later 0.2.x prerelease/stable versions without requiring a peer edit for every tag, while every 0.3 prerelease/stable remains excluded until a separate compatibility review.
|
|
8
|
+
- **Permanent DSH `0.2.0-rc.1` gates:** exact-source Windows/macOS/Linux checks, real Host + Chromium lifecycle coverage, Windows Desktop Node 22/24 authenticated-request checks, Node 24 multi-plugin ordering, and stable/next/preview release-channel canaries are now part of CI.
|
|
9
|
+
- **Settings startup amplification root fix (#577):** the DSH 0.1.7 Settings compatibility layer reuses a stable shared service/cache/listener/mutation queue behind the per-caller facade instead of multiplying full-profile recomposition work across child contexts. Exact `0.1.7-rc.2` multi-plugin measurements dropped from 59 configuration calls to roughly 8–10 in the tested profile. This addresses the second amplification mechanism found while investigating #551; the reporter's field A/B remains open and is not treated as closed by this release.
|
|
10
|
+
- **LM Studio native route (#579):** native LM Studio requests use `/api/v1/chat`, and the plugin maps disabled reasoning to the native `reasoning: "off"` behavior instead of sending an OpenAI-compatible-only field.
|
|
11
|
+
- **Doctor response hardening (#580):** runtime probes enforce the response ceiling while streaming and cancel oversized responses instead of buffering beyond the diagnostic boundary.
|
|
12
|
+
- Keeps the public minimum supported DSH Host at `0.1.0-rc.8`; no settings migration is required.
|
|
13
|
+
|
|
14
|
+
## Validation
|
|
15
|
+
|
|
16
|
+
- Exact DSH `0.2.0-rc.1@4878cdabd87d4041bdaff61d04c966883b9fd07a` passes Ubuntu/macOS/Windows source contracts, official Host peer evaluation, proxy/overlay contracts, real Host + Chromium Vision/mixed-attachment/Settings lifecycles, Windows Desktop Node 22/24, and the Node 24 multi-plugin adversary.
|
|
17
|
+
- The retained DSH `0.1.7-rc.2` real-Host/browser regression gates remain green.
|
|
18
|
+
- A physical macOS run using the published npm runtime `@deepseek-ai/dsh@0.2.0-rc.1` passed plugin admission, authentication boundaries, Settings save/reload/readback, enabled Vision composer flow, mixed attachment paste/upload, and three consecutive Host restarts with persisted settings and no compatibility-disable/uncaught/fatal signatures.
|
|
19
|
+
- Main CI, DSH Contract, Adversarial Fuzz, routing parity, dependency review, preview browser smoke, native multimodal cold-resume, and the DSH release-channel canary passed on the final compatibility head.
|
|
20
|
+
- Local release-prep suite: `1507` passed, `1` intentional skip, `0` failed; the separate backend runtime-policy suite passed `9/9`.
|
|
21
|
+
|
|
22
|
+
## Upgrade
|
|
23
|
+
|
|
24
|
+
Users moving to DSH `0.2.0-rc.1` should upgrade to Vision Router 2.2.6 so the Host compatibility preflight accepts the plugin. Existing supported 0.1.x users can also upgrade in place. Issue #551 remains open for the reporter's original `0.1.7-rc.2` 18-plugin field A/B and is tracked independently from this release.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# v2.2.7
|
|
2
|
+
|
|
3
|
+
DSH Vision Router 2.2.7 is a reliability, Desktop compatibility, and security hotfix. It restores the official Desktop Settings/local-capability path, repairs Electron-local Tesseract execution, hardens artifact/cache publication under symlinked workspaces and overlapping writers, and picks up the upstream Undici 7.29.1 security release without changing the public DSH support floor.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- **Official Desktop origin fix (#589):** `dsh-app://app` is now recognized consistently as the exact DSH-owned local Desktop origin across Settings, Settings IA, local diagnostics, exact-check, screenshot, and 0.1.7 compatibility surfaces. Lookalike origins remain remote, and server-side local-only boundaries are unchanged.
|
|
8
|
+
- **Electron local OCR fix (#590):** the Tesseract `execFile` compatibility layer now synchronizes Node CJS/ESM builtin exports symmetrically during install, rollback, and teardown. This fixes Windows Electron cases where forced Tesseract received the literal `stdin` path or Auto OCR silently fell back to a vision model.
|
|
9
|
+
- **Artifact path identity fix (#594):** artifact containment and atomic writes still use canonical real paths, while successful publication returns the caller-visible workspace path. Symlinked workspaces such as macOS `/tmp -> /private/tmp` no longer make a valid artifact appear to escape its session workspace.
|
|
10
|
+
- **Collision-resistant cache publication (#594):** live-model, capability-profile, background-stop, and image-input-verdict caches now use unique atomic temporary paths. Independent writers in the same process and millisecond can no longer collide on one temp file and fail `rename()` with `ENOENT`.
|
|
11
|
+
- **Undici 7.29.1 security update (#593):** the runtime picks up the upstream 7.29.1 security release, including fixes around connection-option preservation, WebSocket failure handling, shared-cache safety, decompression bounds, retry/body settlement, and response framing. This dependency hardening does not imply every upstream advisory was reachable through Vision Router itself.
|
|
12
|
+
- **DSH 0.2 validation/cache hardening (#588):** canonical compatibility evidence now reflects the validated `0.2.0-rc.1` train, and DSH build-cache manifests include required `lib` outputs so incomplete archives fail closed instead of reaching runtime smoke with missing build artifacts.
|
|
13
|
+
|
|
14
|
+
## Validation
|
|
15
|
+
|
|
16
|
+
- PR #594 completed 75 repository checks with 0 failures, including Node 22/24, Windows/macOS/Linux, artifact-boundary 22/24, DSH minimum/current/preview contracts, exact-source, real Host + Chromium, Windows Desktop Host, routing parity, Host Sharp, hostile-input fuzz, dependency review, and CodeQL.
|
|
17
|
+
- Local final hotfix validation on the Undici 7.29.1 base: main suite `1520` passed, `1` intentional skip, `0` failed; backend runtime-policy suite passed `9/9`; targeted artifact/cache/live-model/capability/background lifecycle coverage passed `43/43`.
|
|
18
|
+
- The public minimum supported DSH Host remains `0.1.0-rc.8`; existing 2.2.x settings require no migration.
|
|
19
|
+
|
|
20
|
+
## Upgrade
|
|
21
|
+
|
|
22
|
+
2.2.6 users should upgrade to 2.2.7, especially when using the official Desktop app, local Tesseract OCR, symlinked/custom workspaces, or long-running installations that may overlap cache writes. No configuration changes are required.
|
package/index.js
CHANGED
|
@@ -1164,8 +1164,9 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
1164
1164
|
// Local backends (format=anthropic etc.) go through their own
|
|
1165
1165
|
// dispatcher; regular HTTP providers keep the plain OpenAI call.
|
|
1166
1166
|
// Both consume the same pre-built OpenAI content blocks (image_url
|
|
1167
|
-
// data URIs); callLocalBackend converts them for the Anthropic
|
|
1168
|
-
|
|
1167
|
+
// data URIs); callLocalBackend converts them for the Anthropic and
|
|
1168
|
+
// LM Studio native wires.
|
|
1169
|
+
text = await ((entry.provider.format === 'anthropic' || entry.provider.format === 'lmstudio')
|
|
1169
1170
|
? callLocalBackend(entry.provider, openAIMessages, {
|
|
1170
1171
|
maxTokens: entry.provider.maxTokens ?? 4096,
|
|
1171
1172
|
signal: options.signal,
|
package/lib/artifact-io.js
CHANGED
|
@@ -106,7 +106,8 @@ async function resolveArtifactTargetForRun(workspace, artifactsDir, relativePath
|
|
|
106
106
|
const lstatImpl = deps.lstat ?? lstat
|
|
107
107
|
|
|
108
108
|
throwIfVisionAborted()
|
|
109
|
-
const
|
|
109
|
+
const workspaceLexical = path.resolve(String(workspace ?? ''))
|
|
110
|
+
const workspaceReal = await realpathImpl(workspaceLexical)
|
|
110
111
|
throwIfVisionAborted()
|
|
111
112
|
const relativeBase = normalizeArtifactsDir(artifactsDir)
|
|
112
113
|
const requestedTarget = normalizeRelativeArtifactPath(relativePath)
|
|
@@ -114,6 +115,7 @@ async function resolveArtifactTargetForRun(workspace, artifactsDir, relativePath
|
|
|
114
115
|
? normalizeRelativeArtifactPath(path.join(ARTIFACT_RUNS_DIR, runId, requestedTarget))
|
|
115
116
|
: requestedTarget
|
|
116
117
|
const lexicalBase = path.resolve(workspaceReal, relativeBase)
|
|
118
|
+
const callerVisibleBase = path.resolve(workspaceLexical, relativeBase)
|
|
117
119
|
if (!isPathInside(workspaceReal, lexicalBase)) {
|
|
118
120
|
throw new Error('vision-router: artifactsDir must stay inside the session workspace')
|
|
119
121
|
}
|
|
@@ -135,6 +137,7 @@ async function resolveArtifactTargetForRun(workspace, artifactsDir, relativePath
|
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
const lexicalTarget = path.resolve(lexicalBase, relativeTarget)
|
|
140
|
+
const callerVisibleTarget = path.resolve(callerVisibleBase, relativeTarget)
|
|
138
141
|
if (!isPathInside(lexicalBase, lexicalTarget)) {
|
|
139
142
|
throw new Error('vision-router: artifact target must stay inside the artifacts directory')
|
|
140
143
|
}
|
|
@@ -186,7 +189,7 @@ async function resolveArtifactTargetForRun(workspace, artifactsDir, relativePath
|
|
|
186
189
|
if (existing?.isDirectory?.()) {
|
|
187
190
|
throw new Error('vision-router: artifact target is a directory')
|
|
188
191
|
}
|
|
189
|
-
return { target, parentReal, artifactsBaseReal, runsBaseReal, runBaseReal, existing, runId }
|
|
192
|
+
return { target, callerVisibleTarget, parentReal, artifactsBaseReal, runsBaseReal, runBaseReal, existing, runId }
|
|
190
193
|
}
|
|
191
194
|
|
|
192
195
|
export function resolveArtifactTarget(workspace, artifactsDir, relativePath, deps = {}) {
|
|
@@ -246,7 +249,12 @@ async function writeArtifactFileForRun(workspace, artifactsDir, relativePath, da
|
|
|
246
249
|
}
|
|
247
250
|
scheduleArtifactRetention(resolved.artifactsBaseReal, { protectRunId: resolved.runId })
|
|
248
251
|
}
|
|
249
|
-
return
|
|
252
|
+
// Keep canonical paths for containment and atomic I/O, but return the
|
|
253
|
+
// caller-visible lexical workspace path. On macOS /tmp resolves to
|
|
254
|
+
// /private/tmp (and real deployments may use symlinked workspaces);
|
|
255
|
+
// leaking the realpath makes a valid artifact appear outside the session
|
|
256
|
+
// workspace to downstream path/address code.
|
|
257
|
+
return resolved.callerVisibleTarget
|
|
250
258
|
} finally {
|
|
251
259
|
if (!published) {
|
|
252
260
|
try { await unlinkImpl(temp) } catch { /* best effort */ }
|
package/lib/client.js
CHANGED
|
@@ -1210,6 +1210,11 @@ window.__ModuleLoader__.load({
|
|
|
1210
1210
|
// become local merely because a stale Connection says true.
|
|
1211
1211
|
const location = locationLike ?? (typeof window !== 'undefined' ? window.location : undefined)
|
|
1212
1212
|
const hostname = typeof location?.hostname === 'string' ? location.hostname.toLowerCase().replace(/^\[|\]$/g, '') : ''
|
|
1213
|
+
const protocol = typeof location?.protocol === 'string' ? location.protocol.toLowerCase() : ''
|
|
1214
|
+
// The official Desktop shell uses dsh-app://app instead of an HTTP
|
|
1215
|
+
// loopback URL. Trust only that exact scheme/host pair: broad custom-
|
|
1216
|
+
// protocol trust would let an unrelated embedded page gain local UI.
|
|
1217
|
+
if (protocol === 'dsh-app:' && hostname === 'app') return false
|
|
1213
1218
|
if (hostname !== '') {
|
|
1214
1219
|
if (hostname === 'localhost' || hostname.endsWith('.localhost') || hostname === '::1') return false
|
|
1215
1220
|
if (/^127(?:\.\d{1,3}){3}$/.test(hostname)) return false
|
package/lib/doctor-cli-p0.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
formatDshHostCapability,
|
|
8
8
|
normalizeDshHostCapabilities,
|
|
9
9
|
} from './dsh-host-capabilities.js'
|
|
10
|
+
import { DOCTOR_RESPONSE_MAX_BYTES, readResponseJsonBounded } from './http-body-limit.js'
|
|
10
11
|
import {
|
|
11
12
|
DSH_SUPPORT_WINDOW,
|
|
12
13
|
DSH_VERIFICATION_EVIDENCE,
|
|
@@ -63,7 +64,9 @@ export async function probeDoctorHostCapabilities({ baseUrl, fetchImpl = globalT
|
|
|
63
64
|
capabilities: unknownSnapshot(),
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
|
-
const body = await response
|
|
67
|
+
const body = await readResponseJsonBounded(response, DOCTOR_RESPONSE_MAX_BYTES, {
|
|
68
|
+
label: 'Doctor Host capability response',
|
|
69
|
+
})
|
|
67
70
|
return {
|
|
68
71
|
ok: true,
|
|
69
72
|
source: 'live-runtime',
|
package/lib/doctor-runtime.js
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from 'node:fs'
|
|
|
3
3
|
import os, { homedir } from 'node:os'
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { CURRENT_VERSION } from './update-check.js'
|
|
6
|
+
import { DOCTOR_RESPONSE_MAX_BYTES, readResponseTextBounded } from './http-body-limit.js'
|
|
6
7
|
|
|
7
8
|
export const DEFAULT_RUNTIME_URL = 'http://127.0.0.1:3080'
|
|
8
9
|
const SAFE_RUNTIME_ROUTES = [
|
|
@@ -111,17 +112,17 @@ export async function probeRuntime({
|
|
|
111
112
|
signal: timeoutSignal(timeoutMs),
|
|
112
113
|
})
|
|
113
114
|
if (response.status === 200) {
|
|
114
|
-
const text =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
115
|
+
const text = await readResponseTextBounded(response, DOCTOR_RESPONSE_MAX_BYTES, {
|
|
116
|
+
label: 'Doctor runtime ownership response',
|
|
117
|
+
})
|
|
118
|
+
const body = text ? JSON.parse(text) : undefined
|
|
119
|
+
const expectedDirectory = path.resolve(dshHome, 'logs', 'vision-router')
|
|
120
|
+
const actualDirectory = typeof body?.directory === 'string' ? path.resolve(body.directory) : undefined
|
|
121
|
+
const candidates = [...new Set(applicableProfiles.filter((name) => typeof name === 'string'))]
|
|
122
|
+
if (body?.local === true && actualDirectory === expectedDirectory && candidates.length === 1 && candidates[0] === requestedProfile) {
|
|
123
|
+
ownership = { requestedProfile, verified: true, profile: requestedProfile, source: 'log-home+unique-profile' }
|
|
124
|
+
} else if (body?.local === true && actualDirectory === expectedDirectory) {
|
|
125
|
+
ownership = { requestedProfile, verified: false, reason: 'profile-ambiguous', candidateProfiles: candidates }
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
} catch {
|
|
@@ -110,6 +110,18 @@ function revisionFor(editor, namespace, current, inherited) {
|
|
|
110
110
|
return previous.revision
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
const CORDIS_ORIGINAL = Symbol.for('cordis.original')
|
|
114
|
+
|
|
115
|
+
function serviceIdentity(value) {
|
|
116
|
+
if (!value || (typeof value !== 'object' && typeof value !== 'function')) return value
|
|
117
|
+
try {
|
|
118
|
+
const original = value[CORDIS_ORIGINAL]
|
|
119
|
+
return original && (typeof original === 'object' || typeof original === 'function') ? original : value
|
|
120
|
+
} catch {
|
|
121
|
+
return value
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
113
125
|
function serviceOf(ctx, name) {
|
|
114
126
|
try {
|
|
115
127
|
const value = typeof ctx?.get === 'function' ? ctx.get(name) : undefined
|
|
@@ -176,17 +188,26 @@ function applyOperations(current, inherited, ops) {
|
|
|
176
188
|
return next
|
|
177
189
|
}
|
|
178
190
|
|
|
179
|
-
function createConfigEditorSettingsFacade(editor, entryConfig, namespace) {
|
|
180
|
-
const listeners =
|
|
191
|
+
function createConfigEditorSettingsFacade(editor, entryConfig, namespace, sharedState) {
|
|
192
|
+
const listeners = sharedState.listeners
|
|
181
193
|
|
|
182
|
-
|
|
194
|
+
// ConfigEditor.configuration() rebuilds the complete profile and, on 0.1.7,
|
|
195
|
+
// composes every entry through structuredClone(). Ordinary profile writes
|
|
196
|
+
// reload this plugin and therefore replace the facade; keep one immutable
|
|
197
|
+
// instance snapshot for runtime reads and refresh only at write boundaries.
|
|
198
|
+
function state(refresh = false) {
|
|
199
|
+
if (sharedState.stateLoaded && !refresh) return sharedState.cachedState
|
|
183
200
|
const row = configEditorRow(editor, namespace)
|
|
184
|
-
if (!row)
|
|
201
|
+
if (!row) {
|
|
202
|
+
sharedState.cachedState = undefined
|
|
203
|
+
sharedState.stateLoaded = true
|
|
204
|
+
return sharedState.cachedState
|
|
205
|
+
}
|
|
185
206
|
const current = objectLike(row.entry?.options?.config) ? row.entry.options.config : {}
|
|
186
207
|
const inherited = objectLike(row.inherited) ? row.inherited : {}
|
|
187
208
|
const base = mergeResolved(entryConfig, inherited)
|
|
188
209
|
const value = mergeResolved(entryConfig, current)
|
|
189
|
-
|
|
210
|
+
sharedState.cachedState = {
|
|
190
211
|
row,
|
|
191
212
|
descriptor: {
|
|
192
213
|
ns: namespace,
|
|
@@ -197,17 +218,25 @@ function createConfigEditorSettingsFacade(editor, entryConfig, namespace) {
|
|
|
197
218
|
applies: true,
|
|
198
219
|
},
|
|
199
220
|
}
|
|
221
|
+
sharedState.stateLoaded = true
|
|
222
|
+
return sharedState.cachedState
|
|
200
223
|
}
|
|
201
224
|
|
|
202
225
|
function notify(value) {
|
|
203
226
|
for (const listener of [...listeners]) {
|
|
204
|
-
try { listener(value, undefined) } catch {}
|
|
227
|
+
try { listener(clone(value), undefined) } catch {}
|
|
205
228
|
}
|
|
206
229
|
}
|
|
207
230
|
|
|
231
|
+
function enqueueMutation(task) {
|
|
232
|
+
const running = sharedState.mutationTail.then(task, task)
|
|
233
|
+
sharedState.mutationTail = running.catch(() => {})
|
|
234
|
+
return running
|
|
235
|
+
}
|
|
236
|
+
|
|
208
237
|
const scope = {
|
|
209
238
|
get() {
|
|
210
|
-
return state()?.descriptor.value ?? entryConfig
|
|
239
|
+
return clone(state()?.descriptor.value ?? entryConfig)
|
|
211
240
|
},
|
|
212
241
|
watch(listener) {
|
|
213
242
|
if (typeof listener !== 'function') return () => {}
|
|
@@ -234,23 +263,35 @@ function createConfigEditorSettingsFacade(editor, entryConfig, namespace) {
|
|
|
234
263
|
const current = state()
|
|
235
264
|
return current ? [clone(current.descriptor)] : []
|
|
236
265
|
},
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
266
|
+
mutate(namespaceName, operations, expectedRevision) {
|
|
267
|
+
let ops
|
|
268
|
+
try {
|
|
269
|
+
if (namespaceName !== namespace) throw new Error(`No configurable plugin entry ${JSON.stringify(namespaceName)}`)
|
|
270
|
+
if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0) {
|
|
271
|
+
throw admissionError('expectedRevision must be a non-negative safe integer')
|
|
272
|
+
}
|
|
273
|
+
// Validate and clone caller-owned values before this write waits behind
|
|
274
|
+
// another mutation, closing a queued-request time-of-check/use gap.
|
|
275
|
+
ops = validateOperations(operations)
|
|
276
|
+
} catch (error) {
|
|
277
|
+
return Promise.reject(error)
|
|
246
278
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
279
|
+
return enqueueMutation(async () => {
|
|
280
|
+
// Runtime reads use the facade snapshot. Writes deliberately cross the
|
|
281
|
+
// ConfigEditor boundary again so optimistic concurrency still observes
|
|
282
|
+
// changes that landed before a Host reload replaced this facade.
|
|
283
|
+
const before = state(true)
|
|
284
|
+
if (!before) throw new Error(`No configurable plugin entry ${JSON.stringify(namespace)}`)
|
|
285
|
+
if (expectedRevision !== before.descriptor.revision) {
|
|
286
|
+
throw settingsConflict(expectedRevision, before.descriptor.revision)
|
|
287
|
+
}
|
|
288
|
+
const entry = before.row.entry
|
|
289
|
+
await editor.edit(entry, (current, inherited) => applyOperations(current, inherited, ops))
|
|
290
|
+
const after = state(true)
|
|
291
|
+
if (!after) throw new Error('Vision Router configuration entry disappeared after the write')
|
|
292
|
+
notify(after.descriptor.value)
|
|
293
|
+
return clone(after.descriptor)
|
|
294
|
+
})
|
|
254
295
|
},
|
|
255
296
|
}
|
|
256
297
|
}
|
|
@@ -264,6 +305,7 @@ function createConfigEditorSettingsFacade(editor, entryConfig, namespace) {
|
|
|
264
305
|
function contextWithLazySettingsCompatibility(ctx, entryConfig, namespace) {
|
|
265
306
|
const wrappers = new WeakMap()
|
|
266
307
|
const facades = new WeakMap()
|
|
308
|
+
const sharedStates = new WeakMap()
|
|
267
309
|
let injectedEditor
|
|
268
310
|
|
|
269
311
|
try {
|
|
@@ -284,9 +326,24 @@ function contextWithLazySettingsCompatibility(ctx, entryConfig, namespace) {
|
|
|
284
326
|
|| !editor || typeof editor.edit !== 'function' || typeof editor.configuration !== 'function') {
|
|
285
327
|
return settings
|
|
286
328
|
}
|
|
329
|
+
const identity = serviceIdentity(editor)
|
|
330
|
+
let sharedState = sharedStates.get(identity)
|
|
331
|
+
if (!sharedState) {
|
|
332
|
+
sharedState = {
|
|
333
|
+
listeners: new Set(),
|
|
334
|
+
cachedState: undefined,
|
|
335
|
+
stateLoaded: false,
|
|
336
|
+
mutationTail: Promise.resolve(),
|
|
337
|
+
}
|
|
338
|
+
sharedStates.set(identity, sharedState)
|
|
339
|
+
}
|
|
287
340
|
let facade = facades.get(editor)
|
|
288
341
|
if (!facade) {
|
|
289
|
-
|
|
342
|
+
// Cordis may hand different traceable proxy views of the same service to
|
|
343
|
+
// different child contexts. Keep each facade bound to its caller-owned
|
|
344
|
+
// proxy for method ownership, but share the expensive immutable snapshot
|
|
345
|
+
// and write queue by the stable underlying service identity.
|
|
346
|
+
facade = createConfigEditorSettingsFacade(editor, entryConfig, namespace, sharedState)
|
|
290
347
|
facades.set(editor, facade)
|
|
291
348
|
}
|
|
292
349
|
return facade
|
|
@@ -6,8 +6,9 @@ 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.
|
|
9
|
+
exactPreview: '0.2.0-rc.1',
|
|
10
10
|
stableCanaryDistTag: 'latest',
|
|
11
|
+
nextCanaryDistTag: 'next',
|
|
11
12
|
previewCanaryDistTag: 'alpha',
|
|
12
13
|
})
|
|
13
14
|
|
|
@@ -48,9 +49,10 @@ export function formatDshSupportWindowLines(capabilities = {}) {
|
|
|
48
49
|
` support floor: DVR ${DSH_SUPPORT_WINDOW.dvrTrain} -> DSH ${DSH_SUPPORT_WINDOW.minimum}`,
|
|
49
50
|
'DSH compatibility verification evidence:',
|
|
50
51
|
` exact stable: ${DSH_VERIFICATION_EVIDENCE.exactStable}`,
|
|
51
|
-
` exact
|
|
52
|
+
` exact next/rc (not a support claim): ${DSH_VERIFICATION_EVIDENCE.exactPreview}`,
|
|
52
53
|
` scheduled stable canary: npm dist-tag ${DSH_VERIFICATION_EVIDENCE.stableCanaryDistTag}`,
|
|
53
|
-
` scheduled
|
|
54
|
+
` scheduled next/rc canary: npm dist-tag ${DSH_VERIFICATION_EVIDENCE.nextCanaryDistTag}`,
|
|
55
|
+
` scheduled alpha/pre-release canary: npm dist-tag ${DSH_VERIFICATION_EVIDENCE.previewCanaryDistTag}`,
|
|
54
56
|
` upgrade advice: ${advice.level} (${advice.code}) — ${advice.message}`,
|
|
55
57
|
])
|
|
56
58
|
}
|
package/lib/http-body-limit.js
CHANGED
|
@@ -3,6 +3,7 @@ const DEFAULT_FALLBACK_MAX_BYTES = 4 * 1024 * 1024
|
|
|
3
3
|
export const MODEL_RESPONSE_MAX_BYTES = 4 * 1024 * 1024
|
|
4
4
|
export const METADATA_RESPONSE_MAX_BYTES = 512 * 1024
|
|
5
5
|
export const ERROR_RESPONSE_MAX_BYTES = 64 * 1024
|
|
6
|
+
export const DOCTOR_RESPONSE_MAX_BYTES = 16 * 1024
|
|
6
7
|
|
|
7
8
|
function normalizedLimit(value) {
|
|
8
9
|
const number = Number(value)
|
|
@@ -34,7 +35,10 @@ export async function readResponseBytesBounded(response, maxBytes, options = {})
|
|
|
34
35
|
const limit = normalizedLimit(maxBytes)
|
|
35
36
|
const label = typeof options.label === 'string' && options.label !== '' ? options.label : 'HTTP response'
|
|
36
37
|
const declared = declaredLength(response)
|
|
37
|
-
if (declared !== undefined && declared > limit)
|
|
38
|
+
if (declared !== undefined && declared > limit) {
|
|
39
|
+
try { await response?.body?.cancel?.('response body limit exceeded') } catch { /* best effort */ }
|
|
40
|
+
throw responseTooLarge(label, limit, declared)
|
|
41
|
+
}
|
|
38
42
|
|
|
39
43
|
const body = response?.body
|
|
40
44
|
if (body && typeof body.getReader === 'function') {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto'
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
2
2
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import process from 'node:process'
|
|
@@ -151,7 +151,7 @@ async function loadCache(file, fsOps) {
|
|
|
151
151
|
|
|
152
152
|
async function saveCache(file, providers, fsOps) {
|
|
153
153
|
const directory = path.dirname(file)
|
|
154
|
-
const temporary = `${file}.${process.pid}.${Date.now()}.tmp`
|
|
154
|
+
const temporary = `${file}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`
|
|
155
155
|
await fsOps.mkdir(directory, { recursive: true })
|
|
156
156
|
const entries = [...providers.values()]
|
|
157
157
|
.filter((entry) => entry && entry.routeMismatch !== true)
|
|
@@ -63,15 +63,25 @@ export function installLocalVisionStabilizer(ctx, config = {}, core) {
|
|
|
63
63
|
const positive = (value, fallback) =>
|
|
64
64
|
Number.isFinite(value) && value > 0 ? Number(value) : fallback
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
let resolvedConfig = config
|
|
67
|
+
|
|
68
|
+
const captureConfig = (value, scope = rawScope) => {
|
|
67
69
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
let candidate = value
|
|
71
|
+
if ((!candidate || typeof candidate !== 'object') && scope && typeof scope.get === 'function') {
|
|
72
|
+
candidate = scope.get()
|
|
73
|
+
}
|
|
74
|
+
resolvedConfig = normalizeRuntimeVisionConfig(
|
|
75
|
+
candidate && typeof candidate === 'object' ? candidate : config,
|
|
76
|
+
)
|
|
70
77
|
} catch {
|
|
71
|
-
|
|
78
|
+
resolvedConfig = config
|
|
72
79
|
}
|
|
80
|
+
return resolvedConfig
|
|
73
81
|
}
|
|
74
82
|
|
|
83
|
+
const actualConfig = () => resolvedConfig
|
|
84
|
+
|
|
75
85
|
const localTaskBudget = (value = actualConfig()) =>
|
|
76
86
|
Math.max(
|
|
77
87
|
1000,
|
|
@@ -344,6 +354,7 @@ export function installLocalVisionStabilizer(ctx, config = {}, core) {
|
|
|
344
354
|
if (typeof watch !== 'function') return watch
|
|
345
355
|
return (listener) =>
|
|
346
356
|
watch.call(target, (...args) => {
|
|
357
|
+
if (rawScope === target) captureConfig(args[0], target)
|
|
347
358
|
syncScreenshot()
|
|
348
359
|
return listener(...args)
|
|
349
360
|
})
|
|
@@ -369,6 +380,7 @@ export function installLocalVisionStabilizer(ctx, config = {}, core) {
|
|
|
369
380
|
const scope = target.register(namespace, schema, fixedOptions)
|
|
370
381
|
if (namespace === 'vision-router') {
|
|
371
382
|
rawScope = scope
|
|
383
|
+
captureConfig(undefined, scope)
|
|
372
384
|
syncScreenshot()
|
|
373
385
|
// The Settings service is dynamic. Never keep its scope after the
|
|
374
386
|
// owning injection fiber unloads; during the gap fall back to the
|
|
@@ -378,6 +390,7 @@ export function installLocalVisionStabilizer(ctx, config = {}, core) {
|
|
|
378
390
|
() => () => {
|
|
379
391
|
if (rawScope !== scope) return
|
|
380
392
|
rawScope = undefined
|
|
393
|
+
resolvedConfig = config
|
|
381
394
|
syncScreenshot()
|
|
382
395
|
},
|
|
383
396
|
'vision-router: local settings scope lifecycle',
|
|
@@ -28,6 +28,10 @@ export const SETTINGS_017_CLIENT_PRELUDE = String.raw`(function(){
|
|
|
28
28
|
var hostname = locationLike && typeof locationLike.hostname === 'string'
|
|
29
29
|
? locationLike.hostname.toLowerCase().replace(/^\[|\]$/g, '')
|
|
30
30
|
: '';
|
|
31
|
+
var protocol = locationLike && typeof locationLike.protocol === 'string'
|
|
32
|
+
? locationLike.protocol.toLowerCase()
|
|
33
|
+
: '';
|
|
34
|
+
if (protocol === 'dsh-app:' && hostname === 'app') return true;
|
|
31
35
|
if (hostname === 'localhost' || hostname.endsWith('.localhost') || hostname === '::1') return true;
|
|
32
36
|
return /^127(?:\.\d{1,3}){3}$/.test(hostname);
|
|
33
37
|
}
|
|
@@ -35,6 +35,10 @@ export const SETTINGS_RC8_CLIENT_PRELUDE = String.raw`(function(){
|
|
|
35
35
|
var hostname = locationLike && typeof locationLike.hostname === 'string'
|
|
36
36
|
? locationLike.hostname.toLowerCase().replace(/^\[|\]$/g, '')
|
|
37
37
|
: '';
|
|
38
|
+
var protocol = locationLike && typeof locationLike.protocol === 'string'
|
|
39
|
+
? locationLike.protocol.toLowerCase()
|
|
40
|
+
: '';
|
|
41
|
+
if (protocol === 'dsh-app:' && hostname === 'app') return true;
|
|
38
42
|
if (hostname === 'localhost' || hostname.endsWith('.localhost') || hostname === '::1') return true;
|
|
39
43
|
return /^127(?:\.\d{1,3}){3}$/.test(hostname);
|
|
40
44
|
}
|
|
@@ -13,7 +13,7 @@ export const SETTINGS_IA_CLIENT_PRELUDE = String.raw`(function(){
|
|
|
13
13
|
function same(a,b){try{return JSON.stringify(a)===JSON.stringify(b);}catch(_){return false;}}
|
|
14
14
|
function bool(value){return value===true||value==='true';}
|
|
15
15
|
function rpcValue(body){if(body&&typeof body==='object'&&body.result&&typeof body.result==='object')return body.result.ok===true?body.result.value:undefined;return body;}
|
|
16
|
-
function localPage(){try{var host=String(
|
|
16
|
+
function localPage(){try{var location=window.location||{},host=String(location.hostname||'').toLowerCase(),protocol=String(location.protocol||'').toLowerCase();return(protocol==='dsh-app:'&&host==='app')||host==='localhost'||host.endsWith('.localhost')||host==='::1'||host==='[::1]'||/^127(?:\.\d{1,3}){3}$/.test(host);}catch(_){return false;}}
|
|
17
17
|
function builtinRow(row){return !!row&&row.provider==='vision-http'&&typeof row.model==='string'&&row.model.indexOf('ovh/')===0;}
|
|
18
18
|
function cloneRow(row,model){return {provider:String(row&&row.provider||''),model:String(model===undefined?(row&&row.model||''):model),fallbacks:[]};}
|
|
19
19
|
function splitChain(value){var builtins=[],users=[];function push(row,model){var next=cloneRow(row,model);(builtinRow(next)?builtins:users).push(next);}arr(value).forEach(function(row){if(!row||typeof row!=='object')return;push(row,row.model);arr(row.fallbacks).forEach(function(fallback){if(typeof fallback==='string'&&fallback.trim())push(row,fallback.trim());});});return {builtins:builtins,users:users};}
|
|
@@ -187,8 +187,12 @@ function callbackForwarder(execFileImpl, patchedCustom) {
|
|
|
187
187
|
return wrapped
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
function
|
|
191
|
-
|
|
190
|
+
function builtinExportSynchronizer(moduleObject, options) {
|
|
191
|
+
const builtinModule = options.builtinChildProcessModule ?? childProcess
|
|
192
|
+
const synchronize = options.syncBuiltinESMExports ?? syncBuiltinESMExports
|
|
193
|
+
return () => {
|
|
194
|
+
if (moduleObject === builtinModule) synchronize()
|
|
195
|
+
}
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
function warnInstallDegraded(ctx, reason) {
|
|
@@ -217,13 +221,26 @@ export function installVisionRouterExecFileCompat(ctx, options = {}) {
|
|
|
217
221
|
const execFileImpl = moduleObject.execFile
|
|
218
222
|
let state = installStates.get(execFileImpl)
|
|
219
223
|
if (state === undefined) {
|
|
224
|
+
const synchronizeBuiltinExports = builtinExportSynchronizer(moduleObject, options)
|
|
220
225
|
const originalCustom = execFileImpl[promisify.custom]
|
|
221
226
|
const patchedCustom = createVisionRouterExecFilePromisifyCompat(execFileImpl, originalCustom, options)
|
|
222
227
|
|
|
223
228
|
if (canReplaceCustomPromisify(execFileImpl)) {
|
|
224
229
|
try {
|
|
225
230
|
setCustomPromisify(execFileImpl, patchedCustom)
|
|
231
|
+
// Electron can expose distinct CJS and ESM execFile function objects.
|
|
232
|
+
// Synchronize after mutating the CJS custom hook so existing ESM named
|
|
233
|
+
// imports observe the same promisify.custom compatibility function.
|
|
234
|
+
synchronizeBuiltinExports()
|
|
226
235
|
} catch (error) {
|
|
236
|
+
try {
|
|
237
|
+
if (execFileImpl[promisify.custom] === patchedCustom) {
|
|
238
|
+
restoreCustomPromisify(execFileImpl, originalCustom)
|
|
239
|
+
synchronizeBuiltinExports()
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
/* best effort rollback; the CJS hook is restored before re-sync */
|
|
243
|
+
}
|
|
227
244
|
patchedCustom.deactivate?.()
|
|
228
245
|
warnInstallDegraded(ctx, error && error.message ? error.message : String(error))
|
|
229
246
|
return () => {}
|
|
@@ -236,6 +253,7 @@ export function installVisionRouterExecFileCompat(ctx, options = {}) {
|
|
|
236
253
|
exposedExecFile: execFileImpl,
|
|
237
254
|
originalCustom,
|
|
238
255
|
patchedCustom,
|
|
256
|
+
synchronizeBuiltinExports,
|
|
239
257
|
}
|
|
240
258
|
installStates.set(execFileImpl, state)
|
|
241
259
|
} else {
|
|
@@ -243,12 +261,12 @@ export function installVisionRouterExecFileCompat(ctx, options = {}) {
|
|
|
243
261
|
try {
|
|
244
262
|
moduleObject.execFile = exposedExecFile
|
|
245
263
|
if (moduleObject.execFile !== exposedExecFile) throw new Error('child_process.execFile export is not writable')
|
|
246
|
-
|
|
264
|
+
synchronizeBuiltinExports()
|
|
247
265
|
} catch (error) {
|
|
248
266
|
try {
|
|
249
267
|
if (moduleObject.execFile === exposedExecFile) {
|
|
250
268
|
moduleObject.execFile = execFileImpl
|
|
251
|
-
|
|
269
|
+
synchronizeBuiltinExports()
|
|
252
270
|
}
|
|
253
271
|
} catch {
|
|
254
272
|
/* best effort rollback */
|
|
@@ -265,6 +283,7 @@ export function installVisionRouterExecFileCompat(ctx, options = {}) {
|
|
|
265
283
|
exposedExecFile,
|
|
266
284
|
originalCustom,
|
|
267
285
|
patchedCustom,
|
|
286
|
+
synchronizeBuiltinExports,
|
|
268
287
|
}
|
|
269
288
|
installStates.set(execFileImpl, state)
|
|
270
289
|
installStates.set(exposedExecFile, state)
|
|
@@ -284,7 +303,10 @@ export function installVisionRouterExecFileCompat(ctx, options = {}) {
|
|
|
284
303
|
|
|
285
304
|
if (state.mode === 'custom-property') {
|
|
286
305
|
if (stillOwnsCustom) {
|
|
287
|
-
try {
|
|
306
|
+
try {
|
|
307
|
+
restoreCustomPromisify(state.execFileImpl, state.originalCustom)
|
|
308
|
+
state.synchronizeBuiltinExports()
|
|
309
|
+
} catch { /* best effort */ }
|
|
288
310
|
}
|
|
289
311
|
} else if (
|
|
290
312
|
state.mode === 'module-wrapper' &&
|
|
@@ -293,7 +315,7 @@ export function installVisionRouterExecFileCompat(ctx, options = {}) {
|
|
|
293
315
|
) {
|
|
294
316
|
try {
|
|
295
317
|
state.moduleObject.execFile = state.execFileImpl
|
|
296
|
-
|
|
318
|
+
state.synchronizeBuiltinExports()
|
|
297
319
|
} catch {
|
|
298
320
|
/* a later host/plugin mutation stays authoritative */
|
|
299
321
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
1
2
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import process from 'node:process'
|
|
@@ -88,7 +89,7 @@ async function load(file, fsOps, at) {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
async function save(file, records, fsOps, at) {
|
|
91
|
-
const temporary = `${file}.${process.pid}.${Date.now()}.tmp`
|
|
92
|
+
const temporary = `${file}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`
|
|
92
93
|
const stops = selectRetainedStops(records.values(), at)
|
|
93
94
|
await fsOps.mkdir(path.dirname(file), { recursive: true })
|
|
94
95
|
await fsOps.writeFile(temporary, JSON.stringify({ version: CACHE_VERSION, stops }), { encoding: 'utf8', mode: 0o600 })
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
1
2
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import process from 'node:process'
|
|
@@ -374,7 +375,7 @@ async function loadCache(file, fsOps, maxEntries) {
|
|
|
374
375
|
|
|
375
376
|
async function saveCache(file, records, fsOps, maxEntries) {
|
|
376
377
|
const directory = path.dirname(file)
|
|
377
|
-
const temporary = `${file}.${process.pid}.${Date.now()}.tmp`
|
|
378
|
+
const temporary = `${file}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`
|
|
378
379
|
const profiles = selectRetainedCapabilityProfiles(records.values(), maxEntries)
|
|
379
380
|
await fsOps.mkdir(directory, { recursive: true })
|
|
380
381
|
await fsOps.writeFile(temporary, JSON.stringify(cacheEnvelope(profiles)), { encoding: 'utf8', mode: 0o600 })
|
|
@@ -19,8 +19,8 @@ export const VISION_EXACT_CHECK_CLIENT = String.raw`(function(){
|
|
|
19
19
|
function text(cn,en){ return zh() ? cn : en; }
|
|
20
20
|
function localPage(){
|
|
21
21
|
try {
|
|
22
|
-
var host=String(
|
|
23
|
-
return host==='localhost'||host.endsWith('.localhost')||host==='::1'||host==='[::1]'||/^127(?:\.\d{1,3}){3}$/.test(host);
|
|
22
|
+
var location=window.location||{},host=String(location.hostname||'').toLowerCase(),protocol=String(location.protocol||'').toLowerCase();
|
|
23
|
+
return (protocol==='dsh-app:'&&host==='app')||host==='localhost'||host.endsWith('.localhost')||host==='::1'||host==='[::1]'||/^127(?:\.\d{1,3}){3}$/.test(host);
|
|
24
24
|
} catch (_) { return false; }
|
|
25
25
|
}
|
|
26
26
|
function rowSelection(row){
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
1
2
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import process from 'node:process'
|
|
@@ -69,7 +70,7 @@ async function load(file, fsOps) {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
async function save(file, records, fsOps) {
|
|
72
|
-
const temporary = `${file}.${process.pid}.${Date.now()}.tmp`
|
|
73
|
+
const temporary = `${file}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`
|
|
73
74
|
const verdicts = selectRetainedVerdicts(records.values())
|
|
74
75
|
await fsOps.mkdir(path.dirname(file), { recursive: true })
|
|
75
76
|
await fsOps.writeFile(temporary, JSON.stringify({ version: CACHE_VERSION, verdicts }), { encoding: 'utf8', mode: 0o600 })
|
|
@@ -34,6 +34,10 @@ export const SETTINGS_CONFIG_FORMS_CLIENT_PRELUDE = String.raw`(function(){
|
|
|
34
34
|
var hostname = locationLike && typeof locationLike.hostname === 'string'
|
|
35
35
|
? locationLike.hostname.toLowerCase().replace(/^\[|\]$/g, '')
|
|
36
36
|
: '';
|
|
37
|
+
var protocol = locationLike && typeof locationLike.protocol === 'string'
|
|
38
|
+
? locationLike.protocol.toLowerCase()
|
|
39
|
+
: '';
|
|
40
|
+
if (protocol === 'dsh-app:' && hostname === 'app') return true;
|
|
37
41
|
if (hostname === 'localhost' || hostname.endsWith('.localhost') || hostname === '::1') return true;
|
|
38
42
|
return /^127(?:\.\d{1,3}){3}$/.test(hostname);
|
|
39
43
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vision-router",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.7",
|
|
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": {
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"undici": "^7.29.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@deepseek-ai/dsh-anonymous-user-id": "^0.1.0-rc.8 || ^0.1.1-rc.1 || ^0.1.3-alpha.2 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.6-alpha.1 || 0.1.7-rc.2",
|
|
58
|
-
"@deepseek-ai/dsh-llm-deepseek": "^0.1.0-rc.8 || ^0.1.1-rc.1 || ^0.1.3-alpha.2 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.6-alpha.1 || 0.1.7-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-anonymous-user-id": "^0.1.0-rc.8 || ^0.1.1-rc.1 || ^0.1.3-alpha.2 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.6-alpha.1 || 0.1.7-rc.2 || >=0.2.0-rc.1 <0.3.0-0",
|
|
58
|
+
"@deepseek-ai/dsh-llm-deepseek": "^0.1.0-rc.8 || ^0.1.1-rc.1 || ^0.1.3-alpha.2 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.6-alpha.1 || 0.1.7-rc.2 || >=0.2.0-rc.1 <0.3.0-0",
|
|
59
59
|
"sharp": ">=0.35.3 <1"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"sharp": "^0.35.4"
|
|
71
71
|
},
|
|
72
72
|
"scripts": {
|
|
73
|
-
"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/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/rc6-real-settings-persistence.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/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/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/repetition-guard.test.js tests/guide-scroll-gate.test.js tests/settings-scroll-jank-regression.test.js tests/android-attachment-compat.test.js tests/free-cloud-first.test.js tests/rc6-rc7-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/pi-ai-bridge-wire-compat.test.js tests/fetch-wrapper-lifecycle.test.js tests/fetch-wrapper-composition.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/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 && node --test tests/vision-backend-runtime-policy.test.js",
|
|
73
|
+
"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/rc6-real-settings-persistence.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/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/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/repetition-guard.test.js tests/guide-scroll-gate.test.js tests/settings-scroll-jank-regression.test.js tests/android-attachment-compat.test.js tests/free-cloud-first.test.js tests/rc6-rc7-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/pi-ai-bridge-wire-compat.test.js tests/fetch-wrapper-lifecycle.test.js tests/fetch-wrapper-composition.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/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 && node --test tests/vision-backend-runtime-policy.test.js",
|
|
74
74
|
"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",
|
|
75
75
|
"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",
|
|
76
76
|
"test:session": "node --test tests/session-vision-state.test.js tests/session-vision-state-integration.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",
|
|
@@ -88,9 +88,6 @@
|
|
|
88
88
|
"quality:round2:run": "node quality/vision-round2/run-host-api.mjs",
|
|
89
89
|
"quality:round2:report": "node quality/vision-round2/report-results.mjs"
|
|
90
90
|
},
|
|
91
|
-
"pnpm": {
|
|
92
|
-
"onlyBuiltDependencies": ["sharp"]
|
|
93
|
-
},
|
|
94
91
|
"dsh": {
|
|
95
92
|
"client": {
|
|
96
93
|
"platform": "web",
|