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.
Files changed (69) hide show
  1. package/README.md +8 -8
  2. package/README.zh.md +8 -8
  3. package/docs/architecture/2.3.0-compat-retirement.md +147 -0
  4. package/docs/architecture/2.3.0-execution-plan.md +410 -0
  5. package/docs/architecture/2.3.0-final-audit.md +279 -0
  6. package/docs/architecture/2.3.0-reopened-plan.md +720 -0
  7. package/docs/architecture/compat-inventory.md +26 -34
  8. package/docs/architecture/dsh-020-rc2-adaptation.md +23 -0
  9. package/docs/architecture/dsh-compatibility-matrix.md +65 -54
  10. package/docs/architecture/dsh-support-window.md +29 -21
  11. package/docs/architecture/final-closure-audit.md +2 -0
  12. package/docs/architecture/host-first-proxy-convergence.md +1 -1
  13. package/docs/releases/v2.2.8.md +25 -0
  14. package/docs/releases/v2.3.0.md +53 -0
  15. package/entry.js +8 -56
  16. package/index.js +161 -691
  17. package/lib/catalog-corrections.js +1 -2
  18. package/lib/client-host-compat-prelude.js +7 -12
  19. package/lib/client-maintenance-actions.js +145 -0
  20. package/lib/client-presentation-boundary-main.js +82 -32
  21. package/lib/client-presentation-boundary.js +60 -40
  22. package/lib/client-script-carrier.js +90 -0
  23. package/lib/client.js +248 -146
  24. package/lib/core-apply-lifecycle.js +47 -0
  25. package/lib/core-primitives.js +88 -135
  26. package/lib/desktop-screenshot-tool.js +262 -0
  27. package/lib/dsh-contract-compat.js +11 -26
  28. package/lib/dsh-support-window.js +4 -4
  29. package/lib/guide-vision-toggle-highlight.js +7 -10
  30. package/lib/http-compat.js +1 -2
  31. package/lib/live-model-client-prelude.js +7 -14
  32. package/lib/local-remote-settings-permission.js +41 -16
  33. package/lib/maintenance-routes.js +136 -0
  34. package/lib/public-config.js +57 -0
  35. package/lib/public-entry.js +2 -7
  36. package/lib/remote-settings-bridge.js +11 -0
  37. package/lib/remote-settings-risk-client-boundary.js +155 -0
  38. package/lib/remote-settings-risk-confirmation.js +17 -105
  39. package/lib/runtime-composition.js +105 -30
  40. package/lib/runtime-i18n-boundary.js +10 -11
  41. package/lib/session-message-source-compat.js +7 -3
  42. package/lib/session-vision-index.js +154 -9
  43. package/lib/session-vision-mode-boundary.js +43 -8
  44. package/lib/settings-client-loader-lifecycle.js +129 -0
  45. package/lib/settings-factory-lifecycle.js +25 -14
  46. package/lib/settings-ia-client-prelude.js +11 -11
  47. package/lib/settings-limit-client-prelude.js +7 -9
  48. package/lib/sharp-runtime.js +0 -7
  49. package/lib/strict-live-model-client-prelude.js +7 -10
  50. package/lib/structured-flow-hardening.js +3 -2
  51. package/lib/v2-settings-ia-integration.js +21 -6
  52. package/lib/vision-backend-runtime-policy.js +3 -2
  53. package/lib/vision-background-benchmark.js +3 -5
  54. package/lib/vision-capability-benchmark-client.js +7 -10
  55. package/lib/vision-capability-benchmark-service.js +1 -0
  56. package/lib/vision-diagnostics-routes.js +216 -0
  57. package/lib/vision-evidence-guidance.js +3 -1
  58. package/lib/vision-exact-check-client.js +11 -6
  59. package/lib/vision-model-visibility-boundary-main.js +7 -11
  60. package/lib/vision-model-visibility-boundary.js +7 -12
  61. package/lib/vision-provider-transport.js +0 -29
  62. package/lib/vision-routing-settings-prelude.js +7 -11
  63. package/lib/vision-toggle-root-hardening.js +50 -8
  64. package/lib/vision-tool-runtime-boundary.js +5 -1
  65. package/lib/vision-turn-budget-client-prelude.js +7 -10
  66. package/lib/web/remote-settings-client.js +9 -14
  67. package/package.json +23 -13
  68. package/lib/android-attachment-compat.js +0 -244
  69. package/lib/settings-client-rc8-lifecycle.js +0 -402
@@ -8,7 +8,7 @@ import {
8
8
  import { withHardDeadline } from './vision-capability-benchmark-hardening.js'
9
9
  import { resolveVisionRoutingAuthority } from './vision-routing-authority.js'
10
10
  import { redactDiagnosticText } from './diagnostic-redaction.js'
11
- import { injectVisionExactCheckClient } from './vision-exact-check-client.js'
11
+ import { installVisionExactCheckClient } from './vision-exact-check-client.js'
12
12
  import { createImageInputVerdictStore } from './vision-image-input-verdict.js'
13
13
  import { createBackgroundBenchmarkStopStore } from './vision-background-stop-store.js'
14
14
  import { backgroundFailurePolicy } from './vision-background-failure-policy.js'
@@ -1033,12 +1033,10 @@ export function installBackgroundCapabilityProfiling(ctx, config, core, store, o
1033
1033
  // Cleanup registration is best-effort; the profiler remains process-scoped.
1034
1034
  }
1035
1035
 
1036
+ installVisionExactCheckClient(ctx)
1037
+
1036
1038
  try {
1037
1039
  ctx?.inject?.(['webServer'], (webCtx) => {
1038
- webCtx.effect(
1039
- () => webCtx.webServer.tapIndex(injectVisionExactCheckClient),
1040
- 'vision-router: exact image check product control',
1041
- )
1042
1040
  webCtx.effect(() => webCtx.webServer.register({
1043
1041
  kind: 'exact',
1044
1042
  path: VISION_CAPABILITY_RUNTIME_PATH,
@@ -1,4 +1,4 @@
1
- import { htmlHasScriptMarker } from './html-script-marker.js'
1
+ import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
2
2
 
3
3
  const CLIENT_MARK = 'data-vision-router-capability-benchmark'
4
4
 
@@ -412,17 +412,14 @@ export const CAPABILITY_BENCHMARK_CLIENT = String.raw`(function(){
412
412
  })();`
413
413
 
414
414
  export function injectCapabilityBenchmarkClient(html) {
415
- if (typeof html !== 'string' || htmlHasScriptMarker(html, CLIENT_MARK)) return html
416
- const script = `<script ${CLIENT_MARK}>${CAPABILITY_BENCHMARK_CLIENT.replace(/<\/script/gi, '<\\/script')}</script>`
417
- const head = html.indexOf('<head>')
418
- return head === -1 ? `${script}${html}` : `${html.slice(0, head + 6)}${script}${html.slice(head + 6)}`
415
+ return injectClientCarrierHtml(html, CLIENT_MARK, CAPABILITY_BENCHMARK_CLIENT)
419
416
  }
420
417
 
421
418
  export function installCapabilityBenchmarkClient(ctx) {
422
- ctx?.inject?.(['webServer'], (webCtx) => {
423
- webCtx.effect(
424
- () => webCtx.webServer.tapIndex(injectCapabilityBenchmarkClient),
425
- 'vision-router: capability benchmark Host presentation client controls',
426
- )
419
+ installClientScriptCarriers(ctx, {
420
+ marker: CLIENT_MARK,
421
+ prelude: CAPABILITY_BENCHMARK_CLIENT,
422
+ label: 'vision-router: capability benchmark client',
423
+ injectHtml: injectCapabilityBenchmarkClient,
427
424
  })
428
425
  }
@@ -307,6 +307,7 @@ export function createExactCapabilityInvoker(ctx, core, candidate, config, optio
307
307
  maxTokens: benchmarkMaxTokensForProvider(exactHttpProvider),
308
308
  signal: callSignal,
309
309
  affinityId: benchmarkAffinityId,
310
+ providerTransport: options.providerTransport,
310
311
  resolveCredential: (ref) => resolvedCredentialValue(ctx, ref),
311
312
  }),
312
313
  fixtureTimeoutMs,
@@ -0,0 +1,216 @@
1
+ import { probeLocalBackends } from './local-connection-probe.js'
2
+ import {
3
+ METADATA_RESPONSE_MAX_BYTES,
4
+ readResponseJsonBounded,
5
+ } from './http-body-limit.js'
6
+
7
+ export const VISION_TEST_CONNECTION_PATH = '/_dsh/vision-router/test-connection'
8
+ export const VISION_MODEL_CAPABILITIES_PATH = '/_dsh/vision-router/model-capabilities'
9
+
10
+ function adapterAvailable(llm, provider) {
11
+ if (!llm || typeof llm.registration !== 'function') return false
12
+ try {
13
+ llm.registration(provider)
14
+ return true
15
+ } catch {
16
+ return false
17
+ }
18
+ }
19
+
20
+ async function probeModels(fetchImpl, baseURL, expectedModel, startedAt) {
21
+ try {
22
+ const response = await fetchImpl(`${String(baseURL).replace(/\/$/, '')}/models`, {
23
+ method: 'GET',
24
+ signal: AbortSignal.timeout(8000),
25
+ })
26
+ const latencyMs = Date.now() - startedAt
27
+ if (!response.ok) {
28
+ return { ok: false, latencyMs, status: response.status, error: `HTTP ${response.status}` }
29
+ }
30
+ const data = await readResponseJsonBounded(
31
+ response,
32
+ METADATA_RESPONSE_MAX_BYTES,
33
+ { label: 'vision backend /models response' },
34
+ ).catch(() => undefined)
35
+ const models = data && Array.isArray(data.data) ? data.data : undefined
36
+ const count = models ? models.length : undefined
37
+ if (
38
+ typeof expectedModel === 'string' &&
39
+ expectedModel !== '' &&
40
+ models &&
41
+ !models.some((entry) => entry && String(entry.id) === expectedModel)
42
+ ) {
43
+ return {
44
+ ok: false,
45
+ latencyMs,
46
+ status: response.status,
47
+ models: count,
48
+ endpoint: baseURL,
49
+ error: `configured model "${expectedModel}" was not returned by /models`,
50
+ }
51
+ }
52
+ return { ok: true, latencyMs, status: response.status, models: count, endpoint: baseURL }
53
+ } catch (error) {
54
+ return {
55
+ ok: false,
56
+ latencyMs: Date.now() - startedAt,
57
+ error: error && error.message ? error.message : String(error),
58
+ }
59
+ }
60
+ }
61
+
62
+ function getArray(factory) {
63
+ const value = typeof factory === 'function' ? factory() : factory
64
+ return Array.isArray(value) ? value : []
65
+ }
66
+
67
+ /**
68
+ * Product diagnostics/settings-support routes kept outside visual Core.
69
+ *
70
+ * The caller supplies three coherent domain faces instead of a Core facade:
71
+ * connection candidates/probes, capability snapshot production, and the
72
+ * Host-ownership state rendered in the connection response. Authentication
73
+ * and remote/local capability policy stay outside this module in the existing
74
+ * scoped WebServer boundary.
75
+ */
76
+ export function installVisionDiagnosticsRoutes(ctx, options = {}) {
77
+ if (!ctx || typeof ctx.inject !== 'function') return ctx
78
+
79
+ const connection = options.connection ?? {}
80
+ const capabilities = options.capabilities ?? {}
81
+ const ownership = options.ownership ?? {}
82
+ const resolveCapability = connection.resolveCapability
83
+ const collectCapabilities = capabilities.collect
84
+ if (typeof resolveCapability !== 'function') {
85
+ throw new TypeError('vision diagnostics routes require a connection capability resolver')
86
+ }
87
+ if (typeof collectCapabilities !== 'function') {
88
+ throw new TypeError('vision diagnostics routes require a capability collector')
89
+ }
90
+
91
+ const llm = ctx.llm
92
+ const httpRoute = typeof connection.httpRoute === 'string' && connection.httpRoute !== ''
93
+ ? connection.httpRoute
94
+ : 'vision-http'
95
+ const fetchImpl = typeof options.fetchImpl === 'function' ? options.fetchImpl : globalThis.fetch
96
+
97
+ const probeConnection = async () => {
98
+ const started = Date.now()
99
+ let first
100
+ for (const pair of getArray(connection.candidatePairs)) {
101
+ if (!pair) continue
102
+ if (pair.provider !== httpRoute && !adapterAvailable(llm, pair.provider)) continue
103
+ const capability = await resolveCapability(pair.provider, pair.model)
104
+ if (capability?.attemptable !== false) {
105
+ first = pair
106
+ break
107
+ }
108
+ }
109
+
110
+ const localProbe = await probeLocalBackends(
111
+ getArray(connection.localBackends),
112
+ (provider) => probeModels(fetchImpl, provider.baseURL, provider.model, started),
113
+ started,
114
+ )
115
+ if (localProbe !== undefined) return localProbe
116
+
117
+ const httpBackends = getArray(connection.httpBackends)
118
+ if (first !== undefined && first.provider === httpRoute) {
119
+ const entry = httpBackends.find((provider) => `${provider.name}/${provider.model}` === first.model)
120
+ if (entry !== undefined) return probeModels(fetchImpl, entry.baseURL, entry.model, started)
121
+ }
122
+
123
+ if (first !== undefined) {
124
+ try {
125
+ await llm.resolveModelInfo(first.provider, first.model)
126
+ return {
127
+ ok: true,
128
+ latencyMs: Date.now() - started,
129
+ detail: `${first.provider}/${first.model} metadata resolved (no network call)`,
130
+ }
131
+ } catch (error) {
132
+ return {
133
+ ok: false,
134
+ latencyMs: Date.now() - started,
135
+ error: error && error.message ? error.message : String(error),
136
+ }
137
+ }
138
+ }
139
+
140
+ const httpFirst = httpBackends[0]
141
+ if (httpFirst !== undefined) return probeModels(fetchImpl, httpFirst.baseURL, httpFirst.model, started)
142
+ return { ok: false, error: 'no usable vision provider configured' }
143
+ }
144
+
145
+ ctx.inject(['webServer'], (webCtx) => {
146
+ webCtx.effect(() => {
147
+ return webCtx.webServer.register({
148
+ kind: 'exact',
149
+ path: VISION_TEST_CONNECTION_PATH,
150
+ handler: async (req, res) => {
151
+ if (req.method !== 'GET') {
152
+ res.setHeader('Allow', 'GET')
153
+ res.writeHead(405)
154
+ res.end()
155
+ return
156
+ }
157
+ try {
158
+ const result = await probeConnection()
159
+ const officialRouteAvailable = adapterAvailable(llm, 'deepseek-official')
160
+ result.stealth = {
161
+ configured: ownership.stealthConfigured,
162
+ active: false,
163
+ reason: ownership.hostOwnsOfficialDeepSeek && !officialRouteAvailable
164
+ ? 'host-owned-official-unavailable'
165
+ : undefined,
166
+ hostOwned: ownership.hostOwnsOfficialDeepSeek,
167
+ }
168
+ res.writeHead(result.ok ? 200 : 502, { 'content-type': 'application/json' })
169
+ res.end(JSON.stringify(result))
170
+ } catch (error) {
171
+ res.writeHead(500, { 'content-type': 'application/json' })
172
+ res.end(JSON.stringify({
173
+ ok: false,
174
+ error: error && error.message ? error.message : String(error),
175
+ }))
176
+ }
177
+ },
178
+ })
179
+ }, 'vision-router: test-connection route')
180
+ })
181
+
182
+ ctx.inject(['webServer'], (webCtx) => {
183
+ webCtx.effect(
184
+ () => webCtx.webServer.register({
185
+ kind: 'exact',
186
+ path: VISION_MODEL_CAPABILITIES_PATH,
187
+ handler: async (req, res) => {
188
+ if (req.method !== 'GET') {
189
+ res.setHeader('Allow', 'GET')
190
+ res.writeHead(405)
191
+ res.end()
192
+ return
193
+ }
194
+ try {
195
+ const snapshot = await collectCapabilities()
196
+ res.writeHead(200, { 'content-type': 'application/json' })
197
+ res.end(JSON.stringify({
198
+ capabilities: snapshot,
199
+ builtinFallback: getArray(capabilities.builtinFallback),
200
+ anonymousRpmPerModel: 2,
201
+ }))
202
+ } catch (error) {
203
+ res.writeHead(500, { 'content-type': 'application/json' })
204
+ res.end(JSON.stringify({
205
+ capabilities: {},
206
+ error: error && error.message ? error.message : String(error),
207
+ }))
208
+ }
209
+ },
210
+ }),
211
+ 'vision-router: model capabilities route',
212
+ )
213
+ })
214
+
215
+ return ctx
216
+ }
@@ -1,3 +1,5 @@
1
+ import { visionRouterMessageSource } from './session-message-source-compat.js'
2
+
1
3
  export const VISION_SUCCESS_GUIDANCE =
2
4
  'Vision Router trusted execution metadata: the preceding vision_describe call SUCCEEDED. ' +
3
5
  'This metadata is not image content. If that successful evidence resolves the visual fact needed for the current task, stop making additional vision calls for that fact and continue the remaining user task from this evidence. ' +
@@ -34,6 +36,6 @@ export function visionDescribeSuccessContext(exec, result) {
34
36
  role: 'user',
35
37
  id: `vision-router-evidence-success-${String(exec.callId)}`,
36
38
  content: [{ type: 'text', text: VISION_SUCCESS_GUIDANCE }],
37
- source: { kind: 'plugin', plugin: 'dsh-vision-router' },
39
+ source: visionRouterMessageSource(exec.agent.session),
38
40
  }
39
41
  }
@@ -1,4 +1,4 @@
1
- import { htmlHasScriptMarker } from './html-script-marker.js'
1
+ import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
2
2
 
3
3
  const CLIENT_MARK = 'data-vision-router-exact-check'
4
4
 
@@ -185,9 +185,14 @@ export const VISION_EXACT_CHECK_CLIENT = String.raw`(function(){
185
185
  })();`
186
186
 
187
187
  export function injectVisionExactCheckClient(html) {
188
- if (typeof html !== 'string' || htmlHasScriptMarker(html, CLIENT_MARK)) return html
189
- const script = `<script ${CLIENT_MARK}>${VISION_EXACT_CHECK_CLIENT.replace(/<\/script/gi, '<\\/script')}</script>`
190
- const closeHead = html.indexOf('</head>')
191
- if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
192
- return `${html}${script}`
188
+ return injectClientCarrierHtml(html, CLIENT_MARK, VISION_EXACT_CHECK_CLIENT)
189
+ }
190
+
191
+ export function installVisionExactCheckClient(ctx) {
192
+ installClientScriptCarriers(ctx, {
193
+ marker: CLIENT_MARK,
194
+ prelude: VISION_EXACT_CHECK_CLIENT,
195
+ label: 'vision-router: exact image check product control',
196
+ injectHtml: injectVisionExactCheckClient,
197
+ })
193
198
  }
@@ -1,3 +1,4 @@
1
+ import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
1
2
  const VISION_MODE_WRAPPER_SOURCE = 'deepseek-official'
2
3
  const DEFAULT_WRAPPER_ROUTE = 'deepseek-vision'
3
4
  const MODEL_SELECTION_TARGET = '@deepseek-ai/dsh-client-ui-model-selection'
@@ -454,19 +455,14 @@ export const VISION_MODEL_VISIBILITY_PRELUDE = String.raw`(function(){
454
455
  })();`
455
456
 
456
457
  export function injectVisionModelVisibilityBoundary(html) {
457
- if (typeof html !== 'string' || html.includes(VISIBILITY_MARK)) return html
458
- const safe = VISION_MODEL_VISIBILITY_PRELUDE.replace(/<\/script/gi, '<\\/script')
459
- const script = `<script ${VISIBILITY_MARK}>${safe}</script>`
460
- const closeHead = html.indexOf('</head>')
461
- if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
462
- return `${html}${script}`
458
+ return injectClientCarrierHtml(html, VISIBILITY_MARK, VISION_MODEL_VISIBILITY_PRELUDE)
463
459
  }
464
460
 
465
461
  export function installVisionModelVisibilityBoundary(ctx) {
466
- ctx?.inject?.(['webServer'], (webCtx) => {
467
- webCtx.effect(
468
- () => webCtx.webServer.tapIndex(injectVisionModelVisibilityBoundary),
469
- 'vision-router: model visibility boundary',
470
- )
462
+ installClientScriptCarriers(ctx, {
463
+ marker: VISIBILITY_MARK,
464
+ prelude: VISION_MODEL_VISIBILITY_PRELUDE,
465
+ label: 'vision-router: model visibility boundary',
466
+ injectHtml: injectVisionModelVisibilityBoundary,
471
467
  })
472
468
  }
@@ -1,4 +1,4 @@
1
- import { htmlHasScriptMarker } from './html-script-marker.js'
1
+ import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
2
2
  import { VISION_MODEL_VISIBILITY_PRELUDE } from './vision-model-visibility-boundary-main.js'
3
3
 
4
4
  const VISIBILITY_MARK = 'data-vision-router-model-visibility-boundary'
@@ -10,19 +10,14 @@ export {
10
10
  } from './vision-model-visibility-boundary-main.js'
11
11
 
12
12
  export function injectVisionModelVisibilityBoundary(html) {
13
- if (typeof html !== 'string' || htmlHasScriptMarker(html, VISIBILITY_MARK)) return html
14
- const safe = VISION_MODEL_VISIBILITY_PRELUDE.replace(/<\/script/gi, '<\\/script')
15
- const script = `<script ${VISIBILITY_MARK}>${safe}</script>`
16
- const closeHead = html.indexOf('</head>')
17
- if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
18
- return `${html}${script}`
13
+ return injectClientCarrierHtml(html, VISIBILITY_MARK, VISION_MODEL_VISIBILITY_PRELUDE)
19
14
  }
20
15
 
21
16
  export function installVisionModelVisibilityBoundary(ctx) {
22
- ctx?.inject?.(['webServer'], (webCtx) => {
23
- webCtx.effect(
24
- () => webCtx.webServer.tapIndex(injectVisionModelVisibilityBoundary),
25
- 'vision-router: model visibility boundary',
26
- )
17
+ installClientScriptCarriers(ctx, {
18
+ marker: VISIBILITY_MARK,
19
+ prelude: VISION_MODEL_VISIBILITY_PRELUDE,
20
+ label: 'vision-router: model visibility boundary',
21
+ injectHtml: injectVisionModelVisibilityBoundary,
27
22
  })
28
23
  }
@@ -33,8 +33,6 @@ const moduleFetch =
33
33
  ? globalThis.fetch.bind(globalThis)
34
34
  : undefined
35
35
 
36
- const installed = []
37
-
38
36
  function live(value) {
39
37
  return typeof value === 'function' ? value() : value
40
38
  }
@@ -175,30 +173,3 @@ export function createVisionProviderTransport({
175
173
  },
176
174
  })
177
175
  }
178
-
179
- /**
180
- * Scoped process/profile carrier for Router-owned compatibility calls whose
181
- * mature signatures do not yet accept an explicit VisionProviderTransport.
182
- * The registry never patches global fetch and is released with the public
183
- * plugin lifecycle. Remove it only after every production compatibility caller
184
- * receives the transport explicitly and no production code reads
185
- * currentVisionProviderTransport().
186
- */
187
- export function installVisionProviderTransport(transport) {
188
- if (!transport || typeof transport.fetch !== 'function') {
189
- throw new TypeError('invalid vision provider transport')
190
- }
191
- const token = Object.freeze({ transport })
192
- installed.push(token)
193
- let active = true
194
- return () => {
195
- if (!active) return
196
- active = false
197
- const index = installed.indexOf(token)
198
- if (index >= 0) installed.splice(index, 1)
199
- }
200
- }
201
-
202
- export function currentVisionProviderTransport() {
203
- return installed[installed.length - 1]?.transport
204
- }
@@ -1,4 +1,4 @@
1
- import { htmlHasScriptMarker } from './html-script-marker.js'
1
+ import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
2
2
 
3
3
  const ROUTING_SETTINGS_PRELUDE_MARK = 'data-vision-router-routing-settings'
4
4
 
@@ -536,18 +536,14 @@ export const VISION_ROUTING_SETTINGS_PRELUDE = String.raw`(function(){
536
536
  })();`
537
537
 
538
538
  export function injectVisionRoutingSettingsPrelude(html) {
539
- if (typeof html !== 'string' || htmlHasScriptMarker(html, ROUTING_SETTINGS_PRELUDE_MARK)) return html
540
- const script = `<script ${ROUTING_SETTINGS_PRELUDE_MARK}>${VISION_ROUTING_SETTINGS_PRELUDE.replace(/<\/script/gi, '<\\/script')}</script>`
541
- const closeHead = html.indexOf('</head>')
542
- if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
543
- return `${html}${script}`
539
+ return injectClientCarrierHtml(html, ROUTING_SETTINGS_PRELUDE_MARK, VISION_ROUTING_SETTINGS_PRELUDE)
544
540
  }
545
541
 
546
542
  export function installVisionRoutingSettingsPrelude(ctx) {
547
- ctx?.inject?.(['webServer'], (webCtx) => {
548
- webCtx.effect(
549
- () => webCtx.webServer.tapIndex(injectVisionRoutingSettingsPrelude),
550
- 'vision-router: v2 routing settings product panel',
551
- )
543
+ installClientScriptCarriers(ctx, {
544
+ marker: ROUTING_SETTINGS_PRELUDE_MARK,
545
+ prelude: VISION_ROUTING_SETTINGS_PRELUDE,
546
+ label: 'vision-router: Vision routing Settings prelude',
547
+ injectHtml: injectVisionRoutingSettingsPrelude,
552
548
  })
553
549
  }
@@ -251,6 +251,7 @@ const CLIENT_PRELUDE = String.raw`(function(){
251
251
  var listeners = new Set();
252
252
  var pending = new WeakMap();
253
253
  var recoveries = new WeakMap();
254
+ var selectionGenerations = new WeakMap();
254
255
  var inFlight = null;
255
256
  var refreshedAt = 0;
256
257
  function emit(){ listeners.forEach(function(fn){ try { fn(); } catch (_) {} }); }
@@ -302,13 +303,29 @@ const CLIENT_PRELUDE = String.raw`(function(){
302
303
  var key = JSON.stringify(selection || {});
303
304
  var held = pending.get(directory);
304
305
  if (held && held.key === key) return held.promise;
306
+ var generation = (selectionGenerations.get(directory) || 0) + 1;
307
+ selectionGenerations.set(directory, generation);
308
+ var current = function(){ return selectionGenerations.get(directory) === generation; };
305
309
  var recovery = recoveryFor(directory);
306
310
  recovery.clear();
307
311
  var promise = Promise.resolve().then(function(){ return directory.select(selection); }).then(
308
- function(value){ recovery.clear(); return value; },
309
- function(error){ recovery.fail(error); throw error; }
312
+ function(value){
313
+ if (!current()) return value;
314
+ if (value && value.ok === false) {
315
+ var failure = value.error;
316
+ var message = failure && failure.message
317
+ ? String(failure.message)
318
+ : String(failure || 'model operation failed');
319
+ if (failure && failure.code) message = String(failure.code) + ': ' + message;
320
+ recovery.fail(new Error(message));
321
+ return value;
322
+ }
323
+ recovery.clear();
324
+ return value;
325
+ },
326
+ function(error){ if (current()) recovery.fail(error); throw error; }
310
327
  );
311
- pending.set(directory, { key: key, promise: promise });
328
+ pending.set(directory, { key: key, promise: promise, generation: generation });
312
329
  promise.finally(function(){
313
330
  var latest = pending.get(directory);
314
331
  if (latest && latest.promise === promise) pending.delete(directory);
@@ -331,9 +348,9 @@ function replaceAll(source, before, after) {
331
348
  return source.includes(before) ? source.split(before).join(after) : source
332
349
  }
333
350
 
334
- export function hardenVisionToggleHtml(html) {
335
- if (typeof html !== 'string') return html
336
- let next = html
351
+ export function hardenVisionClientSource(source) {
352
+ if (typeof source !== 'string') return source
353
+ let next = source
337
354
 
338
355
  next = replaceAll(
339
356
  next,
@@ -391,8 +408,8 @@ export function hardenVisionToggleHtml(html) {
391
408
 
392
409
  next = replaceAll(
393
410
  next,
394
- ` try {\n return Promise.resolve(directory.select(selection)).then(\n function(){ return true; },\n function(){ return false; }\n );\n } catch (_) {\n return Promise.resolve(false);\n }`,
395
- ` var hardening = window.__dshVisionRouterRootHardening;\n var operation;\n try { operation = hardening ? hardening.select(directory, selection) : Promise.resolve(directory.select(selection)); }\n catch (_) { return Promise.resolve(false); }\n return Promise.resolve(operation).then(function(){ return true; }, function(){ return false; });`,
411
+ ` try {\n return Promise.resolve(directory.select(selection)).then(\n function(result){ return !result || result.ok !== false; },\n function(){ return false; }\n );\n } catch (_) {\n return Promise.resolve(false);\n }`,
412
+ ` var hardening = window.__dshVisionRouterRootHardening;\n var operation;\n try { operation = hardening ? hardening.select(directory, selection) : Promise.resolve(directory.select(selection)); }\n catch (_) { return Promise.resolve(false); }\n return Promise.resolve(operation).then(function(result){ return !result || result.ok !== false; }, function(){ return false; });`,
396
413
  )
397
414
 
398
415
  next = replaceAll(
@@ -430,6 +447,13 @@ export function hardenVisionToggleHtml(html) {
430
447
  ` var hardening = window.__dshVisionRouterRootHardening;\n var recovery = hardening ? hardening.recoveryFor(directory) : undefined;\n var store = wrapStore(directory.store, settings, recovery);`,
431
448
  )
432
449
 
450
+ return next
451
+ }
452
+
453
+ export function hardenVisionToggleHtml(html) {
454
+ if (typeof html !== 'string') return html
455
+ let next = hardenVisionClientSource(html)
456
+
433
457
  if (!next.includes(OWNERSHIP_MARK)) {
434
458
  const safe = CLIENT_PRELUDE.replace(/<\/script/gi, '<\\/script')
435
459
  const script = `<script ${OWNERSHIP_MARK}>${safe}</script>`
@@ -474,6 +498,24 @@ export function createVisionToggleRootHardening(ctx, config = {}) {
474
498
  }),
475
499
  'vision-router: route ownership',
476
500
  )
501
+ if (typeof webCtx.on === 'function') {
502
+ webCtx.on('webserver/index-inject', (table) => {
503
+ if (!Array.isArray(table)) return
504
+ for (const row of table) {
505
+ if (row?.kind === 'script' && typeof row.text === 'string') {
506
+ row.text = hardenVisionClientSource(row.text)
507
+ }
508
+ }
509
+ const signature = `/* ${OWNERSHIP_MARK} */`
510
+ if (!table.some((row) => row?.kind === 'script' && typeof row.text === 'string' && row.text.includes(signature))) {
511
+ table.push({
512
+ kind: 'script',
513
+ placement: 'head',
514
+ text: `${signature}\n${CLIENT_PRELUDE}`,
515
+ })
516
+ }
517
+ })
518
+ }
477
519
  webCtx.effect(
478
520
  () => webCtx.webServer.tapIndex(hardenVisionToggleHtml),
479
521
  'vision-router: vision toggle root hardening',
@@ -540,7 +540,11 @@ function wrapLlm(llm, runtimeState) {
540
540
  : state?.callConfigAuthority === 'transparent'
541
541
  ? projectTransparentCallConfig(options, state, runtimeState)
542
542
  : options
543
- return stream.call(target, projected)
543
+ const signal = combineSignals(projected?.signal, state?.signal)
544
+ const call = signal === undefined || signal === projected?.signal
545
+ ? projected
546
+ : { ...(projected ?? {}), signal }
547
+ return stream.call(target, call)
544
548
  }
545
549
  }
546
550
  if (property === 'registerAdapter') {
@@ -1,3 +1,4 @@
1
+ import { injectClientCarrierHtml, installClientScriptCarriers } from './client-script-carrier.js'
1
2
  const CLIENT_PRELUDE_MARK = 'data-vision-router-turn-budget'
2
3
 
3
4
  export const VISION_TURN_BUDGET_CLIENT_PRELUDE = String.raw`(function(){
@@ -259,18 +260,14 @@ export const VISION_TURN_BUDGET_CLIENT_PRELUDE = String.raw`(function(){
259
260
  })();`
260
261
 
261
262
  export function injectVisionTurnBudgetClientPrelude(html) {
262
- if (typeof html !== 'string' || html.includes(CLIENT_PRELUDE_MARK)) return html
263
- const script = `<script ${CLIENT_PRELUDE_MARK}>${VISION_TURN_BUDGET_CLIENT_PRELUDE.replace(/<\/script/gi, '<\\/script')}</script>`
264
- const closeHead = html.indexOf('</head>')
265
- if (closeHead !== -1) return `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
266
- return `${html}${script}`
263
+ return injectClientCarrierHtml(html, CLIENT_PRELUDE_MARK, VISION_TURN_BUDGET_CLIENT_PRELUDE)
267
264
  }
268
265
 
269
266
  export function installVisionTurnBudgetClientPrelude(ctx) {
270
- ctx?.inject?.(['webServer'], (webCtx) => {
271
- webCtx.effect(
272
- () => webCtx.webServer.tapIndex(injectVisionTurnBudgetClientPrelude),
273
- 'vision-router: turn budget client prelude',
274
- )
267
+ installClientScriptCarriers(ctx, {
268
+ marker: CLIENT_PRELUDE_MARK,
269
+ prelude: VISION_TURN_BUDGET_CLIENT_PRELUDE,
270
+ label: 'vision-router: turn budget client prelude',
271
+ injectHtml: injectVisionTurnBudgetClientPrelude,
275
272
  })
276
273
  }
@@ -1,6 +1,6 @@
1
- import { htmlHasScriptMarker } from '../html-script-marker.js'
1
+ import { injectClientCarrierHtml, installClientScriptCarriers } from '../client-script-carrier.js'
2
2
  import { installVisionRouterRemoteSettingsBridge } from '../remote-settings-bridge.js'
3
- import { installSettingsRc8ClientLifecycle } from '../settings-client-rc8-lifecycle.js'
3
+ import { installSettingsClientLoaderLifecycle } from '../settings-client-loader-lifecycle.js'
4
4
 
5
5
  const SETTINGS_CONFIG_FORMS_MARK = 'data-vision-router-settings-configforms-compat'
6
6
 
@@ -187,26 +187,21 @@ export const SETTINGS_CONFIG_FORMS_CLIENT_PRELUDE = String.raw`(function(){
187
187
  })();`
188
188
 
189
189
  export function injectSettingsConfigFormsCompatPrelude(html) {
190
- if (typeof html !== 'string' || htmlHasScriptMarker(html, SETTINGS_CONFIG_FORMS_MARK)) return html
191
- const safe = SETTINGS_CONFIG_FORMS_CLIENT_PRELUDE.replace(/<\/script/gi, '<\\/script')
192
- const script = `<script ${SETTINGS_CONFIG_FORMS_MARK}>${safe}</script>`
193
- const closeHead = html.indexOf('</head>')
194
- return closeHead === -1 ? `${html}${script}` : `${html.slice(0, closeHead)}${script}${html.slice(closeHead)}`
190
+ return injectClientCarrierHtml(html, SETTINGS_CONFIG_FORMS_MARK, SETTINGS_CONFIG_FORMS_CLIENT_PRELUDE)
195
191
  }
196
192
 
197
193
  export function installSettingsConfigFormsCompat(ctx) {
198
- if (!ctx || typeof ctx.inject !== 'function') return
199
- ctx.inject(['webServer'], (webCtx) => {
200
- webCtx.effect(
201
- () => webCtx.webServer.tapIndex(injectSettingsConfigFormsCompatPrelude),
202
- 'vision-router: settings configForms compatibility',
203
- )
194
+ installClientScriptCarriers(ctx, {
195
+ marker: SETTINGS_CONFIG_FORMS_MARK,
196
+ prelude: SETTINGS_CONFIG_FORMS_CLIENT_PRELUDE,
197
+ label: 'vision-router: settings configForms compatibility',
198
+ injectHtml: injectSettingsConfigFormsCompatPrelude,
204
199
  })
205
200
  }
206
201
 
207
202
  export function installVisionRemoteSettingsClient(ctx, logger) {
208
203
  installVisionRouterRemoteSettingsBridge(ctx, logger)
209
204
  installSettingsConfigFormsCompat(ctx)
210
- installSettingsRc8ClientLifecycle(ctx)
205
+ installSettingsClientLoaderLifecycle(ctx)
211
206
  return ctx
212
207
  }