dsh-vision-router 1.7.1 → 1.7.3
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/cordis.patch.yml +23 -10
- package/entry.js +46 -19
- package/lib/client-presentation-boundary.js +271 -0
- package/lib/dsh-contract-compat.js +125 -26
- package/lib/live-model-client-prelude.js +49 -24
- package/package.json +2 -2
package/cordis.patch.yml
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
# without any manual cordis.patch.yml edits. Later layers (the profile's own
|
|
5
5
|
# cordis.patch.yml, --patch overlays) override these rows by id.
|
|
6
6
|
|
|
7
|
-
# 纯增量补丁(issue #34
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# 纯增量补丁(issue #34):不碰核心行。最低支持 Host 仍保留旧兼容路径;
|
|
8
|
+
# batch-attachment 合同的 Host 始终拥有 deepseek-official,插件只提供
|
|
9
|
+
# 「+ 自动识图」包装路由,因此这里永远不默认禁用官方 llm-deepseek 行。
|
|
10
10
|
|
|
11
11
|
# 挂载插件行。默认保持完整视觉工具表常驻(issue #81):虽然渐进挂载可以
|
|
12
12
|
# 少发一小段工具 schema,但图片轮首次扩展工具列表会改变请求前缀,可能让
|
|
@@ -18,14 +18,27 @@
|
|
|
18
18
|
config:
|
|
19
19
|
progressiveTools: false
|
|
20
20
|
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
21
|
+
# Vision Router 的附件产品契约:Host attachment store 负责保存用户提交的
|
|
22
|
+
# durable 原件,具体视觉后端负责本次推理所需的 resize/tile/request budget。
|
|
23
|
+
# 因此存储准入保持 20MiB / 1 亿像素,同时在 DSH rc.8 新增的单边限制上
|
|
24
|
+
# 显式选择 10000px:覆盖 4K/手机原图和常见长截图,但不把 Host 的全局
|
|
25
|
+
# 极端长边安全闸无限放开。该值作用于整个 attachment-local(包括原生视觉
|
|
26
|
+
# provider),所以这里刻意不用 32768/无限;更大的部署需求仍可由后续
|
|
27
|
+
# profile/--patch 层按实际 provider 能力覆盖。
|
|
28
|
+
#
|
|
29
|
+
# DSH 的 patch 是“整段 config 替换”而非深度合并。旧版 Vision Router 曾在
|
|
30
|
+
# profile 自己的 cordis.patch.yml 写入同一行的 20MiB/100MP 两个字段;这层
|
|
31
|
+
# 比 bundle 晚,会把这里新加的 maxImageDimension 擦掉,让 rc.8 又落回 2000。
|
|
32
|
+
# 1.7.2 起运行时只对“20MiB + 100MP + rc.8 默认 2000px”这一历史指纹做一次
|
|
33
|
+
# 内存态迁移到 10000px;显式自定义过 dimension 或其他附件策略一律不碰。
|
|
34
|
+
# 这样升级旧 profile 不要求用户手工清理配置,同时后续 profile/--patch 的
|
|
35
|
+
# 明确部署策略仍保持最终所有权。
|
|
36
|
+
#
|
|
37
|
+
# rc.6/rc.7 没有 maxImageDimension;发布合同 CI 会用真实旧包验证额外配置键
|
|
38
|
+
# 不会破坏最低支持 Host。若旧 Host 开始严格拒绝未知键,此处必须改为按能力
|
|
39
|
+
# 生成配置,而不是靠版本字符串分支。
|
|
28
40
|
- id: attachment-local
|
|
29
41
|
config:
|
|
30
42
|
maxImageBytes: 20971520
|
|
31
43
|
maxImagePixels: 100000000
|
|
44
|
+
maxImageDimension: 10000
|
package/entry.js
CHANGED
|
@@ -15,6 +15,7 @@ import { contextWithVisionExecutionPolicy } from './lib/vision-execution-policy.
|
|
|
15
15
|
import { installLiveModelDiscovery } from './lib/live-model-discovery.js'
|
|
16
16
|
import { installVisionModelRegistry } from './lib/vision-model-registry.js'
|
|
17
17
|
import { installLiveModelClientPrelude } from './lib/live-model-client-prelude.js'
|
|
18
|
+
import { installClientPresentationBoundary } from './lib/client-presentation-boundary.js'
|
|
18
19
|
import { installAdversarialHardening } from './lib/adversarial-hardening.js'
|
|
19
20
|
import { installOllamaColdStartGuard } from './lib/ollama-cold-start.js'
|
|
20
21
|
import { installLocalVisionStabilizer } from './lib/local-vision-stabilizer.js'
|
|
@@ -31,9 +32,10 @@ import {
|
|
|
31
32
|
} from './lib/structured-flow-hardening.js'
|
|
32
33
|
import {
|
|
33
34
|
attachmentContextForContract,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
ensureVisionAttachmentAdmissionPolicy,
|
|
36
|
+
hasBatchAttachmentContract,
|
|
37
|
+
installHostSettingsCompatibility,
|
|
38
|
+
protectHostProviderOwnership,
|
|
37
39
|
} from './lib/dsh-contract-compat.js'
|
|
38
40
|
|
|
39
41
|
// Increment whenever the browser-visible settings contract gains a field whose
|
|
@@ -56,7 +58,7 @@ core.Config.set('visionTurnBudgetMs', z.number().step(1000).min(10000).max(60000
|
|
|
56
58
|
// Both visible entry points — Settings > Vision Router and the legacy
|
|
57
59
|
// Settings > Plugins compatibility card — edit the same Host-owned namespace.
|
|
58
60
|
// Keep the depth enum and custom cap on this final public contract so either
|
|
59
|
-
// entry serializes exactly the same shape
|
|
61
|
+
// entry serializes exactly the same shape on every supported Host generation.
|
|
60
62
|
core.Config.set('visionDepth', z.union(['fast', 'standard', 'deep', 'custom']).default('standard'))
|
|
61
63
|
core.Config.set('visionDepthMaxCalls', z.number().step(1).min(0).max(100).default(0))
|
|
62
64
|
|
|
@@ -79,6 +81,12 @@ core.Config.set(
|
|
|
79
81
|
export * from './index.js'
|
|
80
82
|
export {
|
|
81
83
|
attachmentContextForContract,
|
|
84
|
+
ensureVisionAttachmentAdmissionPolicy,
|
|
85
|
+
hasBatchAttachmentContract,
|
|
86
|
+
installHostSettingsCompatibility,
|
|
87
|
+
protectHostProviderOwnership,
|
|
88
|
+
// Transitional public aliases retained for callers/tests written during the
|
|
89
|
+
// rc.7 compatibility pass. Runtime code below no longer branches on names.
|
|
82
90
|
installRc7SettingsCompatibility,
|
|
83
91
|
isRc7ContractRuntime,
|
|
84
92
|
protectRc7ProviderOwnership,
|
|
@@ -106,7 +114,7 @@ export function apply(ctx, config = {}) {
|
|
|
106
114
|
: 'deepseek-vision',
|
|
107
115
|
visionConfig: config,
|
|
108
116
|
})
|
|
109
|
-
//
|
|
117
|
+
// Newer pi-ai replay envelopes store the real producer under
|
|
110
118
|
// replayState.response.{provider,model}; the older delegated-replay shim
|
|
111
119
|
// recognizes the pre-v2 top-level shape. Layer a narrow private compatibility
|
|
112
120
|
// view so resumed wrapper history keeps provider-native replay metadata rather
|
|
@@ -150,21 +158,35 @@ export function apply(ctx, config = {}) {
|
|
|
150
158
|
? Number(bootConfig.visionTurnBudgetMs)
|
|
151
159
|
: 90000,
|
|
152
160
|
}
|
|
153
|
-
|
|
161
|
+
// The batch-attachment API is the released, non-incidental discriminator
|
|
162
|
+
// between the minimum Host contract and the newer Host-owned integration
|
|
163
|
+
// generation. Keep the branch named after that observable capability rather
|
|
164
|
+
// than a release number so rc.8+ naturally follows the same public contract.
|
|
165
|
+
const batchAttachmentHost = hasBatchAttachmentContract(stabilizedCtx)
|
|
166
|
+
// DSH profile overlays replace an attachment-local config object wholesale.
|
|
167
|
+
// A stale pre-rc.8 Vision Router profile row can therefore erase the newer
|
|
168
|
+
// bundle's maxImageDimension and silently restore rc.8's 2000px default even
|
|
169
|
+
// after the plugin package itself has updated. Repair only that historical
|
|
170
|
+
// 20MiB/100MP fingerprint; explicit deployment policies remain authoritative.
|
|
171
|
+
if (batchAttachmentHost) {
|
|
172
|
+
ensureVisionAttachmentAdmissionPolicy(stabilizedCtx, logging.logger)
|
|
173
|
+
}
|
|
154
174
|
// The remote settings bridge uses DSH Connection's trusted-host carrier
|
|
155
175
|
// fence and its own safe-field capability allow-list. Main's local Web
|
|
156
176
|
// mutation boundary continues to protect the independent /_dsh write routes.
|
|
157
177
|
installVisionRouterRemoteSettingsBridge(stabilizedCtx, logging.logger)
|
|
158
|
-
const ownershipCtx =
|
|
159
|
-
|
|
160
|
-
|
|
178
|
+
const ownershipCtx = batchAttachmentHost
|
|
179
|
+
? protectHostProviderOwnership(stabilizedCtx)
|
|
180
|
+
: stabilizedCtx
|
|
181
|
+
const settingsCtx = batchAttachmentHost
|
|
182
|
+
? installHostSettingsCompatibility(ownershipCtx, { ...runtimeConfig, stealth: false }, {
|
|
161
183
|
namespace: 'vision-router',
|
|
162
184
|
Config: core.Config,
|
|
163
185
|
})
|
|
164
186
|
: ownershipCtx
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
// store-owned, so never
|
|
187
|
+
// The minimum Host keeps the narrow process-local fallback required by the
|
|
188
|
+
// old attachment-local durability walk. Batch-capable hosts keep AttachmentId
|
|
189
|
+
// store-owned, so never fabricate one there: host persistence errors remain
|
|
168
190
|
// authoritative and diagnosable instead of creating a false durable ref.
|
|
169
191
|
const attachmentCompatCtx = attachmentContextForContract(settingsCtx, logging.logger, {
|
|
170
192
|
installAndroidAttachmentCompat,
|
|
@@ -174,7 +196,7 @@ export function apply(ctx, config = {}) {
|
|
|
174
196
|
// one-shot, enforces fast/standard/deep/custom quotas, tracks mixed branches,
|
|
175
197
|
// rejects empty/non-evidence results, and applies one shared visual deadline.
|
|
176
198
|
const structuredCtx = installStructuredFlowHardening(attachmentCompatCtx, runtimeConfig)
|
|
177
|
-
// DSH
|
|
199
|
+
// Newer DSH releases publish llm/adapters-updated synchronously from inside
|
|
178
200
|
// registerAdapter(). Coalesce only Vision Router's listener: nested events
|
|
179
201
|
// mark the topology dirty and the outer pass reruns to a fixed point, so we
|
|
180
202
|
// neither double-register a twin nor lose a provider added mid-pass.
|
|
@@ -196,6 +218,12 @@ export function apply(ctx, config = {}) {
|
|
|
196
218
|
// source strictly for diagnostics (`known` vs `live`) without changing the
|
|
197
219
|
// admission decision.
|
|
198
220
|
installVisionModelRegistry(reconciledCtx, liveDiscovery, { config: runtimeConfig })
|
|
221
|
+
// rc.8 turns ui-attachment into a dynamic presentation plugin and no longer
|
|
222
|
+
// exports its React implementation as package values. Install a narrowly
|
|
223
|
+
// scoped browser boundary that supplies Vision Router's own lightweight
|
|
224
|
+
// gallery to the legacy 1.7.x client factory, so the official package is
|
|
225
|
+
// never value-required at runtime and remains free to evolve independently.
|
|
226
|
+
installClientPresentationBoundary(reconciledCtx)
|
|
199
227
|
// Keep endpoint-discovered ids private to Vision Router's settings client:
|
|
200
228
|
// the prelude wraps this package's browser context rather than changing the
|
|
201
229
|
// global llm.models response (which would expose UNKNOWN_MODEL entries in the
|
|
@@ -229,7 +257,7 @@ export function apply(ctx, config = {}) {
|
|
|
229
257
|
const lms = c.localLmStudio && typeof c.localLmStudio === 'object' ? c.localLmStudio : {}
|
|
230
258
|
logging.logger.info(
|
|
231
259
|
'vision-router: base config summary — contract=%s instantDescribe=%s localDescribeStyle=%s localOllama=%s localLmStudio=%s',
|
|
232
|
-
|
|
260
|
+
batchAttachmentHost ? 'batch-attachments' : 'single-attachment',
|
|
233
261
|
c.instantDescribe === true ? 'on' : 'off',
|
|
234
262
|
c.localDescribeStyle === 'structured' ? 'structured' : 'plain',
|
|
235
263
|
local.enabled === true ? 'on' : 'off',
|
|
@@ -240,11 +268,10 @@ export function apply(ctx, config = {}) {
|
|
|
240
268
|
}
|
|
241
269
|
try {
|
|
242
270
|
const result = core.apply(executionCtx, runtimeConfig)
|
|
243
|
-
//
|
|
244
|
-
// provider directory, not by the live adapter registry alone.
|
|
245
|
-
// main DeepSeek + 自动识图 route as a derived alias of official
|
|
246
|
-
//
|
|
247
|
-
// arbitrary textProvider look like DeepSeek. On rc.6 the helper is inert.
|
|
271
|
+
// On newer Hosts the Settings -> Models surface is backed by the
|
|
272
|
+
// configurable-provider directory, not by the live adapter registry alone.
|
|
273
|
+
// Publish the main DeepSeek + 自动识图 route as a derived alias of official
|
|
274
|
+
// DeepSeek. On older Hosts the helper feature-detects and stays inert.
|
|
248
275
|
installWrapperDirectoryAlias(attachmentCompatCtx, runtimeConfig, logging.logger)
|
|
249
276
|
if (result && typeof result.then === 'function') {
|
|
250
277
|
return result.catch((error) => {
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
const CLIENT_PRESENTATION_MARK = 'data-vision-router-presentation-boundary'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Browser prelude for the one legacy value edge left in lib/client.js.
|
|
5
|
+
*
|
|
6
|
+
* DSH rc.8 made @deepseek-ai/dsh-client-ui-attachment a dynamic presentation
|
|
7
|
+
* plugin and deliberately stopped exporting its React implementation as
|
|
8
|
+
* package values. Vision Router must therefore own the presentation of its
|
|
9
|
+
* `vision_present` tool card instead of reaching into that package.
|
|
10
|
+
*
|
|
11
|
+
* The current 1.7.x browser artifact is a hand-maintained self-registering
|
|
12
|
+
* factory. Rewriting the entire artifact just to replace one value edge would
|
|
13
|
+
* create a much larger compatibility diff, so this boundary intercepts that
|
|
14
|
+
* exact legacy require *before it can reach the DSH module table* and supplies
|
|
15
|
+
* a small plugin-owned gallery with the same narrow props the card already
|
|
16
|
+
* passes. No DSH attachment UI code or CSS is copied.
|
|
17
|
+
*
|
|
18
|
+
* This is intentionally a compatibility boundary, not a general module alias:
|
|
19
|
+
* only dsh-vision-router's own factory and only the historical attachment
|
|
20
|
+
* specifier are intercepted. A future modular client can move these components
|
|
21
|
+
* into its source directly and delete this prelude without changing card data
|
|
22
|
+
* loading or Host attachment ownership.
|
|
23
|
+
*/
|
|
24
|
+
export const CLIENT_PRESENTATION_PRELUDE = String.raw`(function(){
|
|
25
|
+
'use strict';
|
|
26
|
+
var TARGET = 'dsh-vision-router';
|
|
27
|
+
var LEGACY_ATTACHMENT_VALUE = '@deepseek-ai/dsh-client-ui-attachment';
|
|
28
|
+
|
|
29
|
+
function createPresentation(React) {
|
|
30
|
+
function PresentedImage(props) {
|
|
31
|
+
var attachment = props.attachment;
|
|
32
|
+
var load = props.load;
|
|
33
|
+
var labels = props.labels;
|
|
34
|
+
var tile = props.tile === true;
|
|
35
|
+
var state = React.useState(null);
|
|
36
|
+
var src = state[0];
|
|
37
|
+
var setSrc = state[1];
|
|
38
|
+
var failedState = React.useState(false);
|
|
39
|
+
var failed = failedState[0];
|
|
40
|
+
var setFailed = failedState[1];
|
|
41
|
+
var attemptState = React.useState(0);
|
|
42
|
+
var attempt = attemptState[0];
|
|
43
|
+
var setAttempt = attemptState[1];
|
|
44
|
+
var openState = React.useState(false);
|
|
45
|
+
var open = openState[0];
|
|
46
|
+
var setOpen = openState[1];
|
|
47
|
+
|
|
48
|
+
React.useEffect(function(){
|
|
49
|
+
var live = true;
|
|
50
|
+
setSrc(null);
|
|
51
|
+
setFailed(false);
|
|
52
|
+
Promise.resolve().then(function(){ return load(attachment); }).then(
|
|
53
|
+
function(url){ if (live) setSrc(url); },
|
|
54
|
+
function(){ if (live) setFailed(true); }
|
|
55
|
+
);
|
|
56
|
+
return function(){ live = false; };
|
|
57
|
+
}, [attachment, load, attempt]);
|
|
58
|
+
|
|
59
|
+
React.useEffect(function(){
|
|
60
|
+
if (!open || typeof document === 'undefined') return undefined;
|
|
61
|
+
var onKeyDown = function(event){ if (event && event.key === 'Escape') setOpen(false); };
|
|
62
|
+
document.addEventListener('keydown', onKeyDown);
|
|
63
|
+
return function(){ document.removeEventListener('keydown', onKeyDown); };
|
|
64
|
+
}, [open]);
|
|
65
|
+
|
|
66
|
+
var label = attachment && attachment.name ? attachment.name : labels.image;
|
|
67
|
+
var box = tile
|
|
68
|
+
? { width: 64, height: 64 }
|
|
69
|
+
: { maxWidth: 240, maxHeight: 240 };
|
|
70
|
+
var frameStyle = Object.assign({
|
|
71
|
+
appearance: 'none',
|
|
72
|
+
border: '1px solid var(--dsw-alias-border-l2)',
|
|
73
|
+
borderRadius: 8,
|
|
74
|
+
background: 'var(--dsw-alias-bg-layer-3)',
|
|
75
|
+
padding: 0,
|
|
76
|
+
overflow: 'hidden',
|
|
77
|
+
display: 'flex',
|
|
78
|
+
alignItems: 'center',
|
|
79
|
+
justifyContent: 'center',
|
|
80
|
+
cursor: src ? 'zoom-in' : failed ? 'pointer' : 'default'
|
|
81
|
+
}, box);
|
|
82
|
+
|
|
83
|
+
if (failed) {
|
|
84
|
+
return React.createElement('button', {
|
|
85
|
+
type: 'button',
|
|
86
|
+
title: labels.loadFailed,
|
|
87
|
+
onClick: function(){ setAttempt(function(value){ return value + 1; }); },
|
|
88
|
+
style: Object.assign({}, frameStyle, {
|
|
89
|
+
minWidth: tile ? 64 : 120,
|
|
90
|
+
minHeight: tile ? 64 : 72,
|
|
91
|
+
color: 'var(--dsw-alias-label-tertiary)',
|
|
92
|
+
font: 'inherit',
|
|
93
|
+
fontSize: 12,
|
|
94
|
+
padding: 8
|
|
95
|
+
})
|
|
96
|
+
}, labels.loadFailed);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
var thumb = React.createElement('button', {
|
|
100
|
+
type: 'button',
|
|
101
|
+
title: labels.open,
|
|
102
|
+
'aria-label': labels.openNamed(label),
|
|
103
|
+
disabled: !src,
|
|
104
|
+
onClick: function(){ if (src) setOpen(true); },
|
|
105
|
+
style: frameStyle
|
|
106
|
+
}, src
|
|
107
|
+
? React.createElement('img', {
|
|
108
|
+
src: src,
|
|
109
|
+
alt: label,
|
|
110
|
+
style: tile
|
|
111
|
+
? { width: '100%', height: '100%', objectFit: 'cover', display: 'block' }
|
|
112
|
+
: { maxWidth: 240, maxHeight: 240, width: 'auto', height: 'auto', display: 'block' }
|
|
113
|
+
})
|
|
114
|
+
: React.createElement('span', {
|
|
115
|
+
style: { color: 'var(--dsw-alias-label-tertiary)', fontSize: 12, padding: 10 }
|
|
116
|
+
}, labels.loading)
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (!open || !src) return thumb;
|
|
120
|
+
var overlay = React.createElement('div', {
|
|
121
|
+
role: 'dialog',
|
|
122
|
+
'aria-modal': 'true',
|
|
123
|
+
'aria-label': labels.lightbox.dialog,
|
|
124
|
+
onClick: function(event){ if (event.target === event.currentTarget) setOpen(false); },
|
|
125
|
+
style: {
|
|
126
|
+
position: 'fixed', inset: 0, zIndex: 11000, background: '#000b',
|
|
127
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24,
|
|
128
|
+
boxSizing: 'border-box'
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
React.createElement('img', {
|
|
132
|
+
src: src,
|
|
133
|
+
alt: label,
|
|
134
|
+
style: { maxWidth: '92vw', maxHeight: '88vh', objectFit: 'contain', borderRadius: 8 }
|
|
135
|
+
}),
|
|
136
|
+
React.createElement('button', {
|
|
137
|
+
type: 'button',
|
|
138
|
+
'aria-label': labels.lightbox.close,
|
|
139
|
+
title: labels.lightbox.close,
|
|
140
|
+
onClick: function(){ setOpen(false); },
|
|
141
|
+
style: {
|
|
142
|
+
position: 'fixed', top: 16, right: 18, width: 36, height: 36,
|
|
143
|
+
borderRadius: 18, border: '1px solid #ffffff55', background: '#111c',
|
|
144
|
+
color: '#fff', font: 'inherit', fontSize: 22, cursor: 'pointer'
|
|
145
|
+
}
|
|
146
|
+
}, '×')
|
|
147
|
+
);
|
|
148
|
+
return React.createElement(React.Fragment, null, thumb, overlay);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function ImageGallery(props) {
|
|
152
|
+
var images = Array.isArray(props.images) ? props.images : [];
|
|
153
|
+
if (images.length === 0) return null;
|
|
154
|
+
var tile = images.length > 1;
|
|
155
|
+
return React.createElement('div', {
|
|
156
|
+
style: {
|
|
157
|
+
display: 'flex', flexWrap: 'wrap', gap: 6,
|
|
158
|
+
justifyContent: props.align === 'end' ? 'flex-end' : 'flex-start',
|
|
159
|
+
alignItems: 'flex-start', maxWidth: '100%'
|
|
160
|
+
}
|
|
161
|
+
}, images.map(function(image, index){
|
|
162
|
+
var attachment = image && image.attachment;
|
|
163
|
+
var key = attachment && attachment.attachmentId
|
|
164
|
+
? String(attachment.attachmentId) + ':' + index
|
|
165
|
+
: String(index);
|
|
166
|
+
return React.createElement(PresentedImage, {
|
|
167
|
+
key: key,
|
|
168
|
+
attachment: attachment,
|
|
169
|
+
load: props.load,
|
|
170
|
+
labels: props.labels,
|
|
171
|
+
tile: tile
|
|
172
|
+
});
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { ImageGallery: ImageGallery };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function patchLoader(loader) {
|
|
180
|
+
if (!loader || (typeof loader !== 'object' && typeof loader !== 'function')) return;
|
|
181
|
+
|
|
182
|
+
if (typeof loader.load === 'function' && !loader.load.__visionRouterPresentationBoundary) {
|
|
183
|
+
var original = loader.load;
|
|
184
|
+
function load(spec) {
|
|
185
|
+
if (spec && spec.id === TARGET && typeof spec.factory === 'function') {
|
|
186
|
+
var factory = spec.factory;
|
|
187
|
+
spec = Object.assign({}, spec, {
|
|
188
|
+
factory: function(require) {
|
|
189
|
+
var React = require('react');
|
|
190
|
+
var presentation = createPresentation(React);
|
|
191
|
+
function scopedRequire(id) {
|
|
192
|
+
if (id === LEGACY_ATTACHMENT_VALUE) return presentation;
|
|
193
|
+
return require(id);
|
|
194
|
+
}
|
|
195
|
+
return factory(scopedRequire);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return original.call(this, spec);
|
|
200
|
+
}
|
|
201
|
+
Object.defineProperty(load, '__visionRouterPresentationBoundary', { value: true });
|
|
202
|
+
loader.load = load;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// rc.8's parser installs a queue-mode facade first. When the Web shell
|
|
206
|
+
// later calls create(), ClientModuleSystem switches that *same object* to
|
|
207
|
+
// live mode by assigning a brand-new loader.load function. That assignment
|
|
208
|
+
// necessarily erases every queue-time wrapper. Wrap create itself so the
|
|
209
|
+
// boundary is re-applied immediately after the official queue -> live
|
|
210
|
+
// transition and before lazy third-party bundles can register.
|
|
211
|
+
if (typeof loader.create === 'function' && !loader.create.__visionRouterPresentationBoundary) {
|
|
212
|
+
var originalCreate = loader.create;
|
|
213
|
+
function create() {
|
|
214
|
+
var result = originalCreate.apply(this, arguments);
|
|
215
|
+
patchLoader(loader);
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
Object.defineProperty(create, '__visionRouterPresentationBoundary', { value: true });
|
|
219
|
+
loader.create = create;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function install() {
|
|
224
|
+
if (window.__ModuleLoader__) {
|
|
225
|
+
patchLoader(window.__ModuleLoader__);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
var descriptor = Object.getOwnPropertyDescriptor(window, '__ModuleLoader__');
|
|
229
|
+
if (descriptor && descriptor.configurable === false) return;
|
|
230
|
+
var stored;
|
|
231
|
+
Object.defineProperty(window, '__ModuleLoader__', {
|
|
232
|
+
configurable: true,
|
|
233
|
+
enumerable: true,
|
|
234
|
+
get: function(){ return stored; },
|
|
235
|
+
set: function(value) {
|
|
236
|
+
stored = value;
|
|
237
|
+
patchLoader(value);
|
|
238
|
+
Object.defineProperty(window, '__ModuleLoader__', {
|
|
239
|
+
configurable: true,
|
|
240
|
+
enumerable: true,
|
|
241
|
+
writable: true,
|
|
242
|
+
value: stored
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
install();
|
|
249
|
+
})();`
|
|
250
|
+
|
|
251
|
+
export function injectClientPresentationBoundary(html) {
|
|
252
|
+
if (typeof html !== 'string' || html.includes(CLIENT_PRESENTATION_MARK)) return html
|
|
253
|
+
const safe = CLIENT_PRESENTATION_PRELUDE.replace(/<\/script/gi, '<\\/script')
|
|
254
|
+
const script = `<script ${CLIENT_PRESENTATION_MARK}>${safe}</script>`
|
|
255
|
+
// DSH's client-modules index tap prepends the queue-mode loader bootstrap at
|
|
256
|
+
// <head>. Run after that parser bootstrap so we can wrap both load() and the
|
|
257
|
+
// later create() transition. The create wrapper re-applies the boundary when
|
|
258
|
+
// rc.8 replaces loader.load while entering live mode.
|
|
259
|
+
const closeHead = html.indexOf('</head>')
|
|
260
|
+
if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
|
|
261
|
+
return `${html}${script}`
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function installClientPresentationBoundary(ctx) {
|
|
265
|
+
ctx?.inject?.(['webServer'], (webCtx) => {
|
|
266
|
+
webCtx.effect(
|
|
267
|
+
() => webCtx.webServer.tapIndex(injectClientPresentationBoundary),
|
|
268
|
+
'vision-router: client presentation boundary',
|
|
269
|
+
)
|
|
270
|
+
})
|
|
271
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
const
|
|
1
|
+
const HOST_OWNED_ROUTES = new Set(['deepseek-official', 'deepseek-official-native'])
|
|
2
|
+
|
|
3
|
+
const LEGACY_VISION_ATTACHMENT_BYTES = 20 * 1024 * 1024
|
|
4
|
+
const LEGACY_VISION_ATTACHMENT_PIXELS = 100_000_000
|
|
5
|
+
const DSH_RC8_DEFAULT_IMAGE_DIMENSION = 2000
|
|
6
|
+
const VISION_ROUTER_IMAGE_DIMENSION = 10_000
|
|
2
7
|
|
|
3
8
|
function attachmentStoreOf(ctx) {
|
|
4
9
|
if (!ctx || typeof ctx !== 'object') return undefined
|
|
@@ -19,33 +24,119 @@ function attachmentStoreOf(ctx) {
|
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
27
|
+
* Detect the released batch-attachment contract directly. DSH rc.6 exposes
|
|
28
|
+
* validateImage/saveImage/readImage only; rc.7+ adds AttachmentStore.saveImages().
|
|
29
|
+
* Do not infer this from unrelated LLM/settings methods: rc.6 already shipped
|
|
30
|
+
* registerConfigurableProviders(), so those methods cannot prove the attachment
|
|
31
|
+
* ownership generation.
|
|
26
32
|
*/
|
|
27
|
-
export function
|
|
33
|
+
export function hasBatchAttachmentContract(ctx) {
|
|
28
34
|
const store = attachmentStoreOf(ctx)
|
|
29
35
|
return !!store && typeof store.saveImages === 'function'
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
39
|
+
* Repair one historical Vision Router profile overlay on DSH rc.8.
|
|
40
|
+
*
|
|
41
|
+
* Before rc.8 introduced maxImageDimension, Vision Router wrote a profile-local
|
|
42
|
+
* attachment-local override containing only 20MiB / 100MP. DSH patch rows
|
|
43
|
+
* replace the WHOLE config object instead of deep-merging it, so that later
|
|
44
|
+
* profile layer shadows the newer bundle row and rc.8 silently rematerializes
|
|
45
|
+
* its own 2000px default. Plugin updates cannot delete a user's profile layer.
|
|
46
|
+
*
|
|
47
|
+
* The historical 20MiB + 100MP pair is therefore the migration fingerprint.
|
|
48
|
+
* Only when that exact pair is combined with rc.8's exact 2000px default do we
|
|
49
|
+
* raise the live store policy to Vision Router's 10000px contract. Explicitly
|
|
50
|
+
* customised dimensions or unrelated attachment deployments are never touched.
|
|
51
|
+
* LocalAttachmentStore reads `this.imageLimits` for every validation/save, so
|
|
52
|
+
* replacing this immutable policy object repairs authoritative admission as
|
|
53
|
+
* well as the limits projected to the browser for error copy.
|
|
36
54
|
*/
|
|
37
|
-
export function
|
|
55
|
+
export function ensureVisionAttachmentAdmissionPolicy(ctx, logger, options = {}) {
|
|
56
|
+
const store = attachmentStoreOf(ctx)
|
|
57
|
+
const limits = store && store.imageLimits
|
|
58
|
+
if (!store || !limits || typeof limits !== 'object') {
|
|
59
|
+
return { changed: false, reason: 'attachment-limits-unavailable' }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const targetDimension = Number.isInteger(options.maxImageDimension)
|
|
63
|
+
? Math.max(1, options.maxImageDimension)
|
|
64
|
+
: VISION_ROUTER_IMAGE_DIMENSION
|
|
65
|
+
const legacy =
|
|
66
|
+
limits.maxImageBytes === LEGACY_VISION_ATTACHMENT_BYTES &&
|
|
67
|
+
limits.maxImagePixels === LEGACY_VISION_ATTACHMENT_PIXELS &&
|
|
68
|
+
limits.maxImageDimension === DSH_RC8_DEFAULT_IMAGE_DIMENSION
|
|
69
|
+
|
|
70
|
+
if (!legacy) return { changed: false, reason: 'not-legacy-overlay', limits }
|
|
71
|
+
|
|
72
|
+
const next = Object.freeze({ ...limits, maxImageDimension: targetDimension })
|
|
73
|
+
let changed = false
|
|
74
|
+
try {
|
|
75
|
+
changed = Reflect.set(store, 'imageLimits', next)
|
|
76
|
+
} catch {
|
|
77
|
+
changed = false
|
|
78
|
+
}
|
|
79
|
+
if (!changed || store.imageLimits !== next) {
|
|
80
|
+
try {
|
|
81
|
+
logger?.warn?.(
|
|
82
|
+
'vision-router: detected the legacy attachment-local 20MiB/100MP overlay but could not repair maxImageDimension; DSH may still enforce 2000px',
|
|
83
|
+
)
|
|
84
|
+
} catch {
|
|
85
|
+
// Diagnostics must never break Host startup.
|
|
86
|
+
}
|
|
87
|
+
return { changed: false, reason: 'limits-readonly', limits }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
logger?.info?.(
|
|
92
|
+
'vision-router: repaired legacy attachment-local overlay — maxImageDimension %d -> %d',
|
|
93
|
+
DSH_RC8_DEFAULT_IMAGE_DIMENSION,
|
|
94
|
+
targetDimension,
|
|
95
|
+
)
|
|
96
|
+
} catch {
|
|
97
|
+
// Diagnostics must never break Host startup.
|
|
98
|
+
}
|
|
99
|
+
return { changed: true, reason: 'legacy-overlay-repaired', limits: next }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Keep the migration attached to the service lifecycle, not only initial boot.
|
|
104
|
+
* DSH hot-reloads profile/home patch files transactionally; attachment-local can
|
|
105
|
+
* therefore be reconstructed after Vision Router has already started. Cordis'
|
|
106
|
+
* service injection reruns this narrow check for each replacement instance.
|
|
107
|
+
*/
|
|
108
|
+
export function installVisionAttachmentAdmissionPolicy(ctx, logger, options = {}) {
|
|
109
|
+
const initial = ensureVisionAttachmentAdmissionPolicy(ctx, logger, options)
|
|
110
|
+
try {
|
|
111
|
+
if (ctx && typeof ctx.inject === 'function') {
|
|
112
|
+
ctx.inject(['attachments'], (attachmentCtx) => {
|
|
113
|
+
ensureVisionAttachmentAdmissionPolicy(attachmentCtx, logger, options)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
// Older/partial contexts may not expose lifecycle injection. The initial
|
|
118
|
+
// repair above is still authoritative for the currently mounted store.
|
|
119
|
+
}
|
|
120
|
+
return initial
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Preserve host ownership of official DeepSeek routes on the batch-attachment
|
|
125
|
+
* contract generation. Vision Router may wrap/delegate those routes, but must
|
|
126
|
+
* not synthesize or replace them. Other adapters pass through unchanged.
|
|
127
|
+
*/
|
|
128
|
+
export function protectHostProviderOwnership(ctx) {
|
|
38
129
|
if (!ctx || typeof ctx !== 'object' || !ctx.llm || typeof ctx.llm !== 'object') return ctx
|
|
39
130
|
const llm = new Proxy(ctx.llm, {
|
|
40
131
|
get(target, property) {
|
|
41
132
|
if (property === 'registerAdapter') {
|
|
42
133
|
return (routes, adapter) => {
|
|
43
134
|
const list = Array.isArray(routes) ? routes : [routes]
|
|
44
|
-
if (list.some((route) =>
|
|
135
|
+
if (list.some((route) => HOST_OWNED_ROUTES.has(String(route)))) {
|
|
45
136
|
const error = new Error(
|
|
46
|
-
'vision-router: DSH
|
|
137
|
+
'vision-router: DSH owns deepseek-official on this host contract; use the auto-vision wrapper instead of provider takeover',
|
|
47
138
|
)
|
|
48
|
-
error.code = '
|
|
139
|
+
error.code = 'DSH_HOST_PROVIDER_OWNERSHIP'
|
|
49
140
|
throw error
|
|
50
141
|
}
|
|
51
142
|
return target.registerAdapter(routes, adapter)
|
|
@@ -65,9 +156,9 @@ export function protectRc7ProviderOwnership(ctx) {
|
|
|
65
156
|
}
|
|
66
157
|
|
|
67
158
|
/**
|
|
68
|
-
* The common rc.6
|
|
69
|
-
*
|
|
70
|
-
* forcing a
|
|
159
|
+
* The common rc.6+ SettingsProvider service is the stable public seam. This
|
|
160
|
+
* helper mirrors installSettingsSection's attach/detach semantics without
|
|
161
|
+
* forcing a newer package-resolution edge into the minimum-supported host.
|
|
71
162
|
*/
|
|
72
163
|
export function installSettingsSectionCompat(ctx, namespace, Config, entryConfig, hooks) {
|
|
73
164
|
hooks.setSource(() => entryConfig)
|
|
@@ -82,22 +173,23 @@ export function installSettingsSectionCompat(ctx, namespace, Config, entryConfig
|
|
|
82
173
|
hooks.setSource(() => entryConfig)
|
|
83
174
|
hooks.onChange()
|
|
84
175
|
},
|
|
85
|
-
'vision-router:
|
|
176
|
+
'vision-router: host settings compatibility source',
|
|
86
177
|
)
|
|
87
178
|
})
|
|
88
179
|
}
|
|
89
180
|
|
|
90
181
|
/**
|
|
91
|
-
* Bridge
|
|
92
|
-
* `ctx.inject(['settings']) -> settings.register()` callback. The
|
|
93
|
-
*
|
|
182
|
+
* Bridge the newer public settings-section semantics to core's legacy-shaped
|
|
183
|
+
* `ctx.inject(['settings']) -> settings.register()` callback. The old `stealth`
|
|
184
|
+
* flag is masked because host-owned official routes cannot be taken over on
|
|
185
|
+
* this contract generation.
|
|
94
186
|
*/
|
|
95
|
-
export function
|
|
187
|
+
export function installHostSettingsCompatibility(ctx, entryConfig, options = {}) {
|
|
96
188
|
const install = options.installSettingsSection ?? installSettingsSectionCompat
|
|
97
189
|
const ns = options.namespace
|
|
98
190
|
const Config = options.Config
|
|
99
191
|
if (Config === undefined) {
|
|
100
|
-
throw new TypeError('vision-router:
|
|
192
|
+
throw new TypeError('vision-router: host settings compatibility requires Config')
|
|
101
193
|
}
|
|
102
194
|
|
|
103
195
|
let activeSource = () => ({ ...entryConfig, stealth: false })
|
|
@@ -172,13 +264,20 @@ export function installRc7SettingsCompatibility(ctx, entryConfig, options = {})
|
|
|
172
264
|
}
|
|
173
265
|
|
|
174
266
|
/**
|
|
175
|
-
*
|
|
176
|
-
* service keeps AttachmentId store-owned, so
|
|
177
|
-
* untouched
|
|
267
|
+
* The minimum-supported host keeps the narrow Termux fallback. A
|
|
268
|
+
* batch-capable attachment service keeps AttachmentId store-owned, so that
|
|
269
|
+
* host context passes through untouched.
|
|
178
270
|
*/
|
|
179
271
|
export function attachmentContextForContract(ctx, logger, options = {}) {
|
|
180
|
-
if (
|
|
272
|
+
if (hasBatchAttachmentContract(ctx)) return ctx
|
|
181
273
|
const install = options.installAndroidAttachmentCompat
|
|
182
274
|
if (typeof install !== 'function') return ctx
|
|
183
275
|
return install(ctx, logger, options.android)
|
|
184
276
|
}
|
|
277
|
+
|
|
278
|
+
// Transitional aliases for external/tests written against the rc.7-era names.
|
|
279
|
+
// New code must consume the capability-named exports above so future DSH
|
|
280
|
+
// releases do not accrete `if (rc8) / if (rc9)` branches.
|
|
281
|
+
export const isRc7ContractRuntime = hasBatchAttachmentContract
|
|
282
|
+
export const protectRc7ProviderOwnership = protectHostProviderOwnership
|
|
283
|
+
export const installRc7SettingsCompatibility = installHostSettingsCompatibility
|
|
@@ -516,31 +516,51 @@ export const LIVE_MODEL_CLIENT_PRELUDE = String.raw`(function(){
|
|
|
516
516
|
}
|
|
517
517
|
|
|
518
518
|
function patchLoader(loader) {
|
|
519
|
-
if (!loader || typeof loader
|
|
520
|
-
|
|
521
|
-
function load
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
spec
|
|
525
|
-
factory
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
var
|
|
529
|
-
|
|
530
|
-
var
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
519
|
+
if (!loader || (typeof loader !== 'object' && typeof loader !== 'function')) return;
|
|
520
|
+
|
|
521
|
+
if (typeof loader.load === 'function' && !loader.load.__visionRouterLiveModels) {
|
|
522
|
+
var original = loader.load;
|
|
523
|
+
function load(spec) {
|
|
524
|
+
if (spec && spec.id === 'dsh-vision-router' && typeof spec.factory === 'function') {
|
|
525
|
+
var factory = spec.factory;
|
|
526
|
+
spec = Object.assign({}, spec, {
|
|
527
|
+
factory: function(require) {
|
|
528
|
+
var exports = factory(require);
|
|
529
|
+
if (exports && typeof exports.apply === 'function' && !exports.apply.__visionRouterLiveModels) {
|
|
530
|
+
var apply = exports.apply;
|
|
531
|
+
var wrappedApply = function(ctx) {
|
|
532
|
+
var rest = Array.prototype.slice.call(arguments, 1);
|
|
533
|
+
return apply.apply(exports, [wrapContext(ctx)].concat(rest));
|
|
534
|
+
};
|
|
535
|
+
Object.defineProperty(wrappedApply, '__visionRouterLiveModels', { value: true });
|
|
536
|
+
exports.apply = wrappedApply;
|
|
537
|
+
}
|
|
538
|
+
return exports;
|
|
535
539
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return original.call(loader, spec);
|
|
543
|
+
}
|
|
544
|
+
Object.defineProperty(load, '__visionRouterLiveModels', { value: true });
|
|
545
|
+
loader.load = load;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// rc.8 boot has two loader phases on the same object. The parser first
|
|
549
|
+
// exposes queue-mode load(); later ClientModuleSystem.create() assigns a
|
|
550
|
+
// new live-mode load() before any lazy community bundle arrives. A wrapper
|
|
551
|
+
// installed only during parsing is therefore silently discarded. Wrap the
|
|
552
|
+
// transition itself and immediately re-apply this factory/context boundary
|
|
553
|
+
// to the new live sink.
|
|
554
|
+
if (typeof loader.create === 'function' && !loader.create.__visionRouterLiveModels) {
|
|
555
|
+
var originalCreate = loader.create;
|
|
556
|
+
function create() {
|
|
557
|
+
var result = originalCreate.apply(this, arguments);
|
|
558
|
+
patchLoader(loader);
|
|
559
|
+
return result;
|
|
539
560
|
}
|
|
540
|
-
|
|
561
|
+
Object.defineProperty(create, '__visionRouterLiveModels', { value: true });
|
|
562
|
+
loader.create = create;
|
|
541
563
|
}
|
|
542
|
-
Object.defineProperty(load, '__visionRouterLiveModels', { value: true });
|
|
543
|
-
loader.load = load;
|
|
544
564
|
}
|
|
545
565
|
|
|
546
566
|
function install() {
|
|
@@ -574,8 +594,13 @@ export const LIVE_MODEL_CLIENT_PRELUDE = String.raw`(function(){
|
|
|
574
594
|
export function injectLiveModelClientPrelude(html) {
|
|
575
595
|
if (typeof html !== 'string' || html.includes(CLIENT_PRELUDE_MARK)) return html
|
|
576
596
|
const script = `<script ${CLIENT_PRELUDE_MARK}>${LIVE_MODEL_CLIENT_PRELUDE.replace(/<\/script/gi, '<\\/script')}</script>`
|
|
577
|
-
|
|
578
|
-
|
|
597
|
+
// DSH's client-modules index tap prepends its queue-mode facade immediately
|
|
598
|
+
// after <head>. Run after that parser bootstrap so we can wrap both load()
|
|
599
|
+
// and create(); the create wrapper then re-attaches us after rc.8 swaps in
|
|
600
|
+
// its live registration sink, before lazy third-party bundles arrive.
|
|
601
|
+
const closeHead = html.indexOf('</head>')
|
|
602
|
+
if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
|
|
603
|
+
return `${html}${script}`
|
|
579
604
|
}
|
|
580
605
|
|
|
581
606
|
export function installLiveModelClientPrelude(ctx) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vision-router",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.3",
|
|
4
4
|
"description": "Eyes for text-only DeepSeek Harness agents: built-in free vision chain (no key) + pixel-level vision tools (Q&A, grounding, crop, pixel diff, colors, OCR, SVG trace, cutout, screenshots). One-command install, no Python, image turns work like ordinary tool-calling turns.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"sharp": "^0.35.3"
|
|
76
76
|
},
|
|
77
77
|
"scripts": {
|
|
78
|
-
"test": "node --test tests/core.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/provider-directory-fallback.test.js tests/settings-section-order.test.js tests/vision-resilience.test.js tests/client.test.js tests/remote-settings-bridge.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/update-check.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/bundle-defaults.test.js tests/file-logger.test.js tests/replay-delegation.test.js tests/adversarial-hardening.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/local-ollama.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/android-attachment-compat.test.js tests/free-cloud-first.test.js tests/rc6-rc7-compat.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/vision-budget-activation.test.js tests/runtime-boundary-fixes.test.js tests/tesseract-node24-boot.test.js tests/session-vision-state.test.js tests/session-vision-state-integration.test.js tests/image-resource-governor.test.js tests/pixel-diff-stream.test.js tests/large-image-resource-integration.test.js tests/structured-guard-idempotency.test.js"
|
|
78
|
+
"test": "node --test tests/core.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/provider-directory-fallback.test.js tests/settings-section-order.test.js tests/attachment-admission-policy.test.js tests/vision-resilience.test.js tests/client.test.js tests/client-presentation-boundary.test.js tests/remote-settings-bridge.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/update-check.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/bundle-defaults.test.js tests/file-logger.test.js tests/replay-delegation.test.js tests/adversarial-hardening.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/local-ollama.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/android-attachment-compat.test.js tests/free-cloud-first.test.js tests/rc6-rc7-compat.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/vision-budget-activation.test.js tests/runtime-boundary-fixes.test.js tests/tesseract-node24-boot.test.js tests/session-vision-state.test.js tests/session-vision-state-integration.test.js tests/image-resource-governor.test.js tests/pixel-diff-stream.test.js tests/large-image-resource-integration.test.js tests/structured-guard-idempotency.test.js"
|
|
79
79
|
},
|
|
80
80
|
"pnpm": {
|
|
81
81
|
"onlyBuiltDependencies": [
|