dsh-vision-router 2.0.0 → 2.1.0
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 +33 -16
- package/README.zh.md +34 -17
- package/cordis.patch.yml +19 -9
- package/docs/architecture/2x-contract-ledger.md +165 -0
- package/docs/architecture/compat-inventory.md +121 -0
- package/docs/architecture/dsh-compatibility-matrix.md +57 -0
- package/docs/architecture/dsh-support-window.md +50 -0
- package/docs/architecture/final-closure-audit.md +148 -0
- package/docs/architecture/p2-jobs-feasibility.md +94 -0
- package/docs/architecture/p3-compat-retirement.md +46 -0
- package/docs/architecture/p3-host-native-seams.md +43 -0
- package/docs/architecture/p3-native-recovery-evaluation.md +65 -0
- package/docs/architecture/runtime-boundaries.md +124 -0
- package/docs/releases/v2.0.1.md +17 -0
- package/docs/releases/v2.1.0-support-window.md +29 -0
- package/docs/releases/v2.1.0.md +83 -0
- package/entry.js +13 -307
- package/index.js +85 -352
- package/lib/abort-signal-compat.js +191 -0
- package/lib/adapter-update-coalescer.js +113 -29
- package/lib/adversarial-hardening.js +24 -9
- package/lib/artifact-boundary.js +16 -175
- package/lib/artifact-io.js +220 -0
- package/lib/artifact-retention.js +305 -53
- package/lib/catalog-corrections.js +6 -1
- package/lib/client-host-compat-prelude.js +249 -0
- package/lib/client-presentation-boundary.js +61 -3
- package/lib/client.js +71 -72
- package/lib/core-vision-surface.js +124 -0
- package/lib/depth-guidance.js +114 -90
- package/lib/doctor-cli-p0.js +168 -0
- package/lib/doctor-cli.js +7 -0
- package/lib/doctor-vision-limits.js +72 -0
- package/lib/dsh-contract-compat.js +207 -39
- package/lib/dsh-host-capabilities.js +107 -0
- package/lib/dsh-support-window.js +48 -0
- package/lib/http-compat.js +22 -6
- package/lib/image-resource-governor.js +35 -10
- package/lib/legacy-core-vision-policy-bridge.js +25 -226
- package/lib/legacy-global-proxy-boundary.js +127 -0
- package/lib/live-model-client-prelude.js +0 -12
- package/lib/mixed-router.js +81 -81
- package/lib/public-entry.js +42 -4
- package/lib/runtime-composition.js +348 -0
- package/lib/runtime-i18n-boundary.js +683 -0
- package/lib/runtime-i18n-core-scope.js +56 -0
- package/lib/runtime-i18n-core.js +34 -0
- package/lib/runtime-i18n.js +121 -0
- package/lib/session-surface-policy.js +97 -0
- package/lib/session-vision-index.js +282 -0
- package/lib/session-vision-runtime.js +48 -0
- package/lib/settings-factory-lifecycle.js +144 -0
- package/lib/settings-ia-client-prelude.js +74 -2
- package/lib/settings-limit-client-prelude.js +110 -0
- package/lib/settings-native-card-layout.js +297 -0
- package/lib/settings-number-contract.js +29 -0
- package/lib/structured-flow-hardening.js +207 -76
- package/lib/tesseract-exec-compat.js +58 -32
- package/lib/update-check.js +17 -5
- package/lib/v2-settings-ia-integration.js +9 -4
- package/lib/vision-artifact-store.js +77 -0
- package/lib/vision-backend-runtime-policy.js +6 -2
- package/lib/vision-background-benchmark.js +130 -41
- package/lib/vision-background-failure-policy.js +70 -0
- package/lib/vision-background-stop-store.js +54 -9
- package/lib/vision-capability-benchmark-client.js +57 -66
- package/lib/vision-capability-benchmark-presentation.js +97 -0
- package/lib/vision-capability-benchmark-service.js +3 -3
- package/lib/vision-execution-order-apply.js +61 -0
- package/lib/vision-execution-order-plan.js +64 -0
- package/lib/vision-execution-order.js +38 -0
- package/lib/vision-limit-diagnostics.js +202 -0
- package/lib/vision-product-presentation.js +284 -0
- package/lib/vision-provider-transport.js +197 -0
- package/lib/vision-resilience.js +87 -45
- package/lib/vision-routing-evidence.js +366 -0
- package/lib/vision-routing-runtime.js +278 -0
- package/lib/vision-tool-runtime-boundary.js +27 -21
- package/lib/vision-turn-budget-client-prelude.js +9 -9
- package/lib/web/benchmark-panel.js +6 -0
- package/lib/web/diagnostics-panel.js +44 -0
- package/lib/web/index.js +60 -0
- package/lib/web/model-picker.js +22 -0
- package/lib/web/onboarding.js +6 -0
- package/lib/web/product-state.js +59 -0
- package/lib/web/remote-settings-client.js +8 -0
- package/lib/web/routing-section.js +6 -0
- package/lib/web/settings-controller.js +6 -0
- package/lib/web-capability-boundary.js +65 -9
- package/lib/windows-screenshot-dpi-compat.js +148 -0
- package/package.json +14 -6
- package/lib/vision-capability-shadow.js +0 -576
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { htmlHasScriptMarker } from './html-script-marker.js'
|
|
2
|
+
|
|
3
|
+
const CLIENT_HOST_COMPAT_MARK = 'data-vision-router-host-compat'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Client-side Host compatibility shim for the hand-maintained Vision Router
|
|
7
|
+
* settings factory.
|
|
8
|
+
*
|
|
9
|
+
* DSH 0.1.2 moved model-catalog ownership from the legacy
|
|
10
|
+
* `connection.api.llm.models()` facade to `remote.session.modelCatalog()` and
|
|
11
|
+
* renamed the forwarded credential invalidation event. Keep the old client
|
|
12
|
+
* factory unchanged: when (and only when) the new Host capability exists,
|
|
13
|
+
* expose a tiny compatibility view of those two seams to that one factory.
|
|
14
|
+
* Old rc.6/rc.7/rc.8 clients see their original Connection and Remote objects
|
|
15
|
+
* byte-for-byte because the capability probe fails closed.
|
|
16
|
+
*/
|
|
17
|
+
export const CLIENT_HOST_COMPAT_PRELUDE = String.raw`(function(){
|
|
18
|
+
'use strict';
|
|
19
|
+
var TARGET = 'dsh-vision-router';
|
|
20
|
+
var LEGACY_CREDENTIAL_EVENT = 'credentials/updated';
|
|
21
|
+
var HOST_CREDENTIAL_EVENT = 'credentials/reference-updated';
|
|
22
|
+
|
|
23
|
+
function hasHostCatalog(remote) {
|
|
24
|
+
return !!(
|
|
25
|
+
remote && remote.session &&
|
|
26
|
+
typeof remote.session.modelCatalog === 'function'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function wrapCatalogResult(value) {
|
|
31
|
+
// The alpha Host returns ConnectionRpcResult<ModelCatalog>. The legacy
|
|
32
|
+
// factory already understands the older { result: RpcResult } envelope, so
|
|
33
|
+
// adapt only that carrier shape and leave future direct values untouched.
|
|
34
|
+
if (value && typeof value === 'object' && typeof value.ok === 'boolean') {
|
|
35
|
+
return { result: value };
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function catalogModels(remote) {
|
|
41
|
+
return function models() {
|
|
42
|
+
return Promise.resolve(remote.session.modelCatalog()).then(wrapCatalogResult);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function compatibleApi(originalApi, remote) {
|
|
47
|
+
var originalLlm = originalApi && originalApi.llm;
|
|
48
|
+
var llm = originalLlm && (typeof originalLlm === 'object' || typeof originalLlm === 'function')
|
|
49
|
+
? new Proxy(originalLlm, {
|
|
50
|
+
get: function(target, property) {
|
|
51
|
+
if (property === 'models') return catalogModels(remote);
|
|
52
|
+
var value = Reflect.get(target, property, target);
|
|
53
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
: { models: catalogModels(remote) };
|
|
57
|
+
if (originalApi && (typeof originalApi === 'object' || typeof originalApi === 'function')) {
|
|
58
|
+
return new Proxy(originalApi, {
|
|
59
|
+
get: function(target, property) {
|
|
60
|
+
if (property === 'llm') return llm;
|
|
61
|
+
var value = Reflect.get(target, property, target);
|
|
62
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return { llm: llm };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function compatibleConnection(connection, remote) {
|
|
70
|
+
if (!hasHostCatalog(remote)) return connection;
|
|
71
|
+
if (!connection || (typeof connection !== 'object' && typeof connection !== 'function')) return connection;
|
|
72
|
+
var api = compatibleApi(connection.api, remote);
|
|
73
|
+
return new Proxy(connection, {
|
|
74
|
+
get: function(target, property) {
|
|
75
|
+
if (property === 'api') return api;
|
|
76
|
+
var value = Reflect.get(target, property, target);
|
|
77
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function compatibleRemote(remote) {
|
|
83
|
+
if (!hasHostCatalog(remote)) return remote;
|
|
84
|
+
if (!remote || (typeof remote !== 'object' && typeof remote !== 'function')) return remote;
|
|
85
|
+
return new Proxy(remote, {
|
|
86
|
+
get: function(target, property) {
|
|
87
|
+
if (property === '$on') {
|
|
88
|
+
var subscribe = Reflect.get(target, property, target);
|
|
89
|
+
if (typeof subscribe !== 'function') return subscribe;
|
|
90
|
+
return function(event, listener) {
|
|
91
|
+
var args = Array.prototype.slice.call(arguments);
|
|
92
|
+
if (event === LEGACY_CREDENTIAL_EVENT) args[0] = HOST_CREDENTIAL_EVENT;
|
|
93
|
+
return subscribe.apply(target, args);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
var value = Reflect.get(target, property, target);
|
|
97
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function patchCanonicalImageCopy(namespace, dictionaries) {
|
|
103
|
+
if (namespace !== 'vision-router' || !dictionaries || typeof dictionaries !== 'object') return dictionaries;
|
|
104
|
+
var next = Object.assign({}, dictionaries);
|
|
105
|
+
if (next.zh && typeof next.zh === 'object') {
|
|
106
|
+
next.zh = Object.assign({}, next.zh, {
|
|
107
|
+
hintInstantDescribe: '会话日志保留 Host 持久化后的图片;像素工具以这份 canonical 图片为坐标与像素事实源。',
|
|
108
|
+
openPresentedImage: '点击查看图片',
|
|
109
|
+
openNamedImage: '查看图片:{name}'
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (next.en && typeof next.en === 'object') {
|
|
113
|
+
next.en = Object.assign({}, next.en, {
|
|
114
|
+
hintInstantDescribe: 'The session log keeps the Host-persisted image; pixel tools use that canonical image as their coordinate and pixel source of truth.',
|
|
115
|
+
openPresentedImage: 'Open image',
|
|
116
|
+
openNamedImage: 'Open image: {name}'
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return next;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function compatibleLocale(locale) {
|
|
123
|
+
if (!locale || (typeof locale !== 'object' && typeof locale !== 'function')) return locale;
|
|
124
|
+
return new Proxy(locale, {
|
|
125
|
+
get: function(target, property) {
|
|
126
|
+
if (property === 'register') {
|
|
127
|
+
var register = Reflect.get(target, property, target);
|
|
128
|
+
if (typeof register !== 'function') return register;
|
|
129
|
+
return function(namespace, dictionaries) {
|
|
130
|
+
var args = Array.prototype.slice.call(arguments);
|
|
131
|
+
args[1] = patchCanonicalImageCopy(namespace, dictionaries);
|
|
132
|
+
return register.apply(target, args);
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
var value = Reflect.get(target, property, target);
|
|
136
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function compatibleContext(ctx) {
|
|
142
|
+
if (!ctx || typeof ctx !== 'object' || typeof Proxy !== 'function') return ctx;
|
|
143
|
+
var remote = compatibleRemote(ctx.remote);
|
|
144
|
+
var locale = compatibleLocale(ctx.locale);
|
|
145
|
+
return new Proxy(ctx, {
|
|
146
|
+
get: function(target, property) {
|
|
147
|
+
if (property === 'remote') return remote;
|
|
148
|
+
if (property === 'locale') return locale;
|
|
149
|
+
if (property === 'get') {
|
|
150
|
+
var get = Reflect.get(target, property, target);
|
|
151
|
+
if (typeof get !== 'function') return get;
|
|
152
|
+
return function(name) {
|
|
153
|
+
var value = get.call(target, name);
|
|
154
|
+
return name === 'connection' ? compatibleConnection(value, remote) : value;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
var value = Reflect.get(target, property, target);
|
|
158
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function decorate(plugin) {
|
|
164
|
+
if (!plugin || typeof plugin !== 'object' || typeof plugin.apply !== 'function') return plugin;
|
|
165
|
+
if (plugin.apply.__visionRouterHostCompat) return plugin;
|
|
166
|
+
var originalApply = plugin.apply;
|
|
167
|
+
function apply(ctx) {
|
|
168
|
+
var args = Array.prototype.slice.call(arguments);
|
|
169
|
+
args[0] = compatibleContext(ctx);
|
|
170
|
+
return originalApply.apply(this, args);
|
|
171
|
+
}
|
|
172
|
+
try { Object.defineProperty(apply, '__visionRouterHostCompat', { value: true }); } catch (_) {}
|
|
173
|
+
plugin.apply = apply;
|
|
174
|
+
return plugin;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function patchLoader(loader) {
|
|
178
|
+
if (!loader || (typeof loader !== 'object' && typeof loader !== 'function')) return;
|
|
179
|
+
if (typeof loader.load === 'function' && !loader.load.__visionRouterHostCompat) {
|
|
180
|
+
var originalLoad = loader.load;
|
|
181
|
+
function load(spec) {
|
|
182
|
+
if (spec && spec.id === TARGET && typeof spec.factory === 'function') {
|
|
183
|
+
var factory = spec.factory;
|
|
184
|
+
spec = Object.assign({}, spec, {
|
|
185
|
+
factory: function(require) { return decorate(factory(require)); }
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return originalLoad.call(this, spec);
|
|
189
|
+
}
|
|
190
|
+
try { Object.defineProperty(load, '__visionRouterHostCompat', { value: true }); } catch (_) {}
|
|
191
|
+
loader.load = load;
|
|
192
|
+
}
|
|
193
|
+
if (typeof loader.create === 'function' && !loader.create.__visionRouterHostCompat) {
|
|
194
|
+
var originalCreate = loader.create;
|
|
195
|
+
function create() {
|
|
196
|
+
var result = originalCreate.apply(this, arguments);
|
|
197
|
+
patchLoader(loader);
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
try { Object.defineProperty(create, '__visionRouterHostCompat', { value: true }); } catch (_) {}
|
|
201
|
+
loader.create = create;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function install() {
|
|
206
|
+
if (window.__ModuleLoader__) {
|
|
207
|
+
patchLoader(window.__ModuleLoader__);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
var descriptor = Object.getOwnPropertyDescriptor(window, '__ModuleLoader__');
|
|
211
|
+
if (descriptor && descriptor.configurable === false) return;
|
|
212
|
+
var stored;
|
|
213
|
+
Object.defineProperty(window, '__ModuleLoader__', {
|
|
214
|
+
configurable: true,
|
|
215
|
+
enumerable: true,
|
|
216
|
+
get: function(){ return stored; },
|
|
217
|
+
set: function(value) {
|
|
218
|
+
stored = value;
|
|
219
|
+
patchLoader(value);
|
|
220
|
+
Object.defineProperty(window, '__ModuleLoader__', {
|
|
221
|
+
configurable: true,
|
|
222
|
+
enumerable: true,
|
|
223
|
+
writable: true,
|
|
224
|
+
value: stored
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
install();
|
|
231
|
+
})();`
|
|
232
|
+
|
|
233
|
+
export function injectClientHostCompatibility(html) {
|
|
234
|
+
if (typeof html !== 'string' || htmlHasScriptMarker(html, CLIENT_HOST_COMPAT_MARK)) return html
|
|
235
|
+
const safe = CLIENT_HOST_COMPAT_PRELUDE.replace(/<\/script/gi, '<\\/script')
|
|
236
|
+
const script = `<script ${CLIENT_HOST_COMPAT_MARK}>${safe}</script>`
|
|
237
|
+
const closeHead = html.indexOf('</head>')
|
|
238
|
+
if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
|
|
239
|
+
return `${html}${script}`
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function installClientHostCompatibility(ctx) {
|
|
243
|
+
ctx?.inject?.(['webServer'], (webCtx) => {
|
|
244
|
+
webCtx.effect(
|
|
245
|
+
() => webCtx.webServer.tapIndex(injectClientHostCompatibility),
|
|
246
|
+
'vision-router: client Host compatibility boundary',
|
|
247
|
+
)
|
|
248
|
+
})
|
|
249
|
+
}
|
|
@@ -1,16 +1,70 @@
|
|
|
1
1
|
import { htmlHasScriptMarker } from './html-script-marker.js'
|
|
2
2
|
import {
|
|
3
|
-
CLIENT_PRESENTATION_PRELUDE,
|
|
3
|
+
CLIENT_PRESENTATION_PRELUDE as BASE_CLIENT_PRESENTATION_PRELUDE,
|
|
4
4
|
resolveVisionModePair,
|
|
5
5
|
} from './client-presentation-boundary-main.js'
|
|
6
|
+
import {
|
|
7
|
+
installClientHostCompatibility,
|
|
8
|
+
} from './client-host-compat-prelude.js'
|
|
6
9
|
import { installVisionModelVisibilityBoundary } from './vision-model-visibility-boundary.js'
|
|
7
10
|
|
|
8
11
|
const CLIENT_PRESENTATION_MARK = 'data-vision-router-presentation-boundary'
|
|
9
12
|
|
|
13
|
+
const VISION_TOGGLE_TEXT_GLYPH = String.raw` React.createElement('span', { 'aria-hidden': 'true', style: { fontSize: 13, lineHeight: 1 } }, '👁'),
|
|
14
|
+
React.createElement('span', null, t('label')),
|
|
15
|
+
active
|
|
16
|
+
? React.createElement('span', {
|
|
17
|
+
'aria-hidden': 'true',
|
|
18
|
+
style: { fontSize: 12, lineHeight: 1, fontWeight: 800, marginLeft: 1 }
|
|
19
|
+
}, '✓')
|
|
20
|
+
: null`
|
|
21
|
+
|
|
22
|
+
const VISION_TOGGLE_FIXED_SVG = String.raw` React.createElement('svg', {
|
|
23
|
+
width: 14,
|
|
24
|
+
height: 14,
|
|
25
|
+
viewBox: '0 0 14 14',
|
|
26
|
+
fill: 'none',
|
|
27
|
+
'aria-hidden': 'true',
|
|
28
|
+
focusable: 'false',
|
|
29
|
+
style: { display: 'block', flex: '0 0 auto' }
|
|
30
|
+
}, React.createElement('path', {
|
|
31
|
+
fillRule: 'evenodd',
|
|
32
|
+
clipRule: 'evenodd',
|
|
33
|
+
d: 'M7 2.25c-2.84 0-5.04 1.69-6.25 4.25a1.15 1.15 0 0 0 0 1C1.96 10.06 4.16 11.75 7 11.75s5.04-1.69 6.25-4.25a1.15 1.15 0 0 0 0-1C12.04 3.94 9.84 2.25 7 2.25Zm0 1.25c2.16 0 3.96 1.21 5.05 3.5C10.96 9.29 9.16 10.5 7 10.5S3.04 9.29 1.95 7C3.04 4.71 4.84 3.5 7 3.5Zm0 1.25A2.25 2.25 0 1 0 7 9.25a2.25 2.25 0 0 0 0-4.5Zm0 1.25a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z',
|
|
34
|
+
fill: 'currentColor'
|
|
35
|
+
})),
|
|
36
|
+
React.createElement('span', null, t('label')),
|
|
37
|
+
active
|
|
38
|
+
? React.createElement('svg', {
|
|
39
|
+
width: 12,
|
|
40
|
+
height: 12,
|
|
41
|
+
viewBox: '0 0 14 14',
|
|
42
|
+
fill: 'none',
|
|
43
|
+
'aria-hidden': 'true',
|
|
44
|
+
focusable: 'false',
|
|
45
|
+
style: { display: 'block', flex: '0 0 auto', marginLeft: 1 }
|
|
46
|
+
}, React.createElement('path', {
|
|
47
|
+
d: 'M2.75 7.15 5.6 10 11.25 4.35',
|
|
48
|
+
stroke: 'currentColor',
|
|
49
|
+
strokeWidth: 1.5,
|
|
50
|
+
strokeLinecap: 'round',
|
|
51
|
+
strokeLinejoin: 'round'
|
|
52
|
+
}))
|
|
53
|
+
: null`
|
|
54
|
+
|
|
55
|
+
export function transformVisionModeToggleIcons(source = BASE_CLIENT_PRESENTATION_PRELUDE) {
|
|
56
|
+
const input = String(source)
|
|
57
|
+
if (!input.includes(VISION_TOGGLE_TEXT_GLYPH)) {
|
|
58
|
+
throw new Error('vision toggle icon transform anchor missing')
|
|
59
|
+
}
|
|
60
|
+
return input.replace(VISION_TOGGLE_TEXT_GLYPH, VISION_TOGGLE_FIXED_SVG)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const CLIENT_PRESENTATION_PRELUDE = transformVisionModeToggleIcons()
|
|
64
|
+
|
|
10
65
|
export {
|
|
11
|
-
CLIENT_PRESENTATION_PRELUDE,
|
|
12
66
|
resolveVisionModePair,
|
|
13
|
-
}
|
|
67
|
+
}
|
|
14
68
|
|
|
15
69
|
export function injectClientPresentationBoundary(html) {
|
|
16
70
|
if (typeof html !== 'string' || htmlHasScriptMarker(html, CLIENT_PRESENTATION_MARK)) return html
|
|
@@ -28,6 +82,10 @@ export function installClientPresentationBoundary(ctx) {
|
|
|
28
82
|
'vision-router: client presentation boundary',
|
|
29
83
|
)
|
|
30
84
|
})
|
|
85
|
+
// Keep Host-version compatibility in its own capability-detected prelude.
|
|
86
|
+
// This lets the large legacy client factory stay byte-stable while alpha/new
|
|
87
|
+
// Hosts expose their current Remote/Connection seams to it.
|
|
88
|
+
installClientHostCompatibility(ctx)
|
|
31
89
|
// #286 keeps real wrapper routes registered while hiding only confidently
|
|
32
90
|
// owned wrappers from DSH's stock model-selection presentation. Compose that
|
|
33
91
|
// presentation boundary here so v2 can retain its hardened script-marker
|
package/lib/client.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// dsh-vision-router browser half:
|
|
2
|
-
//
|
|
3
|
-
// `vision-router` settings namespace. Self-contained
|
|
1
|
+
// dsh-vision-router browser half: one first-class Settings > Vision Router page
|
|
2
|
+
// backed by the host-owned `vision-router` settings namespace. Self-contained
|
|
4
3
|
// by hand (no bundler in this repo): the client module system wraps it in a
|
|
5
4
|
// CJS factory and the kernel adopts { apply, inject } as a client plugin.
|
|
6
5
|
window.__ModuleLoader__.load({
|
|
@@ -56,9 +55,6 @@ window.__ModuleLoader__.load({
|
|
|
56
55
|
remoteSettingsProxyBody: '远程设置路由返回 404。若使用 Nginx/Caddy/其他反向代理,请同时转发 /vision-router-settings/* 到 DSH。',
|
|
57
56
|
remoteSafeScopeHint: '远程页面只允许修改低风险偏好;网络、凭据、本地后端、产物目录、桌面截图和宿主路由等设置只能在 DSH 本机修改。',
|
|
58
57
|
settingsConflict: '设置已被另一端修改。已刷新最新配置,请确认你的修改后重新保存。',
|
|
59
|
-
legacyMovedTitle: 'Vision Router 设置已迁移',
|
|
60
|
-
legacyMovedBody: '主设置入口现在位于「设置 → Vision Router」。此处仅保留兼容入口,不再维护第二份可编辑表单。',
|
|
61
|
-
legacyOpen: '打开 Vision Router 设置',
|
|
62
58
|
overridden: '已覆盖',
|
|
63
59
|
reset: '恢复默认',
|
|
64
60
|
toggleInstantDescribe: '即时本地识图',
|
|
@@ -255,13 +251,14 @@ window.__ModuleLoader__.load({
|
|
|
255
251
|
guidanceOverridePlaceholder: '输入自定义引导语(留空 = 使用内置)',
|
|
256
252
|
addGuidanceOverride: '+ 添加一条自定义引导',
|
|
257
253
|
selectKind: '选择图片类型…',
|
|
258
|
-
visionDepthFast: '
|
|
259
|
-
visionDepthStandard: '
|
|
260
|
-
visionDepthDeep: '
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
254
|
+
visionDepthFast: '快速(优先整体判断)',
|
|
255
|
+
visionDepthStandard: '标准(按需查证,默认)',
|
|
256
|
+
visionDepthDeep: '细致(主动交叉验证)',
|
|
257
|
+
hintVisionDepth: '看图深度只决定识图策略,不限制调用次数:快速优先整体判断,标准按问题需要查证,细致会主动做更多局部检查与交叉验证。如需限制调用次数,请启用下方「限制深挖次数」。',
|
|
258
|
+
depthCapTitle: '限制深挖次数',
|
|
259
|
+
depthCapHint: '默认关闭,不限制视觉证据调用次数。启用后可设置本轮最多允许多少次成功的深挖证据调用;bootstrap 预识别不计入,失败或空证据调用不占次数。',
|
|
260
|
+
depthCapValueLabel: '最多深挖次数',
|
|
261
|
+
depthCapInvalid: '请输入 1–100 之间的整数。',
|
|
265
262
|
numHintTimeoutMs: '单个视觉请求超时;默认 120000。',
|
|
266
263
|
numHintVisionTaskTimeoutMs: '一次识图任务(含全部 provider、回退与重试)共享的总时限;默认 45000。认证失败/限流会立即熔断对应后端。',
|
|
267
264
|
numHintOcrTimeoutMs: '一次 OCR 任务的总时限;本地 tesseract 最多用 12 秒,视觉模型回退只用剩余部分;默认 30000。',
|
|
@@ -332,9 +329,6 @@ window.__ModuleLoader__.load({
|
|
|
332
329
|
remoteSettingsProxyBody: 'The remote-settings route returned 404. If DSH is behind Nginx, Caddy, or another reverse proxy, also forward /vision-router-settings/* to DSH.',
|
|
333
330
|
remoteSafeScopeHint: 'Remote pages can change low-risk preferences only. Network, credentials, local backends, artifact paths, desktop capture, and host routing remain loopback-only.',
|
|
334
331
|
settingsConflict: 'Settings changed on another client. The latest values were refreshed; review your draft and save again.',
|
|
335
|
-
legacyMovedTitle: 'Vision Router settings moved',
|
|
336
|
-
legacyMovedBody: 'The canonical editor is now Settings → Vision Router. This compatibility entry no longer keeps a second editable form.',
|
|
337
|
-
legacyOpen: 'Open Vision Router settings',
|
|
338
332
|
overridden: 'Overridden',
|
|
339
333
|
reset: 'Reset',
|
|
340
334
|
toggleInstantDescribe: 'Instant local image recognition',
|
|
@@ -534,13 +528,14 @@ window.__ModuleLoader__.load({
|
|
|
534
528
|
guidanceOverridePlaceholder: 'Enter custom guidance (empty = built-in)',
|
|
535
529
|
addGuidanceOverride: '+ Add a custom guidance line',
|
|
536
530
|
selectKind: 'Select image type…',
|
|
537
|
-
visionDepthFast: 'Quick (
|
|
538
|
-
visionDepthStandard: 'Standard (
|
|
539
|
-
visionDepthDeep: 'Thorough (
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
531
|
+
visionDepthFast: 'Quick (overall-first)',
|
|
532
|
+
visionDepthStandard: 'Standard (evidence as needed, default)',
|
|
533
|
+
visionDepthDeep: 'Thorough (proactive cross-checking)',
|
|
534
|
+
hintVisionDepth: 'Vision depth chooses the inspection strategy, not a call-count limit: Quick stays overall-first, Standard verifies evidence as needed, and Thorough proactively inspects details and cross-checks important claims. To cap calls, enable “Limit deep-dive calls” below.',
|
|
535
|
+
depthCapTitle: 'Limit deep-dive calls',
|
|
536
|
+
depthCapHint: 'Off by default, so visual evidence calls are unlimited. Enable this to cap successful deep-evidence calls for the turn. The bootstrap pre-scan does not count, and failed or empty-evidence calls do not consume the cap.',
|
|
537
|
+
depthCapValueLabel: 'Maximum deep-dive calls',
|
|
538
|
+
depthCapInvalid: 'Enter an integer from 1 to 100.',
|
|
544
539
|
numHintTimeoutMs: 'Per vision-call deadline; default 120000.',
|
|
545
540
|
numHintVisionTaskTimeoutMs: 'One vision task (all providers, fallbacks and retries) shares this wall-clock budget; default 45000. Auth failures and rate limits trip their backend immediately.',
|
|
546
541
|
numHintOcrTimeoutMs: 'Total budget for one OCR task: tesseract gets at most 12s, the vision fallback only the remainder; default 30000.',
|
|
@@ -1196,11 +1191,13 @@ window.__ModuleLoader__.load({
|
|
|
1196
1191
|
if (provider === '' || model === '') return undefined
|
|
1197
1192
|
return { provider, model }
|
|
1198
1193
|
}
|
|
1199
|
-
function parseNumber(text, min) {
|
|
1194
|
+
function parseNumber(text, min, max) {
|
|
1200
1195
|
const trimmed = String(text ?? '').trim()
|
|
1201
1196
|
if (trimmed === '') return { clear: true }
|
|
1202
1197
|
const parsed = Number(trimmed)
|
|
1203
|
-
return Number.isInteger(parsed) && parsed >= min
|
|
1198
|
+
return Number.isInteger(parsed) && parsed >= min && (max === undefined || parsed <= max)
|
|
1199
|
+
? { value: parsed }
|
|
1200
|
+
: undefined
|
|
1204
1201
|
}
|
|
1205
1202
|
|
|
1206
1203
|
// ── styles: mirrors the built-in plugin cards (same design tokens) ──────
|
|
@@ -1768,8 +1765,7 @@ window.__ModuleLoader__.load({
|
|
|
1768
1765
|
return list.find(guideElementUsable)
|
|
1769
1766
|
}
|
|
1770
1767
|
// The walkthrough has one canonical destination: the first-class
|
|
1771
|
-
// Vision Router settings section
|
|
1772
|
-
// for compatibility, but onboarding never teaches two competing paths.
|
|
1768
|
+
// Vision Router settings section registered below.
|
|
1773
1769
|
function guideVisionRouterNav() {
|
|
1774
1770
|
const panel = guideSettingsPanel()
|
|
1775
1771
|
if (!panel) return undefined
|
|
@@ -2758,7 +2754,8 @@ window.__ModuleLoader__.load({
|
|
|
2758
2754
|
}
|
|
2759
2755
|
if (key === 'localDescribeStyle') return value === 'structured' ? 'structured' : 'plain'
|
|
2760
2756
|
if (SELECT_KEYS.includes(key)) {
|
|
2761
|
-
|
|
2757
|
+
if (value === 'custom') return 'standard'
|
|
2758
|
+
return value === 'fast' || value === 'standard' || value === 'deep' ? value : 'standard'
|
|
2762
2759
|
}
|
|
2763
2760
|
return typeof value === 'string' ? value : ''
|
|
2764
2761
|
}
|
|
@@ -2785,7 +2782,9 @@ window.__ModuleLoader__.load({
|
|
|
2785
2782
|
const value = parseTextProvider(text)
|
|
2786
2783
|
return value === undefined ? undefined : { value }
|
|
2787
2784
|
}
|
|
2788
|
-
if (NUMBER_KEYS.includes(key) || DEPTH_NUMBER_KEYS.includes(key))
|
|
2785
|
+
if (NUMBER_KEYS.includes(key) || DEPTH_NUMBER_KEYS.includes(key)) {
|
|
2786
|
+
return parseNumber(text, NUMBER_META[key].min, NUMBER_META[key].max)
|
|
2787
|
+
}
|
|
2789
2788
|
if (key === 'guidanceOverrides') {
|
|
2790
2789
|
const rows = Array.isArray(text) ? text : []
|
|
2791
2790
|
const cleaned = []
|
|
@@ -2815,7 +2814,7 @@ window.__ModuleLoader__.load({
|
|
|
2815
2814
|
return text === 'structured' || text === 'plain' ? { value: text } : undefined
|
|
2816
2815
|
}
|
|
2817
2816
|
if (SELECT_KEYS.includes(key)) {
|
|
2818
|
-
return text === 'fast' || text === 'standard' || text === 'deep'
|
|
2817
|
+
return text === 'fast' || text === 'standard' || text === 'deep' ? { value: text } : undefined
|
|
2819
2818
|
}
|
|
2820
2819
|
if (key === 'proxyHosts') {
|
|
2821
2820
|
const list = String(text ?? '')
|
|
@@ -3136,6 +3135,7 @@ window.__ModuleLoader__.load({
|
|
|
3136
3135
|
),
|
|
3137
3136
|
h('select', {
|
|
3138
3137
|
className: 'vr-input vr-select' + (invalidField ? ' vr-input-invalid' : ''),
|
|
3138
|
+
'data-vr-depth-strategy': key === 'visionDepth' ? '1' : undefined,
|
|
3139
3139
|
value: format(key), disabled: editBlocked,
|
|
3140
3140
|
onChange: (event) => setDraft(key, event.target.value),
|
|
3141
3141
|
},
|
|
@@ -3148,6 +3148,44 @@ window.__ModuleLoader__.load({
|
|
|
3148
3148
|
: null,
|
|
3149
3149
|
)
|
|
3150
3150
|
}
|
|
3151
|
+
const depthCapField = () => {
|
|
3152
|
+
const key = 'visionDepthMaxCalls'
|
|
3153
|
+
const raw = format(key)
|
|
3154
|
+
const parsed = Number(raw)
|
|
3155
|
+
const enabled = Number.isInteger(parsed) && parsed > 0
|
|
3156
|
+
const invalidField = key in drafts && parse(key, drafts[key]) === undefined
|
|
3157
|
+
const savedRaw = Number(readValue(snapshot, key))
|
|
3158
|
+
const savedCap = Number.isInteger(savedRaw) && savedRaw >= 1 && savedRaw <= 100 ? savedRaw : 4
|
|
3159
|
+
return h('div', { className: 'vr-field', key, 'data-vr-depth-cap': '1' },
|
|
3160
|
+
h('div', { className: 'vr-field-head' },
|
|
3161
|
+
h('div', { className: 'vr-toggle' },
|
|
3162
|
+
h('span', { className: 'vr-label' }, t('depthCapTitle')),
|
|
3163
|
+
h('input', {
|
|
3164
|
+
type: 'checkbox', className: 'vr-check', checked: enabled,
|
|
3165
|
+
'data-vr-depth-cap-toggle': '1', disabled: editBlocked,
|
|
3166
|
+
onChange: (event) => setDraft(key, event.target.checked ? String(savedCap) : '0'),
|
|
3167
|
+
}),
|
|
3168
|
+
),
|
|
3169
|
+
overriddenBadge(key),
|
|
3170
|
+
),
|
|
3171
|
+
h('p', { className: 'vr-hint' }, t('depthCapHint')),
|
|
3172
|
+
enabled
|
|
3173
|
+
? h('div', { className: 'vr-local-row' },
|
|
3174
|
+
h('label', { className: 'vr-label vr-local-label' }, t('depthCapValueLabel')),
|
|
3175
|
+
h('input', {
|
|
3176
|
+
className: 'vr-input' + (invalidField ? ' vr-input-invalid' : ''),
|
|
3177
|
+
type: 'number', min: 1, max: 100, step: 1, value: raw,
|
|
3178
|
+
'data-vr-depth-cap-value': '1', disabled: editBlocked,
|
|
3179
|
+
onChange: (event) => setDraft(key, event.target.value),
|
|
3180
|
+
}),
|
|
3181
|
+
invalidField
|
|
3182
|
+
? h('p', { className: 'vr-invalid' }, t('depthCapInvalid'))
|
|
3183
|
+
: null,
|
|
3184
|
+
)
|
|
3185
|
+
: null,
|
|
3186
|
+
)
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3151
3189
|
// 自定义识图引导编辑器:每行 [图片类型 select] [引导语 input] [移除],+ 添加。
|
|
3152
3190
|
const GUIDANCE_KIND_OPTIONS = [
|
|
3153
3191
|
{ value: 'code', label: 'code(代码)' },
|
|
@@ -3948,14 +3986,11 @@ window.__ModuleLoader__.load({
|
|
|
3948
3986
|
{ value: 'fast', label: t('visionDepthFast') },
|
|
3949
3987
|
{ value: 'standard', label: t('visionDepthStandard') },
|
|
3950
3988
|
{ value: 'deep', label: t('visionDepthDeep') },
|
|
3951
|
-
{ value: 'custom', label: t('visionDepthCustom') },
|
|
3952
3989
|
])),
|
|
3953
|
-
format('visionDepth') === 'custom'
|
|
3954
|
-
? DEPTH_NUMBER_KEYS.map((key) => textField(key, t(LABEL_KEY[key]), t(HINT_KEY[key]), false))
|
|
3955
|
-
: null,
|
|
3956
3990
|
guidanceOverridesEditor(),
|
|
3957
3991
|
)
|
|
3958
3992
|
: null,
|
|
3993
|
+
depthCapField(),
|
|
3959
3994
|
// Local vision is a primary capability. Keep the heavy editors
|
|
3960
3995
|
// collapsed by default, but make the entry obvious at a glance.
|
|
3961
3996
|
!remoteMode ? h('div', {
|
|
@@ -4214,23 +4249,6 @@ window.__ModuleLoader__.load({
|
|
|
4214
4249
|
)
|
|
4215
4250
|
}
|
|
4216
4251
|
|
|
4217
|
-
function VisionRouterLegacyEntry(props) {
|
|
4218
|
-
const t = props.t
|
|
4219
|
-
const h = React.createElement
|
|
4220
|
-
return h('li', { className: 'vr-card vr-card-open' },
|
|
4221
|
-
h('div', { className: 'vr-body' },
|
|
4222
|
-
h('div', { className: 'vr-quickstart' },
|
|
4223
|
-
h('div', { className: 'vr-quickstart-title' }, t('legacyMovedTitle')),
|
|
4224
|
-
h('p', { className: 'vr-quickstart-body' }, t('legacyMovedBody')),
|
|
4225
|
-
h('button', {
|
|
4226
|
-
type: 'button', className: 'vr-btn vr-btn-save',
|
|
4227
|
-
onClick: () => { guideHostUi.openVisionRouter() },
|
|
4228
|
-
}, t('legacyOpen')),
|
|
4229
|
-
),
|
|
4230
|
-
),
|
|
4231
|
-
)
|
|
4232
|
-
}
|
|
4233
|
-
|
|
4234
4252
|
function apply(ctx) {
|
|
4235
4253
|
const localScope = ctx.settingsScope.bind({ namespace: 'vision-router' })
|
|
4236
4254
|
const presentedImageUrls = new Map()
|
|
@@ -4365,11 +4383,9 @@ window.__ModuleLoader__.load({
|
|
|
4365
4383
|
// this identity stays fixed across slot renders.
|
|
4366
4384
|
const subscribeConnectionReset = (listener) => ctx.on('connection/reset', listener)
|
|
4367
4385
|
const cardInject = { getConnection, t, locale: ctx.locale, remote: ctx.remote, subscribeConnectionReset }
|
|
4368
|
-
//
|
|
4369
|
-
//
|
|
4370
|
-
// the legacy plugin card remains a compatibility shell over DSH's local scope.
|
|
4386
|
+
// Vision Router owns one canonical settings surface. The section switches
|
|
4387
|
+
// to the opt-in remote scope on a non-loopback client.
|
|
4371
4388
|
const sectionCardInject = Object.freeze({ ...cardInject, scope: primaryScope, surface: 'section' })
|
|
4372
|
-
const legacyEntryInject = Object.freeze({ t })
|
|
4373
4389
|
const VisionRouterSettingsSection = (sectionProps) =>
|
|
4374
4390
|
React.createElement(
|
|
4375
4391
|
'ul',
|
|
@@ -4392,23 +4408,6 @@ window.__ModuleLoader__.load({
|
|
|
4392
4408
|
}),
|
|
4393
4409
|
'vision-router: primary settings section',
|
|
4394
4410
|
)
|
|
4395
|
-
ctx.effect(
|
|
4396
|
-
() =>
|
|
4397
|
-
ctx.slots.inject('settings.plugin.item', function* () {
|
|
4398
|
-
yield ctx.slots.register(
|
|
4399
|
-
{
|
|
4400
|
-
name: 'settings.plugin.item',
|
|
4401
|
-
key: 'vision-router',
|
|
4402
|
-
id: 'vision-router',
|
|
4403
|
-
order: 30,
|
|
4404
|
-
label: () => t('nav'),
|
|
4405
|
-
inject: () => legacyEntryInject,
|
|
4406
|
-
},
|
|
4407
|
-
VisionRouterLegacyEntry,
|
|
4408
|
-
)
|
|
4409
|
-
}),
|
|
4410
|
-
'vision-router: legacy plugin settings card',
|
|
4411
|
-
)
|
|
4412
4411
|
ctx.effect(
|
|
4413
4412
|
() =>
|
|
4414
4413
|
ctx.slots.inject('tool.call.toolview', function* () {
|