dsh-vision-router 2.1.7 → 2.2.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 (41) hide show
  1. package/README.md +8 -14
  2. package/README.zh.md +8 -14
  3. package/docs/architecture/compat-inventory.md +1 -1
  4. package/docs/architecture/dsh-compatibility-matrix.md +4 -1
  5. package/docs/architecture/dsh-support-window.md +1 -2
  6. package/docs/releases/v2.2.0.md +25 -0
  7. package/docs/remote-settings.md +2 -0
  8. package/entry.js +2 -0
  9. package/index.js +355 -64
  10. package/lib/artifact-boundary.js +11 -0
  11. package/lib/artifact-io.js +60 -7
  12. package/lib/catalog-corrections.js +5 -0
  13. package/lib/client.js +22 -17
  14. package/lib/core-primitives.js +115 -5
  15. package/lib/degraded-local-evidence.js +39 -0
  16. package/lib/dsh-contract-compat.js +202 -0
  17. package/lib/dsh-support-window.js +1 -1
  18. package/lib/image-offload-compat.js +38 -0
  19. package/lib/local-vision-stabilizer.js +15 -6
  20. package/lib/official-deepseek-catalog.js +120 -0
  21. package/lib/ollama-cold-start.js +3 -15
  22. package/lib/remote-settings-bridge.js +19 -1
  23. package/lib/replay-delegation.js +41 -21
  24. package/lib/runtime-composition.js +15 -2
  25. package/lib/runtime-i18n-boundary.js +4 -13
  26. package/lib/runtime-i18n.js +2 -2
  27. package/lib/session-surface-compat.js +40 -0
  28. package/lib/session-turn-resolver.js +57 -0
  29. package/lib/session-vision-index.js +206 -93
  30. package/lib/session-vision-mode-boundary.js +83 -6
  31. package/lib/session-vision-runtime.js +8 -2
  32. package/lib/session-vision-state.js +0 -13
  33. package/lib/tesseract-exec-compat.js +16 -2
  34. package/lib/twin-image-capability-fallback.js +2 -11
  35. package/lib/vision-artifact-store.js +16 -3
  36. package/lib/vision-attachment-handle-runtime.js +9 -21
  37. package/lib/vision-backend-runtime-policy.js +7 -6
  38. package/lib/vision-breaker-shadow-health.js +15 -5
  39. package/lib/vision-evidence-guidance.js +39 -0
  40. package/lib/vision-resilience.js +73 -1
  41. package/package.json +11 -6
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  resolveArtifactTarget,
3
3
  writeArtifactFile,
4
+ writePersistentArtifactFile,
4
5
  } from './artifact-io.js'
5
6
  import {
6
7
  retainArtifactRun,
@@ -46,6 +47,17 @@ export function createVisionArtifactStore({ workspace, artifactsDir, deps = {} }
46
47
  )
47
48
  }
48
49
 
50
+ const publishPersistent = (relativePath, data, options = {}) => {
51
+ const current = location()
52
+ return writePersistentArtifactFile(
53
+ current.workspace,
54
+ current.artifactsDir,
55
+ relativePath,
56
+ data,
57
+ callDeps(deps, options),
58
+ )
59
+ }
60
+
49
61
  return Object.freeze({
50
62
  resolve(relativePath, options = {}) {
51
63
  const current = location()
@@ -58,10 +70,11 @@ export function createVisionArtifactStore({ workspace, artifactsDir, deps = {} }
58
70
  },
59
71
 
60
72
  publish,
73
+ publishPersistent,
61
74
 
62
- // Temporary-vs-persistent placement is authority/lifetime driven: the
63
- // ambient managed artifactRunId decides whether hardened IO uses `.runs`.
64
- // Keeping this method as an alias avoids inventing a second placement path.
75
+ // Temporary placement remains authority/lifetime driven by the ambient
76
+ // random managed artifactRunId. publishPersistent is the narrow handoff
77
+ // exception and uses one stable reserved run that retention still owns.
65
78
  publishTemporary(relativePath, data, options = {}) {
66
79
  return publish(relativePath, data, options)
67
80
  },
@@ -29,13 +29,11 @@ function attachmentIdOf(ref) {
29
29
  return id === '' ? undefined : id
30
30
  }
31
31
 
32
- function sessionEvents(session) {
32
+ function sessionMessages(session) {
33
33
  try {
34
- if (session && typeof session.snapshotEvents === 'function') {
35
- const events = session.snapshotEvents()
36
- return Array.isArray(events) ? events : []
37
- }
38
- return Array.isArray(session?.events) ? session.events : []
34
+ if (!session || typeof session.deriveMessages !== 'function') return []
35
+ const messages = session.deriveMessages()
36
+ return Array.isArray(messages) ? messages : []
39
37
  } catch {
40
38
  return []
41
39
  }
@@ -55,18 +53,9 @@ function collectImageRefsFromMessages(messages, out) {
55
53
  for (const message of messages) collectImageRefsFromBlocks(message?.content, out)
56
54
  }
57
55
 
58
- function authorizedRefs(core, agent) {
56
+ function authorizedRefs(agent) {
59
57
  const refs = []
60
- const session = agent?.session
61
- const events = sessionEvents(session)
62
- if (typeof core?.collectEventAttachmentRefs === 'function') {
63
- try {
64
- const durable = core.collectEventAttachmentRefs(events)
65
- if (Array.isArray(durable)) refs.push(...durable)
66
- } catch {
67
- // The resolver remains fail-closed; pending refs below may still suffice.
68
- }
69
- }
58
+ collectImageRefsFromMessages(sessionMessages(agent?.session), refs)
70
59
  collectImageRefsFromMessages(agent?.inbox?.nextTurn, refs)
71
60
  collectImageRefsFromMessages(agent?.inbox?.nextStep, refs)
72
61
 
@@ -99,12 +88,12 @@ function isCanonicalSha256Id(value) {
99
88
  * only when exactly one image authorized by the current Session has that
100
89
  * prefix. Zero or multiple matches fail closed.
101
90
  */
102
- export function resolveProjectedAttachmentHandle(handle, { core, sessionVisionIndex, agent } = {}) {
91
+ export function resolveProjectedAttachmentHandle(handle, { sessionVisionIndex, agent } = {}) {
103
92
  const token = typeof handle === 'string' ? handle.trim() : ''
104
93
  if (!isProjectedAttachmentHandle(token)) return { kind: 'not-projected', value: handle }
105
94
 
106
95
  const session = agent?.session
107
- const refs = authorizedRefs(core, agent)
96
+ const refs = authorizedRefs(agent)
108
97
  const wanted = token.toLowerCase()
109
98
  const matches = refs.filter((ref) => {
110
99
  const id = attachmentIdOf(ref)
@@ -124,7 +113,7 @@ export function resolveProjectedAttachmentHandle(handle, { core, sessionVisionIn
124
113
  try {
125
114
  sessionVisionIndex?.recordAttachments?.(session, [candidate])
126
115
  } catch {
127
- // A durable event can still be recovered by lookupAttachment below.
116
+ // Authorization is still fail-closed if the bounded cache cannot be warmed.
128
117
  }
129
118
  if (!sessionVisionIndex || typeof sessionVisionIndex.lookupAttachment !== 'function') {
130
119
  return { kind: 'unknown', handle: token }
@@ -201,7 +190,6 @@ export function wrapVisionAttachmentHandleDefinition(def, options = {}) {
201
190
  ...def,
202
191
  execute(args, exec) {
203
192
  const nextArgs = canonicalizeArgs(args, {
204
- core: options.core,
205
193
  sessionVisionIndex: options.sessionVisionIndex,
206
194
  agent: exec?.agent,
207
195
  toolName: def.name,
@@ -1,4 +1,5 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks'
2
+ import { blocksHaveRetainedImage, isOffloadedImageBlock, offloadedImagePlaceholder } from './image-offload-compat.js'
2
3
 
3
4
  const visionBackendScope = new AsyncLocalStorage()
4
5
  const wrappedContexts = new WeakMap()
@@ -127,12 +128,8 @@ async function resolveCredential(ctx, plan) {
127
128
  return undefined
128
129
  }
129
130
 
130
- function contentHasImage(core, messages) {
131
- return (messages ?? []).some((message) => {
132
- if (!Array.isArray(message?.content)) return false
133
- if (typeof core?.blocksHaveImage === 'function') return core.blocksHaveImage(message.content)
134
- return message.content.some((block) => block?.type === 'image')
135
- })
131
+ function contentHasImage(_core, messages) {
132
+ return (messages ?? []).some((message) => blocksHaveRetainedImage(message?.content))
136
133
  }
137
134
 
138
135
  async function toOpenAIMessages(ctx, messages) {
@@ -151,6 +148,10 @@ async function toOpenAIMessages(ctx, messages) {
151
148
  continue
152
149
  }
153
150
  if (block?.type === 'image' && block.attachment) {
151
+ if (isOffloadedImageBlock(block)) {
152
+ converted.push({ type: 'text', text: offloadedImagePlaceholder(block) })
153
+ continue
154
+ }
154
155
  const stored = await attachments.readImage(block.attachment)
155
156
  const mediaType = block.attachment.mediaType || stored?.mediaType || 'image/png'
156
157
  const data = Buffer.from(stored.data).toString('base64')
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto'
2
+ import { createSessionTurnResolver } from './session-turn-resolver.js'
2
3
 
3
- function turnNumberOf(session) {
4
+ function legacyTurnNumberOf(session) {
4
5
  try {
5
6
  const events = session?.events
6
7
  if (!Array.isArray(events)) return 0
@@ -19,8 +20,15 @@ function sessionIdOf(session) {
19
20
  }
20
21
  }
21
22
 
22
- export function visionBreakerScopeOf(session) {
23
- return `${sessionIdOf(session)}:${turnNumberOf(session)}`
23
+ export function visionBreakerScopeOf(session, turnOf) {
24
+ let turn
25
+ try {
26
+ if (typeof turnOf === 'function') turn = turnOf(session)
27
+ } catch {
28
+ turn = undefined
29
+ }
30
+ if (!Number.isInteger(turn) || turn < 0) turn = legacyTurnNumberOf(session)
31
+ return `${sessionIdOf(session)}:${turn}`
24
32
  }
25
33
 
26
34
  function credentialFingerprintOf(value) {
@@ -76,7 +84,9 @@ function healthFromGate(gate) {
76
84
  * giving shadow code mutation authority. `capture()` is called only while
77
85
  * core.apply() constructs the breaker; `healthForCandidate()` uses peek().
78
86
  */
79
- export function createVisionBreakerShadowHealth(ctx) {
87
+ export function createVisionBreakerShadowHealth(ctx, options = {}) {
88
+ const sessionTurnResolver = options.sessionTurnResolver ?? createSessionTurnResolver(ctx)
89
+ const turnOf = sessionTurnResolver.turnOf
80
90
  let breaker
81
91
  return {
82
92
  capture(value) {
@@ -88,7 +98,7 @@ export function createVisionBreakerShadowHealth(ctx) {
88
98
  const key = typeof candidate?.key === 'string' ? candidate.key : undefined
89
99
  if (!key) return undefined
90
100
  const fingerprint = await visionBreakerFingerprintForCandidate(ctx, candidate)
91
- const scope = visionBreakerScopeOf(context.session)
101
+ const scope = visionBreakerScopeOf(context.session, turnOf)
92
102
  return healthFromGate(breaker.peek(key, fingerprint, scope))
93
103
  },
94
104
 
@@ -0,0 +1,39 @@
1
+ export const VISION_SUCCESS_GUIDANCE =
2
+ 'Vision Router trusted execution metadata: the preceding vision_describe call SUCCEEDED. ' +
3
+ '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. ' +
4
+ 'If no non-visual work remains, answer now in exactly the format the user requested; when the user asks for only one value or short phrase, do not add Markdown, explanation, or a restatement. ' +
5
+ 'Call another vision tool only for a specific unresolved visual fact. For verbatim or exact-text tasks such as identifiers, amounts, times, version strings, or code, preserve every visible symbol, prefix, suffix, and punctuation from the most faithful evidence already collected; a later semantic summary must not overwrite a more exact transcription. Focused OCR or localization is still appropriate when character identity remains uncertain.'
6
+
7
+ function isVisionFailureEnvelope(value) {
8
+ const text = value.trim()
9
+ if (!text.startsWith('{')) return false
10
+ try {
11
+ const parsed = JSON.parse(text)
12
+ return (
13
+ parsed !== null &&
14
+ typeof parsed === 'object' &&
15
+ parsed.ok === false &&
16
+ typeof parsed.code === 'string' &&
17
+ /^VISION_[A-Z0-9_]+$/u.test(parsed.code)
18
+ )
19
+ } catch {
20
+ return false
21
+ }
22
+ }
23
+
24
+ export function visionDescribeSuccessContext(exec, result) {
25
+ if (
26
+ exec?.name !== 'vision_describe' ||
27
+ !exec?.agent ||
28
+ result?.isError !== false ||
29
+ typeof result.value !== 'string' ||
30
+ result.value.trim().length === 0 ||
31
+ isVisionFailureEnvelope(result.value)
32
+ ) return undefined
33
+ return {
34
+ role: 'user',
35
+ id: `vision-router-evidence-success-${String(exec.callId)}`,
36
+ content: [{ type: 'text', text: VISION_SUCCESS_GUIDANCE }],
37
+ source: { kind: 'plugin', plugin: 'dsh-vision-router' },
38
+ }
39
+ }
@@ -376,12 +376,14 @@ export function createVisionTurnMemory({
376
376
  maxScopes = 64,
377
377
  maxSessions = maxScopes,
378
378
  maxAttemptsPerScope = 64,
379
+ maxEvidencePerScope = 32,
379
380
  } = {}) {
380
381
  const scopes = new Map() // scope -> { failedKinds:Set, attempted: [], allFailed:false }
381
382
  const lastScopeBySession = new Map() // bounded LRU sessionId -> scope
382
383
  const scopeLimit = Math.max(1, Math.floor(Number(maxScopes) || 64))
383
384
  const sessionLimit = Math.max(1, Math.floor(Number(maxSessions) || scopeLimit))
384
385
  const attemptLimit = Math.max(1, Math.floor(Number(maxAttemptsPerScope) || 64))
386
+ const evidenceLimit = Math.max(1, Math.floor(Number(maxEvidencePerScope) || 32))
385
387
 
386
388
  const dropScope = (scope) => {
387
389
  scopes.delete(scope)
@@ -410,7 +412,14 @@ export function createVisionTurnMemory({
410
412
  const entry = (scope) => {
411
413
  let hit = scopes.get(scope)
412
414
  if (hit === undefined) {
413
- hit = { failedKinds: new Set(), attempted: [], allFailed: false }
415
+ hit = {
416
+ failedKinds: new Set(),
417
+ attempted: [],
418
+ allFailed: false,
419
+ localOcr: new Map(),
420
+ derivedArtifacts: new Map(),
421
+ degradedRefinements: new Map(),
422
+ }
414
423
  scopes.set(scope, hit)
415
424
  pruneScopes()
416
425
  } else {
@@ -453,6 +462,69 @@ export function createVisionTurnMemory({
453
462
  return hit === undefined ? [] : [...hit.attempted]
454
463
  },
455
464
 
465
+ resolveSource(scope, sourceKey) {
466
+ const key = typeof sourceKey === 'string' ? sourceKey : ''
467
+ const hit = scopes.get(scope)
468
+ if (hit === undefined || key === '') return key
469
+ for (const [artifactToken, rootSource] of hit.derivedArtifacts) {
470
+ if (key === artifactToken || key.includes(artifactToken)) return rootSource
471
+ }
472
+ return key
473
+ },
474
+
475
+ recordLocalOcr(scope, sourceKey, evidence = {}) {
476
+ const hit = entry(scope)
477
+ const key = this.resolveSource(scope, sourceKey)
478
+ if (key === '') return
479
+ hit.localOcr.delete(key)
480
+ hit.localOcr.set(key, Object.freeze({ uncertain: evidence.uncertain === true }))
481
+ while (hit.localOcr.size > evidenceLimit) hit.localOcr.delete(hit.localOcr.keys().next().value)
482
+ },
483
+
484
+ hasLocalOcr(scope, sourceKey) {
485
+ const hit = scopes.get(scope)
486
+ if (hit === undefined) return false
487
+ return hit.localOcr.has(this.resolveSource(scope, sourceKey))
488
+ },
489
+
490
+ degradedEvidenceTokens(scope) {
491
+ const hit = scopes.get(scope)
492
+ if (hit === undefined || !hit.allFailed || hit.localOcr.size === 0) return []
493
+ const tokens = [...hit.localOcr.keys()]
494
+ for (const [artifactToken, rootSource] of hit.derivedArtifacts) {
495
+ if (hit.localOcr.has(rootSource)) tokens.push(artifactToken)
496
+ }
497
+ return tokens
498
+ },
499
+
500
+ recordDerivedArtifact(scope, sourceKey, artifactToken) {
501
+ const token = typeof artifactToken === 'string' ? artifactToken : ''
502
+ if (token === '') return
503
+ const hit = entry(scope)
504
+ const rootSource = this.resolveSource(scope, sourceKey)
505
+ if (rootSource === '') return
506
+ hit.derivedArtifacts.delete(token)
507
+ hit.derivedArtifacts.set(token, rootSource)
508
+ while (hit.derivedArtifacts.size > evidenceLimit) hit.derivedArtifacts.delete(hit.derivedArtifacts.keys().next().value)
509
+ },
510
+
511
+ degradedRefinementCount(scope, sourceKey) {
512
+ const hit = scopes.get(scope)
513
+ if (hit === undefined) return 0
514
+ return hit.degradedRefinements.get(this.resolveSource(scope, sourceKey)) ?? 0
515
+ },
516
+
517
+ recordDegradedRefinement(scope, sourceKey) {
518
+ const hit = entry(scope)
519
+ const key = this.resolveSource(scope, sourceKey)
520
+ if (key === '') return 0
521
+ const count = (hit.degradedRefinements.get(key) ?? 0) + 1
522
+ hit.degradedRefinements.delete(key)
523
+ hit.degradedRefinements.set(key, count)
524
+ while (hit.degradedRefinements.size > evidenceLimit) hit.degradedRefinements.delete(hit.degradedRefinements.keys().next().value)
525
+ return count
526
+ },
527
+
456
528
  stats() {
457
529
  return { scopes: scopes.size, sessions: lastScopeBySession.size }
458
530
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-vision-router",
3
- "version": "2.1.7",
3
+ "version": "2.2.0",
4
4
  "description": "Eyes for text-only DeepSeek Harness agents: built-in free vision chain (no key) + pixel-level vision tools (Q&A, grounding, crop, pixel diff, colors, OCR, SVG trace, cutout, screenshots). One-command install, no Python, image turns work like ordinary tool-calling turns.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -54,8 +54,8 @@
54
54
  "undici": "^7.29.0"
55
55
  },
56
56
  "peerDependencies": {
57
- "@deepseek-ai/dsh-anonymous-user-id": "^0.1.0-rc.8 || ^0.1.1-rc.1 || ^0.1.3-alpha.2 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
58
- "@deepseek-ai/dsh-llm-deepseek": "^0.1.0-rc.8 || ^0.1.1-rc.1 || ^0.1.3-alpha.2 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
57
+ "@deepseek-ai/dsh-anonymous-user-id": "^0.1.0-rc.8 || ^0.1.1-rc.1 || ^0.1.3-alpha.2 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1",
58
+ "@deepseek-ai/dsh-llm-deepseek": "^0.1.0-rc.8 || ^0.1.1-rc.1 || ^0.1.3-alpha.2 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1",
59
59
  "sharp": ">=0.35.3 <1"
60
60
  },
61
61
  "peerDependenciesMeta": {
@@ -70,8 +70,8 @@
70
70
  "sharp": "^0.35.4"
71
71
  },
72
72
  "scripts": {
73
- "test": "node --test tests/index-modularization.test.js tests/adapter-prepare-call-compat.test.js tests/core.test.js tests/issue-374-delegated-call-config.test.js tests/grounding-coordinate-frame.test.js tests/grounding-coordinate-runtime.test.js tests/capability-advisory.test.js tests/vision-execution-policy.test.js tests/vision-backend-diagnostics.test.js tests/live-model-discovery.test.js tests/live-model-credential-resolution.test.js tests/vision-model-registry.test.js tests/live-model-client-stability.test.js tests/providers-persistence.test.js tests/provider-directory-fallback.test.js tests/model-catalog-policy.test.js tests/settings-ia-client-prelude.test.js tests/settings-ia-fj.test.js tests/settings-ia-acceptance-regression.test.js tests/settings-migration.test.js tests/settings-section-order.test.js tests/attachment-admission-policy.test.js tests/vision-resilience.test.js tests/vision-resilience-lifecycle.test.js tests/vision-breaker-readonly.test.js tests/alpha1-browser-lifecycle-integration.test.js tests/issue-367-remote-session-inject.test.js tests/vision-mode-toggle-injection.test.js tests/client.test.js tests/issue-307-regression.test.js tests/issue-284-vision-mode-toggle.test.js tests/issue-284-vision-mode-hardening.test.js tests/issue-284-attachment-id-hint.test.js tests/issue-284-model-visibility.test.js tests/issue-284-vision-selection-effort.test.js tests/client-presentation-boundary.test.js tests/clipboard-image-paste-compat.test.js tests/remote-settings-bridge.test.js tests/remote-settings-v2-contract.test.js tests/remote-settings-risk-confirmation.test.js tests/local-remote-settings-permission.test.js tests/rc6-real-settings-persistence.test.js tests/artifact-path-security.test.js tests/http-compat.test.js tests/catalog-corrections.test.js tests/update-check.test.js tests/update-check-signal-lifecycle.test.js tests/self-update.test.js tests/doctor.test.js tests/doctor-cli.test.js tests/legacy-session-repair.test.js tests/profile-pnpm-diagnostics.test.js tests/doctor-v2.test.js tests/doctor-cli-v2.test.js tests/doctor-runtime.test.js tests/dsh-host-capabilities.test.js tests/doctor-host-capabilities.test.js tests/session-repair-v2.test.js tests/bundle-defaults.test.js tests/file-logger.test.js tests/replay-delegation.test.js tests/twin-image-capability-fallback.test.js tests/twin-image-custom-wrapper.test.js tests/adversarial-hardening.test.js tests/vision-adversarial-hardening-v2.test.js tests/vision-capability-benchmark-visual-proof.test.js tests/wrapper-directory.test.js tests/logging-ui.test.js tests/manifest-dependencies.test.js tests/structured-bootstrap.test.js tests/structured-bootstrap-gate.test.js tests/vision-model-guidance-contract.test.js tests/local-ollama.test.js tests/ollama-cold-start.test.js tests/ollama-cold-start-loaded.test.js tests/zero-regression-gate.test.js tests/runtime-e2e.test.js tests/local-vision-stabilizer.test.js tests/local-connection-probe.test.js tests/repetition-guard.test.js tests/guide-scroll-gate.test.js tests/settings-scroll-jank-regression.test.js tests/android-attachment-compat.test.js tests/free-cloud-first.test.js tests/rc6-rc7-compat.test.js tests/native-image-coexistence.test.js tests/issue-276-session-image-ownership.test.js tests/issue-276-entry-policy-bridge.test.js tests/issue-276-policy-hardening.test.js tests/issue-289-native-nonintervention.test.js tests/pi-ai-bridge-wire-compat.test.js tests/mixed-router.test.js tests/depth-tier.test.js tests/depth-quota-behavior.test.js tests/structured-flow-hardening.test.js tests/structured-flow-adversarial.test.js tests/vision-budget-activation.test.js tests/runtime-boundary-fixes.test.js tests/security-resource-boundaries.test.js tests/security-property-fuzz.test.js tests/security-adversarial-fuzz-contract.test.js tests/runtime-i18n.test.js tests/tesseract-node24-boot.test.js tests/session-vision-state.test.js tests/session-vision-state-integration.test.js tests/image-resource-governor.test.js tests/resource-retention-lifecycle.test.js tests/pixel-diff-stream.test.js tests/large-image-resource-integration.test.js tests/structured-guard-idempotency.test.js tests/vision-routing-product.test.js tests/vision-runtime-performance.test.js tests/vision-routing-settings-prelude.test.js tests/v2-settings-ia-integration.test.js tests/vision-capability-router.test.js tests/vision-capability-reference.test.js tests/vision-capability-benchmark.test.js tests/vision-capability-grounding-proof-contract.test.js tests/vision-capability-probe.test.js tests/vision-capability-axis-freshness.test.js tests/vision-capability-shadow-health.test.js tests/vision-capability-benchmark-service.test.js tests/vision-capability-preflight-hardening.test.js tests/vision-capability-benchmark-contract.test.js tests/vision-capability-benchmark-client.test.js tests/vision-capability-benchmark-client-productization.test.js tests/vision-capability-adapter-route.test.js tests/vision-background-benchmark.test.js tests/vision-background-lifecycle.test.js tests/vision-background-benchmark-productization.test.js tests/vision-capability-benchmark-budget.test.js tests/vision-image-input-verdict.test.js tests/vision-capability-failure-classification.test.js tests/vision-capability-gate-client.test.js tests/v2-product-review-fixes.test.js tests/qa-top5-reliability.test.js tests/qa-screenshot-runtime.test.js tests/qa-runtime-identity.test.js tests/qa-turn-budget-cancellation.test.js tests/qa-cancellation-publication.test.js tests/qa-reliability-tail.test.js tests/qa-endpoint-route-alias.test.js tests/settings-native-card-semantics.test.js tests/settings-native-card-lazy-data.test.js tests/settings-invalid-staged-dirty.test.js tests/vision-toggle-root-hardening.test.js && node --test tests/vision-backend-runtime-policy.test.js",
74
- "test:core": "node --test tests/core.test.js tests/runtime-e2e.test.js tests/structured-bootstrap.test.js tests/structured-flow-hardening.test.js tests/structured-flow-adversarial.test.js tests/vision-execution-policy.test.js tests/vision-backend-runtime-policy.test.js",
73
+ "test": "node --test tests/index-modularization.test.js tests/adapter-prepare-call-compat.test.js tests/core.test.js tests/issue-374-delegated-call-config.test.js tests/grounding-coordinate-frame.test.js tests/grounding-coordinate-runtime.test.js tests/capability-advisory.test.js tests/vision-execution-policy.test.js tests/vision-backend-diagnostics.test.js tests/live-model-discovery.test.js tests/live-model-credential-resolution.test.js tests/vision-model-registry.test.js tests/live-model-client-stability.test.js tests/providers-persistence.test.js tests/provider-directory-fallback.test.js tests/model-catalog-policy.test.js tests/settings-ia-client-prelude.test.js tests/settings-ia-fj.test.js tests/settings-ia-acceptance-regression.test.js tests/settings-migration.test.js tests/settings-section-order.test.js tests/attachment-admission-policy.test.js tests/vision-resilience.test.js tests/degraded-local-evidence.test.js tests/vision-resilience-lifecycle.test.js tests/vision-breaker-readonly.test.js tests/alpha1-browser-lifecycle-integration.test.js tests/issue-367-remote-session-inject.test.js tests/vision-mode-toggle-injection.test.js tests/client.test.js tests/issue-307-regression.test.js tests/issue-284-vision-mode-toggle.test.js tests/issue-284-vision-mode-hardening.test.js tests/issue-284-attachment-id-hint.test.js tests/issue-284-model-visibility.test.js tests/issue-284-vision-selection-effort.test.js tests/client-presentation-boundary.test.js tests/clipboard-image-paste-compat.test.js tests/remote-settings-bridge.test.js tests/remote-settings-v2-contract.test.js tests/remote-settings-risk-confirmation.test.js tests/local-remote-settings-permission.test.js tests/rc6-real-settings-persistence.test.js tests/artifact-path-security.test.js tests/http-compat.test.js tests/catalog-corrections.test.js tests/update-check.test.js tests/update-check-signal-lifecycle.test.js tests/self-update.test.js tests/doctor.test.js tests/doctor-cli.test.js tests/legacy-session-repair.test.js tests/profile-pnpm-diagnostics.test.js tests/doctor-v2.test.js tests/doctor-cli-v2.test.js tests/doctor-runtime.test.js tests/dsh-host-capabilities.test.js tests/doctor-host-capabilities.test.js tests/session-repair-v2.test.js tests/bundle-defaults.test.js tests/file-logger.test.js tests/official-deepseek-catalog.test.js tests/replay-delegation.test.js tests/twin-image-capability-fallback.test.js tests/twin-image-custom-wrapper.test.js tests/adversarial-hardening.test.js tests/vision-adversarial-hardening-v2.test.js tests/vision-capability-benchmark-visual-proof.test.js tests/wrapper-directory.test.js tests/logging-ui.test.js tests/manifest-dependencies.test.js tests/structured-bootstrap.test.js tests/structured-bootstrap-gate.test.js tests/vision-model-guidance-contract.test.js tests/local-ollama.test.js tests/ollama-cold-start.test.js tests/ollama-cold-start-loaded.test.js tests/zero-regression-gate.test.js tests/runtime-e2e.test.js tests/local-vision-stabilizer.test.js tests/local-connection-probe.test.js tests/repetition-guard.test.js tests/guide-scroll-gate.test.js tests/settings-scroll-jank-regression.test.js tests/android-attachment-compat.test.js tests/free-cloud-first.test.js tests/rc6-rc7-compat.test.js tests/native-image-coexistence.test.js tests/issue-276-session-image-ownership.test.js tests/issue-276-entry-policy-bridge.test.js tests/issue-276-policy-hardening.test.js tests/issue-289-native-nonintervention.test.js tests/issue-512-tool-restriction.test.js tests/pi-ai-bridge-wire-compat.test.js tests/mixed-router.test.js tests/depth-tier.test.js tests/depth-quota-behavior.test.js tests/structured-flow-hardening.test.js tests/structured-flow-adversarial.test.js tests/vision-budget-activation.test.js tests/runtime-boundary-fixes.test.js tests/security-resource-boundaries.test.js tests/security-property-fuzz.test.js tests/security-adversarial-fuzz-contract.test.js tests/runtime-i18n.test.js tests/tesseract-adaptive-ocr.test.js tests/tesseract-node24-boot.test.js tests/session-vision-state.test.js tests/session-vision-state-integration.test.js tests/image-resource-governor.test.js tests/resource-retention-lifecycle.test.js tests/pixel-diff-stream.test.js tests/large-image-resource-integration.test.js tests/structured-guard-idempotency.test.js tests/vision-routing-product.test.js tests/vision-runtime-performance.test.js tests/vision-routing-settings-prelude.test.js tests/v2-settings-ia-integration.test.js tests/vision-capability-router.test.js tests/vision-capability-reference.test.js tests/vision-capability-benchmark.test.js tests/vision-capability-grounding-proof-contract.test.js tests/vision-capability-probe.test.js tests/vision-capability-axis-freshness.test.js tests/vision-capability-shadow-health.test.js tests/vision-capability-benchmark-service.test.js tests/vision-capability-preflight-hardening.test.js tests/vision-capability-benchmark-contract.test.js tests/vision-capability-benchmark-client.test.js tests/vision-capability-benchmark-client-productization.test.js tests/vision-capability-adapter-route.test.js tests/vision-background-benchmark.test.js tests/vision-background-lifecycle.test.js tests/vision-background-benchmark-productization.test.js tests/vision-capability-benchmark-budget.test.js tests/vision-image-input-verdict.test.js tests/vision-capability-failure-classification.test.js tests/vision-capability-gate-client.test.js tests/v2-product-review-fixes.test.js tests/qa-top5-reliability.test.js tests/qa-screenshot-runtime.test.js tests/qa-runtime-identity.test.js tests/qa-turn-budget-cancellation.test.js tests/qa-cancellation-publication.test.js tests/qa-reliability-tail.test.js tests/qa-endpoint-route-alias.test.js tests/settings-native-card-semantics.test.js tests/settings-native-card-lazy-data.test.js tests/settings-invalid-staged-dirty.test.js tests/vision-toggle-root-hardening.test.js && node --test tests/vision-backend-runtime-policy.test.js",
74
+ "test:core": "node --test tests/core.test.js tests/degraded-local-evidence.test.js tests/runtime-e2e.test.js tests/structured-bootstrap.test.js tests/structured-flow-hardening.test.js tests/structured-flow-adversarial.test.js tests/vision-execution-policy.test.js tests/vision-backend-runtime-policy.test.js",
75
75
  "test:routing": "node --test tests/issue-374-delegated-call-config.test.js tests/twin-image-capability-fallback.test.js tests/twin-image-custom-wrapper.test.js tests/vision-routing-product.test.js tests/vision-capability-router.test.js tests/vision-capability-probe.test.js tests/vision-capability-shadow-health.test.js tests/vision-runtime-performance.test.js tests/vision-background-benchmark.test.js tests/vision-image-input-verdict.test.js",
76
76
  "test:session": "node --test tests/session-vision-state.test.js tests/session-vision-state-integration.test.js tests/replay-delegation.test.js tests/legacy-session-repair.test.js tests/session-repair-v2.test.js tests/issue-276-session-image-ownership.test.js",
77
77
  "test:resources": "node --test tests/artifact-path-security.test.js tests/image-resource-governor.test.js tests/resource-retention-lifecycle.test.js tests/pixel-diff-stream.test.js tests/large-image-resource-integration.test.js tests/qa-turn-budget-cancellation.test.js tests/qa-cancellation-publication.test.js",
@@ -81,7 +81,12 @@
81
81
  "test:grounding": "node --test tests/grounding-coordinate-frame.test.js tests/grounding-coordinate-runtime.test.js tests/vision-capability-grounding-proof-contract.test.js",
82
82
  "test:stress": "node scripts/image-resource-stress.mjs && node --test tests/large-image-resource-integration.test.js tests/resource-retention-lifecycle.test.js",
83
83
  "test:adversarial-fuzz": "node scripts/security-adversarial-fuzz.mjs",
84
- "test:property-fuzz": "node --test tests/security-property-fuzz.test.js"
84
+ "test:property-fuzz": "node --test tests/security-property-fuzz.test.js",
85
+ "test:quality": "node --test tests/vision-quality-round2-baseline.test.js",
86
+ "quality:round2:render": "node quality/vision-round2/render-fixtures.mjs",
87
+ "quality:round2:template": "node quality/vision-round2/make-run-template.mjs",
88
+ "quality:round2:run": "node quality/vision-round2/run-host-api.mjs",
89
+ "quality:round2:report": "node quality/vision-round2/report-results.mjs"
85
90
  },
86
91
  "pnpm": {
87
92
  "onlyBuiltDependencies": ["sharp"]