dsh-vision-router 2.2.7 → 2.3.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 +8 -8
- package/README.zh.md +8 -8
- package/docs/architecture/2.3.0-compat-retirement.md +147 -0
- package/docs/architecture/2.3.0-execution-plan.md +410 -0
- package/docs/architecture/2.3.0-final-audit.md +279 -0
- package/docs/architecture/2.3.0-reopened-plan.md +720 -0
- package/docs/architecture/compat-inventory.md +26 -34
- package/docs/architecture/dsh-020-rc2-adaptation.md +23 -0
- package/docs/architecture/dsh-compatibility-matrix.md +65 -54
- package/docs/architecture/dsh-support-window.md +29 -21
- package/docs/architecture/final-closure-audit.md +2 -0
- package/docs/architecture/host-first-proxy-convergence.md +1 -1
- package/docs/releases/v2.2.8.md +25 -0
- package/docs/releases/v2.3.0.md +53 -0
- package/entry.js +8 -56
- package/index.js +161 -691
- package/lib/catalog-corrections.js +1 -2
- package/lib/client-host-compat-prelude.js +7 -12
- package/lib/client-maintenance-actions.js +145 -0
- package/lib/client-presentation-boundary-main.js +82 -32
- package/lib/client-presentation-boundary.js +60 -40
- package/lib/client-script-carrier.js +90 -0
- package/lib/client.js +248 -146
- package/lib/core-apply-lifecycle.js +47 -0
- package/lib/core-primitives.js +88 -135
- package/lib/desktop-screenshot-tool.js +262 -0
- package/lib/dsh-contract-compat.js +11 -26
- package/lib/dsh-support-window.js +4 -4
- package/lib/guide-vision-toggle-highlight.js +7 -10
- package/lib/http-compat.js +1 -2
- package/lib/live-model-client-prelude.js +7 -14
- package/lib/local-remote-settings-permission.js +41 -16
- package/lib/maintenance-routes.js +136 -0
- package/lib/public-config.js +57 -0
- package/lib/public-entry.js +2 -7
- package/lib/remote-settings-bridge.js +11 -0
- package/lib/remote-settings-risk-client-boundary.js +155 -0
- package/lib/remote-settings-risk-confirmation.js +17 -105
- package/lib/runtime-composition.js +105 -30
- package/lib/runtime-i18n-boundary.js +10 -11
- package/lib/session-message-source-compat.js +7 -3
- package/lib/session-vision-index.js +154 -9
- package/lib/session-vision-mode-boundary.js +43 -8
- package/lib/settings-client-loader-lifecycle.js +129 -0
- package/lib/settings-factory-lifecycle.js +25 -14
- package/lib/settings-ia-client-prelude.js +11 -11
- package/lib/settings-limit-client-prelude.js +7 -9
- package/lib/sharp-runtime.js +0 -7
- package/lib/strict-live-model-client-prelude.js +7 -10
- package/lib/structured-flow-hardening.js +3 -2
- package/lib/v2-settings-ia-integration.js +21 -6
- package/lib/vision-backend-runtime-policy.js +3 -2
- package/lib/vision-background-benchmark.js +3 -5
- package/lib/vision-capability-benchmark-client.js +7 -10
- package/lib/vision-capability-benchmark-service.js +1 -0
- package/lib/vision-diagnostics-routes.js +216 -0
- package/lib/vision-evidence-guidance.js +3 -1
- package/lib/vision-exact-check-client.js +11 -6
- package/lib/vision-model-visibility-boundary-main.js +7 -11
- package/lib/vision-model-visibility-boundary.js +7 -12
- package/lib/vision-provider-transport.js +0 -29
- package/lib/vision-routing-settings-prelude.js +7 -11
- package/lib/vision-toggle-root-hardening.js +50 -8
- package/lib/vision-tool-runtime-boundary.js +5 -1
- package/lib/vision-turn-budget-client-prelude.js +7 -10
- package/lib/web/remote-settings-client.js +9 -14
- package/package.json +23 -13
- package/lib/android-attachment-compat.js +0 -244
- package/lib/settings-client-rc8-lifecycle.js +0 -402
|
@@ -134,14 +134,26 @@ function disposeHeldRestriction(held) {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
function releaseAgentToolRestriction(agent, restrictions) {
|
|
137
|
+
function releaseAgentToolRestriction(agent, restrictions, activeRestrictions) {
|
|
138
138
|
if (!isObject(agent)) return
|
|
139
139
|
const held = restrictions.get(agent)
|
|
140
140
|
if (!held) return
|
|
141
141
|
disposeHeldRestriction(held)
|
|
142
|
+
activeRestrictions?.delete(held)
|
|
142
143
|
restrictions.delete(agent)
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
function rememberAgentToolRestriction(agent, restrictions, activeRestrictions, held) {
|
|
147
|
+
restrictions.set(agent, held)
|
|
148
|
+
activeRestrictions?.add(held)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function releaseAllAgentToolRestrictions(activeRestrictions) {
|
|
152
|
+
if (!(activeRestrictions instanceof Set)) return
|
|
153
|
+
for (const held of activeRestrictions) disposeHeldRestriction(held)
|
|
154
|
+
activeRestrictions.clear()
|
|
155
|
+
}
|
|
156
|
+
|
|
145
157
|
function restrictionFailureLabel(error) {
|
|
146
158
|
const name =
|
|
147
159
|
typeof error?.name === 'string' && error.name !== ''
|
|
@@ -165,10 +177,10 @@ function warnRestrictionFailure(agent, error, deny, failed = deny) {
|
|
|
165
177
|
} catch {}
|
|
166
178
|
}
|
|
167
179
|
|
|
168
|
-
function syncAgentToolRestriction(agent, enabled, restrictions, owned, registryTools) {
|
|
180
|
+
function syncAgentToolRestriction(agent, enabled, restrictions, owned, registryTools, activeRestrictions) {
|
|
169
181
|
if (!isObject(agent)) return
|
|
170
182
|
if (enabled) {
|
|
171
|
-
releaseAgentToolRestriction(agent, restrictions)
|
|
183
|
+
releaseAgentToolRestriction(agent, restrictions, activeRestrictions)
|
|
172
184
|
return
|
|
173
185
|
}
|
|
174
186
|
|
|
@@ -181,19 +193,19 @@ function syncAgentToolRestriction(agent, enabled, restrictions, owned, registryT
|
|
|
181
193
|
const deny = restrictableOwnedVisionToolNames(registryTools, owned)
|
|
182
194
|
if (typeof restrict !== 'function') return
|
|
183
195
|
if (deny.length === 0) {
|
|
184
|
-
releaseAgentToolRestriction(agent, restrictions)
|
|
196
|
+
releaseAgentToolRestriction(agent, restrictions, activeRestrictions)
|
|
185
197
|
return
|
|
186
198
|
}
|
|
187
199
|
|
|
188
200
|
const held = restrictions.get(agent)
|
|
189
201
|
const signature = deny.join('\u0000')
|
|
190
202
|
if (held?.signature === signature) return
|
|
191
|
-
releaseAgentToolRestriction(agent, restrictions)
|
|
203
|
+
releaseAgentToolRestriction(agent, restrictions, activeRestrictions)
|
|
192
204
|
|
|
193
205
|
try {
|
|
194
206
|
// Current DSH can mask all inherited plugin tools in one scoped operation.
|
|
195
207
|
const dispose = restrict.call(tools, { deny })
|
|
196
|
-
|
|
208
|
+
rememberAgentToolRestriction(agent, restrictions, activeRestrictions, {
|
|
197
209
|
signature,
|
|
198
210
|
disposers: typeof dispose === 'function' ? [dispose] : [],
|
|
199
211
|
})
|
|
@@ -212,7 +224,9 @@ function syncAgentToolRestriction(agent, enabled, restrictions, owned, registryT
|
|
|
212
224
|
failed.push(name)
|
|
213
225
|
}
|
|
214
226
|
}
|
|
215
|
-
if (disposers.length > 0)
|
|
227
|
+
if (disposers.length > 0) {
|
|
228
|
+
rememberAgentToolRestriction(agent, restrictions, activeRestrictions, { signature, disposers })
|
|
229
|
+
}
|
|
216
230
|
if (failed.length > 0 || disposers.length === 0) {
|
|
217
231
|
warnRestrictionFailure(agent, combinedError, deny, failed.length > 0 ? failed : deny)
|
|
218
232
|
}
|
|
@@ -311,6 +325,7 @@ function wrapTools(tools, ctx, config, owned) {
|
|
|
311
325
|
export function installSessionVisionModeBoundary(ctx, config = {}) {
|
|
312
326
|
if (!isObject(ctx)) return { ctx, config }
|
|
313
327
|
const restrictions = new WeakMap()
|
|
328
|
+
const activeRestrictions = new Set()
|
|
314
329
|
const assemblySnapshots = new WeakMap()
|
|
315
330
|
const ownedVisionTools = new Map()
|
|
316
331
|
let toolsView
|
|
@@ -335,6 +350,7 @@ export function installSessionVisionModeBoundary(ctx, config = {}) {
|
|
|
335
350
|
restrictions,
|
|
336
351
|
ownedVisionTools,
|
|
337
352
|
ctx?.tools,
|
|
353
|
+
activeRestrictions,
|
|
338
354
|
)
|
|
339
355
|
}
|
|
340
356
|
|
|
@@ -354,6 +370,7 @@ export function installSessionVisionModeBoundary(ctx, config = {}) {
|
|
|
354
370
|
restrictions,
|
|
355
371
|
ownedVisionTools,
|
|
356
372
|
ctx?.tools,
|
|
373
|
+
activeRestrictions,
|
|
357
374
|
)
|
|
358
375
|
}
|
|
359
376
|
return authority
|
|
@@ -419,13 +436,30 @@ export function installSessionVisionModeBoundary(ctx, config = {}) {
|
|
|
419
436
|
})
|
|
420
437
|
ctx.on?.('agent/disposed', ({ agent }) => {
|
|
421
438
|
clearAgent(agent)
|
|
422
|
-
releaseAgentToolRestriction(agent, restrictions)
|
|
439
|
+
releaseAgentToolRestriction(agent, restrictions, activeRestrictions)
|
|
423
440
|
})
|
|
424
441
|
} catch {
|
|
425
442
|
// Minimum Hosts may not expose every lifecycle event. Prompt assembly and
|
|
426
443
|
// execution guards preserve the safety boundary without making apply fail.
|
|
427
444
|
}
|
|
428
445
|
|
|
446
|
+
// tools.restrict() is invoked through the long-lived Agent scope so its
|
|
447
|
+
// disposer belongs to that scope, not to this plugin fiber. A Settings/HMR
|
|
448
|
+
// recompose can therefore retire this boundary while the Agent survives. If
|
|
449
|
+
// we do not release those masks here, the next boundary generation cannot
|
|
450
|
+
// discover or dispose the old WeakMap entries and Vision tools stay hidden
|
|
451
|
+
// even after the Session selects a Router-owned route again.
|
|
452
|
+
try {
|
|
453
|
+
if (typeof ctx.effect === 'function') {
|
|
454
|
+
ctx.effect(
|
|
455
|
+
() => () => releaseAllAgentToolRestrictions(activeRestrictions),
|
|
456
|
+
'vision-router: Session Vision restriction lifecycle',
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
} catch {
|
|
460
|
+
// Agent disposal / live enable transitions still release their own masks.
|
|
461
|
+
}
|
|
462
|
+
|
|
429
463
|
const wrapped = new Proxy(ctx, {
|
|
430
464
|
get(target, property) {
|
|
431
465
|
if (property === 'tools') {
|
|
@@ -467,6 +501,7 @@ export function installSessionVisionModeBoundary(ctx, config = {}) {
|
|
|
467
501
|
restrictions,
|
|
468
502
|
ownedVisionTools,
|
|
469
503
|
target?.tools,
|
|
504
|
+
activeRestrictions,
|
|
470
505
|
)
|
|
471
506
|
return runWithSessionVisionModeAuthority(
|
|
472
507
|
authority,
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
|
|
2
|
+
import {
|
|
3
|
+
LOCAL_PERMISSION_CLIENT_BOUNDARY_SOURCE,
|
|
4
|
+
LOCAL_REMOTE_SETTINGS_PERMISSION_FIELD,
|
|
5
|
+
LOCAL_REMOTE_SETTINGS_PERMISSION_PATH,
|
|
6
|
+
} from './local-remote-settings-permission.js'
|
|
7
|
+
import { REMOTE_SETTINGS_RISK_CLIENT_BOUNDARY_SOURCE } from './remote-settings-risk-client-boundary.js'
|
|
8
|
+
|
|
9
|
+
// Keep the legacy marker value so mixed-generation/HMR pages do not install
|
|
10
|
+
// a second lifecycle wrapper after the source/API rename.
|
|
11
|
+
const SETTINGS_CLIENT_LOADER_LIFECYCLE_MARK = 'data-vision-router-settings-rc8-lifecycle'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Supported Hosts can replace ModuleLoader.load() inside loader.create(). This
|
|
15
|
+
* compatibility owner only re-attaches the current local-permission and
|
|
16
|
+
* remote-risk product boundaries after that queue-to-live transition; product
|
|
17
|
+
* semantics stay in their capability owners.
|
|
18
|
+
*/
|
|
19
|
+
export const SETTINGS_CLIENT_LOADER_LIFECYCLE_PRELUDE = String.raw`(function(){
|
|
20
|
+
'use strict';
|
|
21
|
+
var TARGET = 'dsh-vision-router';
|
|
22
|
+
${LOCAL_PERMISSION_CLIENT_BOUNDARY_SOURCE}
|
|
23
|
+
var localPermissionBoundary = createLocalRemoteSettingsPermissionClientBoundary({
|
|
24
|
+
endpoint: '${LOCAL_REMOTE_SETTINGS_PERMISSION_PATH}',
|
|
25
|
+
field: '${LOCAL_REMOTE_SETTINGS_PERMISSION_FIELD}',
|
|
26
|
+
fetchImpl: typeof fetch === 'function'
|
|
27
|
+
? function(url, options) { return fetch(url, options); }
|
|
28
|
+
: undefined
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
${REMOTE_SETTINGS_RISK_CLIENT_BOUNDARY_SOURCE}
|
|
32
|
+
var riskBoundary = createRemoteSettingsRiskClientBoundary({
|
|
33
|
+
confirmImpl: function(message) {
|
|
34
|
+
return typeof window.confirm === 'function' && window.confirm(message) === true;
|
|
35
|
+
},
|
|
36
|
+
alertImpl: function(message) {
|
|
37
|
+
try { if (typeof window.alert === 'function') window.alert(message); } catch (_) {}
|
|
38
|
+
},
|
|
39
|
+
locale: function() {
|
|
40
|
+
try {
|
|
41
|
+
return String((document && document.documentElement && document.documentElement.lang)
|
|
42
|
+
|| (navigator && navigator.language) || '');
|
|
43
|
+
} catch (_) { return ''; }
|
|
44
|
+
},
|
|
45
|
+
location: function() {
|
|
46
|
+
try { return window && window.location; } catch (_) { return undefined; }
|
|
47
|
+
},
|
|
48
|
+
normalizeLoopback: true
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function patchLiveLoader(loader) {
|
|
52
|
+
if (!loader || typeof loader.load !== 'function' || loader.load.__visionRouterSettingsRc8Lifecycle) return;
|
|
53
|
+
var original = loader.load;
|
|
54
|
+
function load(spec) {
|
|
55
|
+
if (spec && spec.id === TARGET && typeof spec.factory === 'function') {
|
|
56
|
+
var factory = spec.factory;
|
|
57
|
+
spec = Object.assign({}, spec, {
|
|
58
|
+
factory: function(require) {
|
|
59
|
+
var exports = factory(require);
|
|
60
|
+
if (exports && typeof exports.apply === 'function' && !exports.apply.__visionRouterSettingsRc8Lifecycle) {
|
|
61
|
+
var apply = exports.apply;
|
|
62
|
+
var wrappedApply = function(ctx) {
|
|
63
|
+
var rest = Array.prototype.slice.call(arguments, 1);
|
|
64
|
+
var nextCtx = riskBoundary.wrapContext(localPermissionBoundary.wrapContext(ctx));
|
|
65
|
+
return apply.apply(exports, [nextCtx].concat(rest));
|
|
66
|
+
};
|
|
67
|
+
Object.defineProperty(wrappedApply, '__visionRouterSettingsRc8Lifecycle', { value: true });
|
|
68
|
+
exports.apply = wrappedApply;
|
|
69
|
+
}
|
|
70
|
+
return exports;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return original.call(this, spec);
|
|
75
|
+
}
|
|
76
|
+
Object.defineProperty(load, '__visionRouterSettingsRc8Lifecycle', { value: true });
|
|
77
|
+
loader.load = load;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function patchCreate(loader) {
|
|
81
|
+
if (!loader || typeof loader.create !== 'function' || loader.create.__visionRouterSettingsRc8Lifecycle) return;
|
|
82
|
+
var originalCreate = loader.create;
|
|
83
|
+
function create() {
|
|
84
|
+
var result = originalCreate.apply(this, arguments);
|
|
85
|
+
patchLiveLoader(loader);
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
Object.defineProperty(create, '__visionRouterSettingsRc8Lifecycle', { value: true });
|
|
89
|
+
loader.create = create;
|
|
90
|
+
if (loader.mode === 'live') patchLiveLoader(loader);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function install() {
|
|
94
|
+
if (window.__ModuleLoader__) {
|
|
95
|
+
patchCreate(window.__ModuleLoader__);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
var descriptor = Object.getOwnPropertyDescriptor(window, '__ModuleLoader__');
|
|
99
|
+
if (descriptor && descriptor.configurable === false) return;
|
|
100
|
+
var previousGet = descriptor && descriptor.get;
|
|
101
|
+
var previousSet = descriptor && descriptor.set;
|
|
102
|
+
var stored = descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value') ? descriptor.value : undefined;
|
|
103
|
+
Object.defineProperty(window, '__ModuleLoader__', {
|
|
104
|
+
configurable: true,
|
|
105
|
+
enumerable: !descriptor || descriptor.enumerable !== false,
|
|
106
|
+
get: function(){ return previousGet ? previousGet.call(window) : stored; },
|
|
107
|
+
set: function(value) {
|
|
108
|
+
if (previousSet) previousSet.call(window, value); else stored = value;
|
|
109
|
+
try { patchCreate(previousGet ? previousGet.call(window) : value); } catch (_) {}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
if (stored) patchCreate(stored);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try { install(); } catch (_) {}
|
|
116
|
+
})();`
|
|
117
|
+
|
|
118
|
+
export function injectSettingsClientLoaderLifecyclePrelude(html) {
|
|
119
|
+
return injectClientCarrierHtml(html, SETTINGS_CLIENT_LOADER_LIFECYCLE_MARK, SETTINGS_CLIENT_LOADER_LIFECYCLE_PRELUDE)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function installSettingsClientLoaderLifecycle(ctx) {
|
|
123
|
+
installClientScriptCarriers(ctx, {
|
|
124
|
+
marker: SETTINGS_CLIENT_LOADER_LIFECYCLE_MARK,
|
|
125
|
+
prelude: SETTINGS_CLIENT_LOADER_LIFECYCLE_PRELUDE,
|
|
126
|
+
label: 'vision-router: settings client loader lifecycle',
|
|
127
|
+
injectHtml: injectSettingsClientLoaderLifecyclePrelude,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
@@ -8,20 +8,20 @@ import {
|
|
|
8
8
|
} from './settings-limit-client-prelude.js'
|
|
9
9
|
import {
|
|
10
10
|
SETTINGS_NATIVE_CARD_IA_PRELUDE,
|
|
11
|
+
SETTINGS_NATIVE_CARD_STYLE,
|
|
11
12
|
injectSettingsNativeCardStyle,
|
|
12
13
|
} from './settings-native-card-layout.js'
|
|
14
|
+
import { appendStructuredClientCarrier, injectClientCarrierHtml } from './client-script-carrier.js'
|
|
13
15
|
|
|
14
16
|
const CLIENT_MARK = 'data-vision-router-settings-factory-lifecycle'
|
|
15
17
|
const IA_SCRIPT_MARK = 'data-vision-router-settings-ia'
|
|
16
18
|
const LIMIT_SCRIPT_MARK = 'data-vision-router-settings-limit-hardening'
|
|
17
19
|
|
|
18
|
-
function
|
|
19
|
-
const
|
|
20
|
-
return
|
|
20
|
+
function removeScriptMarker(source, marker) {
|
|
21
|
+
const pattern = new RegExp(`<script\\b[^>]*\\b${marker}(?:\\s|=|>|/)[^>]*>[\\s\\S]*?<\\/script\\s*>`, 'gi')
|
|
22
|
+
return source.replace(pattern, '')
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
const IA_SCRIPT = scriptMarkup(IA_SCRIPT_MARK, SETTINGS_IA_CLIENT_PRELUDE)
|
|
24
|
-
const LIMIT_SCRIPT = scriptMarkup(LIMIT_SCRIPT_MARK, SETTINGS_LIMIT_CLIENT_PRELUDE)
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* One lifecycle owner for every decorator that shapes the Vision Router
|
|
@@ -123,19 +123,30 @@ export function injectSettingsFactoryLifecycle(html) {
|
|
|
123
123
|
let next = injectSettingsLimitClientPrelude(html)
|
|
124
124
|
next = injectSettingsIaClientPrelude(next)
|
|
125
125
|
next = injectSettingsNativeCardStyle(next)
|
|
126
|
-
next = next
|
|
127
|
-
next = next
|
|
128
|
-
|
|
129
|
-
const safe = SETTINGS_FACTORY_LIFECYCLE_PRELUDE.replace(/<\/script/gi, '<\\/script')
|
|
130
|
-
const script = `<script ${CLIENT_MARK}>${safe}</script>`
|
|
131
|
-
const closeHead = next.indexOf('</head>')
|
|
132
|
-
return closeHead === -1
|
|
133
|
-
? `${next}${script}`
|
|
134
|
-
: `${next.slice(0, closeHead)}${script}${next.slice(closeHead)}`
|
|
126
|
+
next = removeScriptMarker(next, LIMIT_SCRIPT_MARK)
|
|
127
|
+
next = removeScriptMarker(next, IA_SCRIPT_MARK)
|
|
128
|
+
return injectClientCarrierHtml(next, CLIENT_MARK, SETTINGS_FACTORY_LIFECYCLE_PRELUDE)
|
|
135
129
|
}
|
|
136
130
|
|
|
137
131
|
export function installSettingsFactoryLifecycle(ctx) {
|
|
138
132
|
ctx?.inject?.(['webServer'], (webCtx) => {
|
|
133
|
+
if (typeof webCtx.on === 'function') {
|
|
134
|
+
webCtx.on('webserver/index-inject', (table) => {
|
|
135
|
+
if (!Array.isArray(table)) return
|
|
136
|
+
const historical = [`/* ${IA_SCRIPT_MARK} */`, `/* ${LIMIT_SCRIPT_MARK} */`]
|
|
137
|
+
for (let index = table.length - 1; index >= 0; index -= 1) {
|
|
138
|
+
const row = table[index]
|
|
139
|
+
if (row?.kind === 'script' && typeof row.text === 'string' && historical.some((mark) => row.text.includes(mark))) {
|
|
140
|
+
table.splice(index, 1)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
appendStructuredClientCarrier(table, CLIENT_MARK, SETTINGS_FACTORY_LIFECYCLE_PRELUDE)
|
|
144
|
+
const styleMark = 'data-vision-router-settings-native-cards'
|
|
145
|
+
if (!table.some((row) => row?.kind === 'style' && typeof row.text === 'string' && row.text.includes(styleMark))) {
|
|
146
|
+
table.push({ kind: 'style', placement: 'head', text: `/* ${styleMark} */\n${SETTINGS_NATIVE_CARD_STYLE}` })
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
}
|
|
139
150
|
webCtx.effect(
|
|
140
151
|
() => webCtx.webServer.tapIndex(injectSettingsFactoryLifecycle),
|
|
141
152
|
'vision-router: converged settings client factory lifecycle',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { injectClientCarrierHtml, injectClientStyleHtml, installClientScriptCarriers } from './client-script-carrier.js'
|
|
1
2
|
const CLIENT_MARK = 'data-vision-router-settings-ia'
|
|
2
3
|
|
|
3
4
|
export const SETTINGS_IA_CLIENT_PRELUDE = String.raw`(function(){
|
|
@@ -180,7 +181,7 @@ export const SETTINGS_IA_CLIENT_PRELUDE = String.raw`(function(){
|
|
|
180
181
|
function modelOptions(provider,current,allowAll){var group=groupById.get(provider),models=arr(group&&group.models),listed=new Set(models.map(function(model){return model&&model.id;}).filter(Boolean)),nodes=[h('option',{value:'',key:'__empty'},allowAll?tx('全部模型','All models'):(provider?tx('选择模型…','Choose model…'):tx('先选供应商','Choose provider first')))];if(current&&!listed.has(current))nodes.push(h('option',{value:current,key:'__stale-'+current},current+' '+tx('(已保存)','(saved)')));models.forEach(function(model){if(model&&typeof model.id==='string'&&model.id)nodes.push(h('option',{value:model.id,key:model.id},modelName(group,model.id)));});return nodes;}
|
|
181
182
|
function modelRow(row,index,rows,setRows,allowAll){var ready=groups.length>0;function update(next){var copy=rows.map(function(item){return Object.assign({},item);});copy[index]=next;setRows(copy);setSaveState({status:'idle'});}function providerChanged(provider){var next=Object.assign({},row,{provider:provider,model:''});if(!allowAll)next.fallbacks=[];update(next);}function remove(){var copy=rows.filter(function(_,i){return i!==index;});setRows(copy.length?copy:[{provider:'',model:'',fallbacks:[]}]);setSaveState({status:'idle'});}return h('div',{className:'vr-chain-row',key:index},ready?h('select',{className:'vr-input vr-select',value:row.provider||'',disabled:!writable||saving,onChange:function(event){providerChanged(event.target.value);}},providerOptions(row.provider||'')):h('input',{className:'vr-input',placeholder:'provider',value:row.provider||'',disabled:!writable||saving,onChange:function(event){providerChanged(event.target.value);}}),ready?h('select',{className:'vr-input vr-select',value:row.model||'',disabled:!writable||saving||!row.provider,onChange:function(event){update(Object.assign({},row,{model:event.target.value}));}},modelOptions(row.provider||'',row.model||'',allowAll)):h('input',{className:'vr-input',placeholder:allowAll?tx('model(留空=全部)','model (empty = all)'):'model',value:row.model||'',disabled:!writable||saving,onChange:function(event){update(Object.assign({},row,{model:event.target.value}));}}),h('button',{type:'button',className:'vr-reset',disabled:!writable||saving,onClick:remove},tx('移除','Remove')));}
|
|
182
183
|
|
|
183
|
-
function generalPage(){var rows=chainRows(),configured=rows.filter(function(row){return row&&row.provider&&row.model;}),localOnly=toggleValue('localOnlyVision',false),fallback=!localOnly&&toggleValue('freeFallback',true),status=localOnly?tx('仅本地视觉策略已启用','Local-only vision policy enabled'):configured.length?tx('识图已就绪','Vision ready'):fallback?tx('已可使用内置免费识图','Built-in free vision is ready'):tx('暂无可用识图模型','No vision model configured'),ordered=configured.map(function(row){return row.model;});if(fallback)ordered.push(tx('内置免费兜底','Built-in free fallback'));var stale=catalog.status==='ready'?configured.filter(function(row){var group=groupById.get(row.provider);return !group||!arr(group.models).some(function(model){return model&&model.id===row.model;});}):[];return h(React.Fragment,null,title(tx('常规','General'),tx('这里只设置“谁负责看图”。聊天模型仍在聊天页选择。','This page only decides who reads images; the chat model is still chosen in chat.')),card([toggle('localOnlyVision',tx('仅本地视觉','Local-only vision'),tx('开启后,Vision Router 只允许回环地址上的本地视觉后端(Ollama、LM Studio 或本机自定义 HTTP);所有云端/DSH Provider 和 OVH
|
|
184
|
+
function generalPage(){var rows=chainRows(),configured=rows.filter(function(row){return row&&row.provider&&row.model;}),localOnly=toggleValue('localOnlyVision',false),fallback=!localOnly&&toggleValue('freeFallback',true),status=localOnly?tx('仅本地视觉策略已启用','Local-only vision policy enabled'):configured.length?tx('识图已就绪','Vision ready'):fallback?tx('已可使用内置免费识图','Built-in free vision is ready'):tx('暂无可用识图模型','No vision model configured'),ordered=configured.map(function(row){return row.model;});if(fallback)ordered.push(tx('内置免费兜底','Built-in free fallback'));var stale=catalog.status==='ready'?configured.filter(function(row){var group=groupById.get(row.provider);return !group||!arr(group.models).some(function(model){return model&&model.id===row.model;});}):[];return h(React.Fragment,null,title(tx('常规','General'),tx('这里只设置“谁负责看图”。聊天模型仍在聊天页选择。','This page only decides who reads images; the chat model is still chosen in chat.')),card([toggle('localOnlyVision',tx('仅本地视觉','Local-only vision'),tx('开启后,Vision Router 只允许回环地址上的本地视觉后端(Ollama、LM Studio 或本机自定义 HTTP);所有云端/DSH Provider 和 OVH 免费兜底都会在运行时被硬阻止。已保存的云端配置不会删除,关闭后可恢复。Vision Router 不会把图片和裁剪发送到云端视觉服务,但视觉后端返回的描述、OCR、坐标等识图结果文本仍会交给当前聊天模型;若聊天模型在云端,图片语义仍可能离开本机。严格纯本地还需同时选择本地聊天模型。此隐私开关只能在运行 DSH 的本机页面修改。','When enabled, Vision Router only allows visual backends on loopback addresses (Ollama, LM Studio, or custom local HTTP). Cloud/DSH providers and the OVH free fallback are blocked at runtime. Saved cloud settings are preserved for later restoration. Vision Router does not send image bytes or crops to cloud vision services, but derived result text—descriptions, OCR, and coordinates—is still passed to the current chat model; if that model is remote, image semantics may still leave this machine. For a strict end-to-end local workflow, select a local chat model too. This privacy control can only be changed from the local DSH settings page.'),!local),h('div',{className:'vr-ia-status',key:'status'},h('strong',null,(localOnly?'🔒 ':configured.length||fallback?'● ':'⚠ ')+status),h('p',{className:'vr-hint'},tx('需要看图时,在聊天框旁点击「👁 识图」。','Use “👁 Vision” beside the chat box when an image needs inspection.')),h('p',{className:localOnly?'vr-hint':'vr-failed',key:'egress'},localOnly?tx('当前仅本地视觉策略正在生效:云端识图行和 OVH 免费兜底会保留配置但不会执行;Vision Router 只会把图片和裁剪发往回环地址上的本地视觉端点。识图结果文本仍会交给当前聊天模型;若聊天模型在云端,图片语义仍可能离开本机。严格纯本地还需同时选择本地聊天模型。','Local-only vision is active: cloud vision rows and the OVH fallback remain saved but cannot execute; Vision Router sends image bytes and crops only to local visual endpoints on loopback addresses. Derived result text is still passed to the current chat model; if that model is remote, image semantics may still leave this machine. For a strict end-to-end local workflow, select a local chat model too.'):tx('数据流向:使用云端识图模型(包括默认 OVHcloud 匿名兜底)时,图片/裁剪、提示词和相关上下文会离开本机;免费或免 Key 不等于离线。若要阻止 Vision Router 将图片发送到远程视觉端点,请开启上方“仅本地视觉”。','Data flow: cloud vision models, including the default anonymous OVHcloud fallback, receive images/crops, prompts and related context; free or keyless does not mean offline. Enable Local-only vision above to prevent Vision Router from sending image data to remote visual endpoints.')),ordered.length?h('p',{className:'vr-ia-chain-summary'},ordered.join(' → ')):null,stale.length?h('p',{className:'vr-failed'},tx('有已保存的识图模型当前不在可调用目录中;请到“诊断”重新检测。','A saved vision model is not currently in the callable catalog; re-detect it in Diagnostics.')):null)]),card([h('div',{className:'vr-field',id:'vr-vision-backend-chain','data-vr-guide-target':'vision-backend',key:'chain'},fieldHead('providers',tx('识图模型','Vision models')),h('p',{className:'vr-hint'},tx('从上到下依次尝试;前一个失败时自动切换到下一个。','Models are tried from top to bottom and fall through automatically on failure.')),rows.map(function(row,index){return modelRow(row,index,rows,setChainDraft,false);}),h('button',{type:'button',className:'vr-btn',disabled:!writable||saving,onClick:function(){setChainDraft(rows.concat([{provider:'',model:'',fallbacks:[]}]))}},tx('+ 添加备用识图模型','+ Add fallback vision model'))),toggle('freeFallback',tx('内置免费兜底','Built-in free fallback'),localOnly?tx('仅本地视觉开启时,OVH 免费兜底由运行时强制禁用;这里保留原设置,关闭仅本地视觉后可恢复。','The OVH fallback is runtime-blocked while Local-only vision is enabled. Its saved value is preserved and becomes active again when Local-only vision is turned off.'):tx('这是远程 OVHcloud 服务;前面的模型都失败后会上传图片并自动尝试,无需 API Key。','This is a remote OVHcloud service. After earlier models fail, it uploads the image and tries automatically; no API key is required.'),localOnly)]));}
|
|
184
185
|
function strategyPage(){
|
|
185
186
|
var structured=toggleValue('structuredVisionBootstrap',false);
|
|
186
187
|
var rawDepth=String(value('visionDepth','standard')||'standard');
|
|
@@ -261,7 +262,7 @@ export const SETTINGS_IA_CLIENT_PRELUDE = String.raw`(function(){
|
|
|
261
262
|
function advancedPage(){var routing=toggleValue('routing',false),turnBudget=Number(value('visionTurnBudgetMs',0))||0,customBudget=turnBudget>0;return h(React.Fragment,null,title(tx('高级','Advanced'),tx('这些默认值对大多数用户已经合适。','Defaults are suitable for most users.')),card([h('h4',{className:'vr-ia-subtitle',key:'p'},tx('性能与稳定性','Performance & stability')),toggle('downscale',tx('自动缩放','Auto downscale')),numberField('downscaleMaxPixels',tx('图片像素上限','Image pixel limit'),null,1000,100000000),toggle('cache',tx('识图缓存','Vision answer cache')),numberField('cacheTtlSeconds',tx('缓存有效期(秒)','Cache TTL (seconds)'),null,0,31536000),numberField('cacheMaxEntries',tx('最大缓存数量','Maximum cached answers'),null,1,100000),h('h4',{className:'vr-ia-subtitle',key:'t'},tx('超时','Timeouts')),numberField('timeoutMs',tx('单次模型请求','Single model request'),tx('毫秒。','Milliseconds.'),1000,600000),numberField('visionTaskTimeoutMs',tx('单个视觉任务','Single visual task'),tx('包含该任务内部的重试和备用模型;不是每个后端各自一份。','Includes retries and fallbacks inside the task; it is not a fresh budget per backend.'),1000,180000),numberField('ocrTimeoutMs',tx('OCR 任务','OCR task'),null,1000,120000),h('div',{className:'vr-field',key:'ocr-engine'},fieldHead('ocrEngine',tx('OCR 默认引擎','Default OCR engine')),h('select',{className:'vr-input',value:String(value('ocrEngine','auto')||'auto'),disabled:!writable||saving,onChange:function(event){setValue('ocrEngine',event.target.value);}},h('option',{value:'auto'},tx('自动(Tesseract → 视觉模型)','Auto (Tesseract → vision)')),h('option',{value:'tesseract'},tx('仅 Tesseract','Tesseract only')),h('option',{value:'vision'},tx('仅视觉模型','Vision model only'))),h('p',{className:'vr-hint'},tx('vision_ocr 未显式指定 engine 时使用;单次调用的 engine=tesseract/vision 始终优先。','Used when vision_ocr does not explicitly choose an engine; per-call engine=tesseract/vision always wins.'))),h('div',{className:'vr-field',key:'budget'},fieldHead('visionTurnBudgetMs',tx('整轮视觉工具上限','Whole-turn vision-tool limit')),h('select',{className:'vr-input',value:customBudget?'custom':'unlimited',disabled:!writable||saving,onChange:function(event){setValue('visionTurnBudgetMs',event.target.value==='unlimited'?0:(turnBudget>0?turnBudget:180000));}},h('option',{value:'unlimited'},tx('不限制(推荐)','Unlimited (recommended)')),h('option',{value:'custom'},tx('自定义','Custom'))),customBudget?numberField('visionTurnBudgetMs',tx('上限(毫秒)','Limit (ms)'),null,10000,600000):null)]),card([h('h4',{className:'vr-ia-subtitle',key:'cost'},tx('模型顺序与成本','Model order & cost')),toggle('freeCloudFirst',tx('免费云模型优先','Try free cloud models first'))]),card([h('h4',{className:'vr-ia-subtitle',key:'scope'},tx('识图模式范围','Vision mode scope')),toggle('autoWrapProviders',tx('自动允许已启用模型使用识图','Automatically allow enabled models to use Vision mode')),wrappersEditor()]),local?card([h('h4',{className:'vr-ia-subtitle',key:'network'},tx('网络与远程','Network & remote')),h('p',{className:'vr-hint',key:'network-authority'},tx('默认沿用 DSH/Host 的网络出口;只有需要让视觉请求走不同出口时才填写下方覆盖。','By default Vision Router follows the DSH/Host network path. Use the override below only when vision requests need a different egress route.')),toggle('allowRemoteSettings',tx('允许可信 Host 远程修改设置','Allow trusted-host remote settings'),tx('默认关闭;trustedHosts 不是身份认证。','Off by default; trustedHosts is not authentication.')),textField('proxy',tx('视觉专用代理覆盖','Vision-only proxy override'),tx('留空即沿用 DSH/Host;支持 http(s)://、socks5://,并兼容旧 socks5h://。','Leave empty to follow DSH/Host; supports http(s):// and socks5://, with legacy socks5h:// compatibility.')),textareaArray('proxyHosts',tx('视觉代理覆盖域名','Vision proxy override hosts'),tx('仅在上方覆盖非空时生效;每行一个,其余请求继续沿用 DSH/Host。','Used only when the override above is non-empty; one host per line. All other requests keep the DSH/Host path.'))]):null,card([h('h4',{className:'vr-ia-subtitle',key:'compat'},tx('兼容模式','Compatibility')),toggle('rewriteImages',tx('保护纯文本模型','Protect text-only models')),toggle('routing',tx('整轮视觉路由(旧工作流)','Whole-turn vision routing (legacy workflow)')),routing?toggle('reverseRouting',tx('纯文字消息继续使用聊天模型','Keep text-only messages on the chat model')):null,routing?textProviderEditor():null]),card([h('button',{type:'button',className:'vr-btn',key:'dev',onClick:function(){setOpen(Object.assign({},open,{developer:!open.developer}));}},open.developer?tx('隐藏开发者设置','Hide developer settings'):tx('显示开发者设置','Show developer settings')),open.developer?h('div',{className:'vr-ia-dev',key:'body'},toggle('progressiveTools',tx('渐进式工具暴露','Progressive tool exposure')),h('p',{className:'vr-hint',key:'progressive-restart'},tx('保存后需重启 DSH 才生效。','Restart DSH after saving for this change to take effect.')),local?toggle('stealth','Stealth'):null,local?textField('wrapperRoute',tx('包装路由名','Wrapper route name')):null,local?textField('chainRoute',tx('视觉链路由名','Vision chain route name')):null,textareaArray('extraVisionModels',tx('额外视觉能力标记','Extra vision capability labels'),tx('只有诊断发现模型未声明图片能力时才需要。','Needed only when diagnostics show missing image-capability metadata.'))):null]));}
|
|
262
263
|
function diagnosticValue(label,valueText){return h('div',{className:'vr-ia-diag-row',key:label},h('span',null,label),h('strong',null,valueText));}
|
|
263
264
|
function updatePanel(){if(!local)return null;var result=updateState.result,auto=result&&result.autoUpdate,current=result&&result.currentVersion?result.currentVersion:tx('检测中','Checking'),latest=result&&result.latestVersion?result.latestVersion:'—',available=result&&result.ok===true&&result.updateAvailable===true,profile=auto&&auto.profile?auto.profile:'web',manualVersion=result&&result.latestVersion?result.latestVersion:'<version>',spec='dsh-vision-router@'+manualVersion,pnpm='pnpm dsh plugin --profile '+profile+' add '+spec,npx='npx @deepseek-ai/dsh plugin --profile '+profile+' add '+spec;return card([h('h4',{className:'vr-ia-subtitle',key:'title'},tx('版本更新','Updates')),h('p',{className:'vr-hint',key:'status'},updateState.status==='running'?tx('正在检查更新…','Checking for updates…'):result&&result.ok===false?tx('更新检查失败:','Update check failed: ')+String(result.error||'unknown'):available?tx('发现新版本:v','Update available: v')+latest+tx('(当前 v',' (current v')+current+')':result&&result.ok===true?tx('已是最新版本 v','Up to date: v')+current:tx('尚未检查','Not checked yet')),h('div',{className:'vr-ia-actions',key:'actions'},h('button',{type:'button',className:'vr-btn',disabled:updateState.status==='running',onClick:function(){void runUpdateCheck(true);}},tx('检查更新','Check for updates')),available&&auto&&auto.supported===true&&auto.token?h('button',{type:'button',className:'vr-btn vr-btn-save',disabled:selfUpdateState.status==='running',onClick:function(){void runSelfUpdate();}},selfUpdateState.status==='running'?tx('更新中…','Updating…'):tx('一键更新','Update now')):null),selfUpdateState.status==='done'&&selfUpdateState.result?h('p',{className:'vr-hint',key:'updated'},tx('更新完成,请重启 DSH。','Update complete. Restart DSH.')):selfUpdateState.error?h('p',{className:'vr-failed',key:'uerr'},String(selfUpdateState.error)):null,available&&(!auto||auto.supported!==true)?h('div',{className:'vr-ia-manual',key:'manual'},h('p',{className:'vr-hint'},tx('当前安装方式不支持安全的一键更新,请使用与你当前 DSH 安装方式一致的命令:','This install method cannot be safely auto-updated. Use the command matching your DSH installation:')),h('code',{className:'vr-ia-code'},pnpm),h('code',{className:'vr-ia-code'},npx)):null]);}
|
|
264
|
-
function diagnosticsPage(){var providerCount=groups.length,modelCount=groups.reduce(function(total,group){return total+arr(group&&group.models).length;},0),configured=chainRows().filter(function(row){return row&&row.provider&&row.model;}).length,ollama=obj(value('localOllama',{})),lm=obj(value('localLmStudio',{})),testText=!local?tx('仅本机可见','Local only'):testState.status==='running'?tx('检测中','Checking'):testState.status==='done'&&testState.result&&testState.result.ok===true?tx('连接正常','Connected'):testState.status==='done'?tx('连接失败','Failed'):tx('未检测','Not checked'),capsText=caps.status==='ready'?tx('正常','Ready'):caps.status==='loading'?tx('检测中','Checking'):caps.status==='error'?tx('不可用','Unavailable'):tx('未检测','Not checked');var rows=[[tx('设置协议','Settings contract'),String(value('settingsContractRevision','—'))],[tx('模型目录','Model catalog'),catalog.status==='ready'?tx('正常','Ready'):catalog.status==='loading'?tx('检测中','Checking'):tx('不可用','Unavailable')],[tx('图片能力元数据','Image capability metadata'),capsText],[tx('可选 Provider','Selectable providers'),String(providerCount)],[tx('可选模型','Selectable models'),String(modelCount)],[tx('已配置识图模型','Configured vision models'),String(configured)],[tx('内置免费兜底','Built-in free fallback'),toggleValue('freeFallback',true)?(caps.builtinFallback.length?tx('已启用,','Enabled, ')+caps.builtinFallback.length+tx(' 个模型',' models'):tx('已启用','Enabled')):tx('已关闭','Disabled')],[tx('后端连接','Backend connection'),testText],[tx('Ollama','Ollama'),local?(ollama.enabled===true?tx('已启用','Enabled'):tx('未启用','Disabled')):tx('仅本机可见','Local only')],[tx('LM Studio','LM Studio'),local?(lm.enabled===true?tx('已启用','Enabled'):tx('未启用','Disabled')):tx('仅本机可见','Local only')],[tx('桌面截图','Desktop capture'),local?(toggleValue('desktopScreenshot',false)?tx('已启用','Enabled'):tx('未启用','Disabled')):tx('仅本机可见','Local only')],[tx('网络出口','Network egress'),local?(String(value('proxy','')).trim()?tx('视觉专用覆盖','Vision-only override'):tx('DSH/Host(默认)','DSH/Host (default)')):tx('仅本机可见','Local only')],[tx('远程设置','Remote settings'),local?(toggleValue('allowRemoteSettings',false)?tx('已启用','Enabled'):tx('未启用','Disabled')):tx('当前为远程安全视图','Remote safe view')]];function reportText(){return ['Vision Router diagnostics'].concat(rows.map(function(row){return row[0]+': '+row[1];}),['doctor: dsh-vision-router doctor']).join('\n');}async function copyReport(){try{if(typeof navigator!=='undefined'&&navigator.clipboard&&typeof navigator.clipboard.writeText==='function')await navigator.clipboard.writeText(reportText());setActionState({status:'copied'});}catch(error){setActionState({status:'error',error:error&&error.message?error.message:String(error)});}}return h(React.Fragment,null,title(tx('诊断','Diagnostics'),tx('状态页不会修改你的模型/路由设置;连接测试和更新操作会明确由按钮触发。','The status page does not change model or routing settings; connection tests and updates are explicit actions.')),card(rows.map(function(row){return diagnosticValue(row[0],row[1]);})),card([h('div',{className:'vr-ia-actions',key:'actions'},local?h('button',{type:'button',className:'vr-btn',disabled:testState.status==='running',onClick:function(){void runTestConnection();}},tx('测试连接','Test connection')):null,h('button',{type:'button',className:'vr-btn',onClick:function(){invalidateCatalog();}},tx('重新检测模型','Re-detect models')),local?h('button',{type:'button',className:'vr-btn',onClick:function(){void openLogs();}},tx('打开日志文件夹','Open logs folder')):null,h('button',{type:'button',className:'vr-btn',onClick:function(){void copyReport();}},tx('复制诊断信息','Copy diagnostics'))),h('p',{className:'vr-hint',key:'doctor'},tx('需要完整 DSH 版本、安装、Profile、运行时路由和会话诊断时执行:dsh-vision-router doctor','For the full DSH version, installation, profile, runtime-route, and session report, run: dsh-vision-router doctor')),testState.status==='done'&&testState.result&&testState.result.ok!==true?h('p',{className:'vr-failed',key:'testerr'},String(testState.result.error||tx('连接测试失败','Connection test failed'))):null,actionState.status==='error'?h('p',{className:'vr-failed',key:'aerr'},String(actionState.error||'unknown')):null]),updatePanel());}
|
|
265
|
+
function diagnosticsPage(){var providerCount=groups.length,modelCount=groups.reduce(function(total,group){return total+arr(group&&group.models).length;},0),configured=chainRows().filter(function(row){return row&&row.provider&&row.model;}).length,ollama=obj(value('localOllama',{})),lm=obj(value('localLmStudio',{})),testText=!local?tx('仅本机可见','Local only'):testState.status==='running'?tx('检测中','Checking'):testState.status==='done'&&testState.result&&testState.result.ok===true?tx('连接正常','Connected'):testState.status==='done'?tx('连接失败','Failed'):tx('未检测','Not checked'),capsText=caps.status==='ready'?tx('正常','Ready'):caps.status==='loading'?tx('检测中','Checking'):caps.status==='error'?tx('不可用','Unavailable'):tx('未检测','Not checked');var rows=[[tx('设置协议','Settings contract'),String(value('settingsContractRevision','—'))],[tx('模型目录','Model catalog'),catalog.status==='ready'?tx('正常','Ready'):catalog.status==='loading'?tx('检测中','Checking'):tx('不可用','Unavailable')],[tx('图片能力元数据','Image capability metadata'),capsText],[tx('可选 Provider','Selectable providers'),String(providerCount)],[tx('可选模型','Selectable models'),String(modelCount)],[tx('已配置识图模型','Configured vision models'),String(configured)],[tx('内置免费兜底','Built-in free fallback'),toggleValue('freeFallback',true)?(caps.builtinFallback.length?tx('已启用,','Enabled, ')+caps.builtinFallback.length+tx(' 个模型',' models'):tx('已启用','Enabled')):tx('已关闭','Disabled')],[tx('后端连接','Backend connection'),testText],[tx('Ollama','Ollama'),local?(ollama.enabled===true?tx('已启用','Enabled'):tx('未启用','Disabled')):tx('仅本机可见','Local only')],[tx('LM Studio','LM Studio'),local?(lm.enabled===true?tx('已启用','Enabled'):tx('未启用','Disabled')):tx('仅本机可见','Local only')],[tx('桌面截图','Desktop capture'),local?(toggleValue('desktopScreenshot',false)?tx('已启用','Enabled'):tx('未启用','Disabled')):tx('仅本机可见','Local only')],[tx('网络出口','Network egress'),local?(String(value('proxy','')).trim()?tx('视觉专用覆盖','Vision-only override'):tx('DSH/Host(默认)','DSH/Host (default)')):tx('仅本机可见','Local only')],[tx('识图结果去向','Vision result destination'),tx('当前聊天模型(可能为云端)','Current chat model (may be remote)')],[tx('远程设置','Remote settings'),local?(toggleValue('allowRemoteSettings',false)?tx('已启用','Enabled'):tx('未启用','Disabled')):tx('当前为远程安全视图','Remote safe view')]];function reportText(){return ['Vision Router diagnostics'].concat(rows.map(function(row){return row[0]+': '+row[1];}),['doctor: dsh-vision-router doctor']).join('\n');}async function copyReport(){try{if(typeof navigator!=='undefined'&&navigator.clipboard&&typeof navigator.clipboard.writeText==='function')await navigator.clipboard.writeText(reportText());setActionState({status:'copied'});}catch(error){setActionState({status:'error',error:error&&error.message?error.message:String(error)});}}return h(React.Fragment,null,title(tx('诊断','Diagnostics'),tx('状态页不会修改你的模型/路由设置;连接测试和更新操作会明确由按钮触发。','The status page does not change model or routing settings; connection tests and updates are explicit actions.')),card(rows.map(function(row){return diagnosticValue(row[0],row[1]);})),card([h('div',{className:'vr-ia-actions',key:'actions'},local?h('button',{type:'button',className:'vr-btn',disabled:testState.status==='running',onClick:function(){void runTestConnection();}},tx('测试连接','Test connection')):null,h('button',{type:'button',className:'vr-btn',onClick:function(){invalidateCatalog();}},tx('重新检测模型','Re-detect models')),local?h('button',{type:'button',className:'vr-btn',onClick:function(){void openLogs();}},tx('打开日志文件夹','Open logs folder')):null,h('button',{type:'button',className:'vr-btn',onClick:function(){void copyReport();}},tx('复制诊断信息','Copy diagnostics'))),h('p',{className:'vr-hint',key:'doctor'},tx('需要完整 DSH 版本、安装、Profile、运行时路由和会话诊断时执行:dsh-vision-router doctor','For the full DSH version, installation, profile, runtime-route, and session report, run: dsh-vision-router doctor')),testState.status==='done'&&testState.result&&testState.result.ok!==true?h('p',{className:'vr-failed',key:'testerr'},String(testState.result.error||tx('连接测试失败','Connection test failed'))):null,actionState.status==='error'?h('p',{className:'vr-failed',key:'aerr'},String(actionState.error||'unknown')):null]),updatePanel());}
|
|
265
266
|
|
|
266
267
|
var pages=[['general',tx('常规','General')],['strategy',tx('识图策略','Vision strategy')],['local',tx('本地与设备','Local & device')],['advanced',tx('高级','Advanced')],['diagnostics',tx('诊断','Diagnostics')]];
|
|
267
268
|
var content=page==='strategy'?strategyPage():page==='local'?localPageContent():page==='advanced'?advancedPage():page==='diagnostics'?diagnosticsPage():generalPage();
|
|
@@ -282,17 +283,16 @@ const SETTINGS_IA_STYLE = String.raw`
|
|
|
282
283
|
`
|
|
283
284
|
|
|
284
285
|
export function injectSettingsIaClientPrelude(html) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const closeHead = html.indexOf('</head>')
|
|
288
|
-
return closeHead === -1 ? `${html}${script}` : `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
|
|
286
|
+
const withStyle = injectClientStyleHtml(html, CLIENT_MARK, SETTINGS_IA_STYLE)
|
|
287
|
+
return injectClientCarrierHtml(withStyle, CLIENT_MARK, SETTINGS_IA_CLIENT_PRELUDE)
|
|
289
288
|
}
|
|
290
289
|
|
|
291
290
|
export function installSettingsIaClientPrelude(ctx) {
|
|
292
|
-
ctx
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
291
|
+
installClientScriptCarriers(ctx, {
|
|
292
|
+
marker: CLIENT_MARK,
|
|
293
|
+
prelude: SETTINGS_IA_CLIENT_PRELUDE,
|
|
294
|
+
label: 'vision-router: Settings IA client prelude',
|
|
295
|
+
injectHtml: injectSettingsIaClientPrelude,
|
|
296
|
+
style: { marker: CLIENT_MARK, text: SETTINGS_IA_STYLE },
|
|
297
297
|
})
|
|
298
298
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
|
|
1
2
|
import { SETTINGS_NUMBER_META } from './settings-number-contract.js'
|
|
2
3
|
|
|
3
4
|
const CLIENT_MARK = 'data-vision-router-settings-limit-hardening'
|
|
@@ -94,17 +95,14 @@ export const SETTINGS_LIMIT_CLIENT_PRELUDE = String.raw`(function(){
|
|
|
94
95
|
})();`
|
|
95
96
|
|
|
96
97
|
export function injectSettingsLimitClientPrelude(html) {
|
|
97
|
-
|
|
98
|
-
const script = `<script ${CLIENT_MARK}>${SETTINGS_LIMIT_CLIENT_PRELUDE.replace(/<\/script/gi, '<\\/script')}</script>`
|
|
99
|
-
const closeHead = html.indexOf('</head>')
|
|
100
|
-
return closeHead === -1 ? `${html}${script}` : `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
|
|
98
|
+
return injectClientCarrierHtml(html, CLIENT_MARK, SETTINGS_LIMIT_CLIENT_PRELUDE)
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
export function installSettingsLimitClientPrelude(ctx) {
|
|
104
|
-
ctx
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
installClientScriptCarriers(ctx, {
|
|
103
|
+
marker: CLIENT_MARK,
|
|
104
|
+
prelude: SETTINGS_LIMIT_CLIENT_PRELUDE,
|
|
105
|
+
label: 'vision-router: settings numeric limits',
|
|
106
|
+
injectHtml: injectSettingsLimitClientPrelude,
|
|
109
107
|
})
|
|
110
108
|
}
|
package/lib/sharp-runtime.js
CHANGED
|
@@ -10,7 +10,6 @@ import { Worker } from 'node:worker_threads'
|
|
|
10
10
|
import { createRequire } from 'node:module'
|
|
11
11
|
import { pathToFileURL } from 'node:url'
|
|
12
12
|
import { promisify } from 'node:util'
|
|
13
|
-
import { appendPromptToImageOnlyMessage, fetchWithOpenAICompatibility } from './http-compat.js'
|
|
14
13
|
import {
|
|
15
14
|
directSessionAffinityHeaders,
|
|
16
15
|
isOfficialOpenCodeGoUrl,
|
|
@@ -19,12 +18,6 @@ import {
|
|
|
19
18
|
sessionIdentityOf,
|
|
20
19
|
} from './session-affinity.js'
|
|
21
20
|
import { runWithVisionSessionAffinity, streamWithVisionSessionAffinity } from './session-affinity-runtime.js'
|
|
22
|
-
import {
|
|
23
|
-
routingCorrectionFor,
|
|
24
|
-
toAnthropicMessages,
|
|
25
|
-
callAnthropicCompatible,
|
|
26
|
-
anthropicMediaType,
|
|
27
|
-
} from './catalog-corrections.js'
|
|
28
21
|
import { createCachedUpdateChecker } from './update-check.js'
|
|
29
22
|
import { probeLocalBackends } from './local-connection-probe.js'
|
|
30
23
|
import { detectDshSelfUpdatePlan, runDshPluginUpdate } from './self-update.js'
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
|
|
1
2
|
import { LIVE_MODEL_CLIENT_PRELUDE } from './live-model-client-prelude.js'
|
|
2
3
|
|
|
3
4
|
const CLIENT_PRELUDE_MARK = 'data-vision-router-live-models'
|
|
@@ -120,18 +121,14 @@ function replaceMergeCatalog(source) {
|
|
|
120
121
|
export const STRICT_LIVE_MODEL_CLIENT_PRELUDE = replaceMergeCatalog(LIVE_MODEL_CLIENT_PRELUDE)
|
|
121
122
|
|
|
122
123
|
export function injectStrictLiveModelClientPrelude(html) {
|
|
123
|
-
|
|
124
|
-
const script = `<script ${CLIENT_PRELUDE_MARK}>${STRICT_LIVE_MODEL_CLIENT_PRELUDE.replace(/<\/script/gi, '<\\/script')}</script>`
|
|
125
|
-
const closeHead = html.indexOf('</head>')
|
|
126
|
-
if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
|
|
127
|
-
return `${html}${script}`
|
|
124
|
+
return injectClientCarrierHtml(html, CLIENT_PRELUDE_MARK, STRICT_LIVE_MODEL_CLIENT_PRELUDE)
|
|
128
125
|
}
|
|
129
126
|
|
|
130
127
|
export function installStrictLiveModelClientPrelude(ctx) {
|
|
131
|
-
ctx
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
128
|
+
installClientScriptCarriers(ctx, {
|
|
129
|
+
marker: CLIENT_PRELUDE_MARK,
|
|
130
|
+
prelude: STRICT_LIVE_MODEL_CLIENT_PRELUDE,
|
|
131
|
+
label: 'vision-router: strict live model client prelude',
|
|
132
|
+
injectHtml: injectStrictLiveModelClientPrelude,
|
|
136
133
|
})
|
|
137
134
|
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
} from './turn-budget-context.js'
|
|
5
5
|
import { runWithDepthConfig } from './depth-guidance.js'
|
|
6
6
|
import { runtimeLanguageFor } from './runtime-i18n.js'
|
|
7
|
+
import { visionRouterMessageSource } from './session-message-source-compat.js'
|
|
7
8
|
|
|
8
9
|
const STRUCTURED_EVIDENCE_TOOLS = new Set([
|
|
9
10
|
'vision_describe',
|
|
@@ -440,7 +441,7 @@ function appendGuardMessage(decision, payload, state, config) {
|
|
|
440
441
|
? '已达到本轮设置的深挖次数上限。不要再调用视觉工具;请基于已经获得的证据作答,并明确仍存在的不确定性。'
|
|
441
442
|
: '连续多次后续视觉调用都没有产出可用证据。不要继续重复调用视觉工具;请基于已经获得的证据作答,并明确仍存在的不确定性。',
|
|
442
443
|
}],
|
|
443
|
-
source:
|
|
444
|
+
source: visionRouterMessageSource(payload?.agent?.session),
|
|
444
445
|
}
|
|
445
446
|
return appendSyntheticGuardOnce(decision, baseMessages, state, message, true)
|
|
446
447
|
}
|
|
@@ -461,7 +462,7 @@ function appendGuardMessage(decision, payload, state, config) {
|
|
|
461
462
|
? 'The structured bootstrap is complete, but no usable task-directed visual evidence has been produced yet. Call at least one vision tool that can add or verify evidence, then answer after it succeeds.'
|
|
462
463
|
: '结构化预识别已经完成,但目前还没有产出可用的任务定向视觉证据。请至少调用一个能新增或验证证据的视觉工具,成功后再作答。',
|
|
463
464
|
}],
|
|
464
|
-
source:
|
|
465
|
+
source: visionRouterMessageSource(payload?.agent?.session),
|
|
465
466
|
}
|
|
466
467
|
return appendSyntheticGuardOnce(decision, baseMessages, state, message)
|
|
467
468
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { appendStructuredClientCarrier, injectClientCarrierHtml } from './client-script-carrier.js'
|
|
2
2
|
|
|
3
3
|
const V2_SETTINGS_IA_MARK = 'data-vision-router-v2-settings-ia'
|
|
4
4
|
|
|
@@ -241,16 +241,31 @@ export const V2_SETTINGS_IA_CLIENT = String.raw`(function(){
|
|
|
241
241
|
})();`
|
|
242
242
|
|
|
243
243
|
export function injectV2SettingsIaIntegration(html) {
|
|
244
|
-
if (typeof html !== 'string'
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
244
|
+
if (typeof html !== 'string') return html
|
|
245
|
+
const styleMark = 'data-vision-router-v2-settings-ia-style'
|
|
246
|
+
let next = html
|
|
247
|
+
if (!next.includes(styleMark)) {
|
|
248
|
+
const style = `<style ${styleMark}>${V2_SETTINGS_IA_STYLE}</style>`
|
|
249
|
+
const closeHead = next.indexOf('</head>')
|
|
250
|
+
next = closeHead === -1 ? `${next}${style}` : `${next.slice(0, closeHead)}${style}${next.slice(closeHead)}`
|
|
251
|
+
}
|
|
252
|
+
return injectClientCarrierHtml(next, V2_SETTINGS_IA_MARK, V2_SETTINGS_IA_CLIENT)
|
|
249
253
|
}
|
|
250
254
|
|
|
251
255
|
export function installV2SettingsIaIntegration(ctx) {
|
|
252
256
|
if (!ctx || typeof ctx.inject !== 'function') return
|
|
253
257
|
ctx.inject(['webServer'], (webCtx) => {
|
|
258
|
+
if (typeof webCtx.on === 'function') {
|
|
259
|
+
webCtx.on('webserver/index-inject', (table) => {
|
|
260
|
+
if (!Array.isArray(table)) return
|
|
261
|
+
appendStructuredClientCarrier(table, V2_SETTINGS_IA_MARK, V2_SETTINGS_IA_CLIENT)
|
|
262
|
+
const styleMark = 'data-vision-router-v2-settings-ia-style'
|
|
263
|
+
if (!table.some((row) => row?.kind === 'style' && typeof row.text === 'string' && row.text.includes(styleMark))) {
|
|
264
|
+
table.push({ kind: 'style', placement: 'head', text: `/* ${styleMark} */
|
|
265
|
+
${V2_SETTINGS_IA_STYLE}` })
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
}
|
|
254
269
|
webCtx.effect(
|
|
255
270
|
() => webCtx.webServer.tapIndex(injectV2SettingsIaIntegration),
|
|
256
271
|
'vision-router: integrate v2 routing controls into settings IA',
|
|
@@ -215,7 +215,7 @@ function failureStream(message, code = 'VISION_IMAGE_DELIVERY_UNAVAILABLE') {
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
function directStream(ctx, core, plan, call, logger, source) {
|
|
218
|
+
function directStream(ctx, core, plan, call, logger, source, providerTransport) {
|
|
219
219
|
return {
|
|
220
220
|
async *[Symbol.asyncIterator]() {
|
|
221
221
|
const started = Date.now()
|
|
@@ -252,6 +252,7 @@ function directStream(ctx, core, plan, call, logger, source) {
|
|
|
252
252
|
maxTokens: call.maxTokens ?? 4096,
|
|
253
253
|
signal: call.signal,
|
|
254
254
|
sessionId: call.sessionId,
|
|
255
|
+
providerTransport,
|
|
255
256
|
resolveCredential: async (ref) => ref === keyRef ? apiKey : undefined,
|
|
256
257
|
},
|
|
257
258
|
)
|
|
@@ -349,7 +350,7 @@ function llmWithRuntimePolicy(ctx, llm, options) {
|
|
|
349
350
|
)
|
|
350
351
|
return
|
|
351
352
|
}
|
|
352
|
-
yield* directStream(ctx, core, plan, call, logger, source)
|
|
353
|
+
yield* directStream(ctx, core, plan, call, logger, source, options.providerTransport)
|
|
353
354
|
return
|
|
354
355
|
}
|
|
355
356
|
|