dsh-vision-router 2.1.0 → 2.1.1
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.
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# v2.1.1
|
|
2
|
+
|
|
3
|
+
This is an urgent browser-startup hotfix for DVR 2.1.0.
|
|
4
|
+
|
|
5
|
+
## Fixed
|
|
6
|
+
|
|
7
|
+
- Fixes #367: DSH Web could fail while applying `dsh-vision-router` with `cannot get property "remote.session" without inject`.
|
|
8
|
+
- The 2.1.0 client Host compatibility prelude no longer probes the optional `remote.session` namespace through the traceable `ctx.remote` proxy. It resolves the optional namespace through `ctx.get('remote.session')` first and keeps a guarded fallback for older/non-Cordis harnesses.
|
|
9
|
+
- The new Host model-catalog bridge still uses `remote.session.modelCatalog()` when available, while older supported Hosts keep the original `connection.api.llm.models()` path and exact legacy Remote/Connection surfaces.
|
|
10
|
+
|
|
11
|
+
## Regression coverage
|
|
12
|
+
|
|
13
|
+
- Adds a production-shaped regression where reading `ctx.remote.session` throws the exact #367 Cordis error while `ctx.get('remote.session')` succeeds.
|
|
14
|
+
- Adds the same test to the default Node 22/24 suite and the Web compatibility suite.
|
|
15
|
+
- Keeps a fail-closed legacy-host regression so the hotfix does not turn the optional alpha namespace into a hard browser dependency.
|
|
16
|
+
|
|
17
|
+
## Upgrade
|
|
18
|
+
|
|
19
|
+
Users on 2.1.0 who hit the browser loader failure should upgrade to 2.1.1 and restart the DSH Web/Desktop process. No settings migration is required.
|
|
@@ -20,11 +20,22 @@ export const CLIENT_HOST_COMPAT_PRELUDE = String.raw`(function(){
|
|
|
20
20
|
var LEGACY_CREDENTIAL_EVENT = 'credentials/updated';
|
|
21
21
|
var HOST_CREDENTIAL_EVENT = 'credentials/reference-updated';
|
|
22
22
|
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
function optionalHostSession(ctx, remote) {
|
|
24
|
+
try {
|
|
25
|
+
if (ctx && typeof ctx.get === 'function') {
|
|
26
|
+
var session = ctx.get('remote.session');
|
|
27
|
+
if (session) return session;
|
|
28
|
+
}
|
|
29
|
+
} catch (_) {}
|
|
30
|
+
try {
|
|
31
|
+
return remote && remote.session;
|
|
32
|
+
} catch (_) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function hasHostCatalog(session) {
|
|
38
|
+
return !!(session && typeof session.modelCatalog === 'function');
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
function wrapCatalogResult(value) {
|
|
@@ -37,23 +48,23 @@ export const CLIENT_HOST_COMPAT_PRELUDE = String.raw`(function(){
|
|
|
37
48
|
return value;
|
|
38
49
|
}
|
|
39
50
|
|
|
40
|
-
function catalogModels(
|
|
51
|
+
function catalogModels(session) {
|
|
41
52
|
return function models() {
|
|
42
|
-
return Promise.resolve(
|
|
53
|
+
return Promise.resolve(session.modelCatalog()).then(wrapCatalogResult);
|
|
43
54
|
};
|
|
44
55
|
}
|
|
45
56
|
|
|
46
|
-
function compatibleApi(originalApi,
|
|
57
|
+
function compatibleApi(originalApi, session) {
|
|
47
58
|
var originalLlm = originalApi && originalApi.llm;
|
|
48
59
|
var llm = originalLlm && (typeof originalLlm === 'object' || typeof originalLlm === 'function')
|
|
49
60
|
? new Proxy(originalLlm, {
|
|
50
61
|
get: function(target, property) {
|
|
51
|
-
if (property === 'models') return catalogModels(
|
|
62
|
+
if (property === 'models') return catalogModels(session);
|
|
52
63
|
var value = Reflect.get(target, property, target);
|
|
53
64
|
return typeof value === 'function' ? value.bind(target) : value;
|
|
54
65
|
}
|
|
55
66
|
})
|
|
56
|
-
: { models: catalogModels(
|
|
67
|
+
: { models: catalogModels(session) };
|
|
57
68
|
if (originalApi && (typeof originalApi === 'object' || typeof originalApi === 'function')) {
|
|
58
69
|
return new Proxy(originalApi, {
|
|
59
70
|
get: function(target, property) {
|
|
@@ -66,10 +77,10 @@ export const CLIENT_HOST_COMPAT_PRELUDE = String.raw`(function(){
|
|
|
66
77
|
return { llm: llm };
|
|
67
78
|
}
|
|
68
79
|
|
|
69
|
-
function compatibleConnection(connection,
|
|
70
|
-
if (!hasHostCatalog(
|
|
80
|
+
function compatibleConnection(connection, session) {
|
|
81
|
+
if (!hasHostCatalog(session)) return connection;
|
|
71
82
|
if (!connection || (typeof connection !== 'object' && typeof connection !== 'function')) return connection;
|
|
72
|
-
var api = compatibleApi(connection.api,
|
|
83
|
+
var api = compatibleApi(connection.api, session);
|
|
73
84
|
return new Proxy(connection, {
|
|
74
85
|
get: function(target, property) {
|
|
75
86
|
if (property === 'api') return api;
|
|
@@ -79,8 +90,8 @@ export const CLIENT_HOST_COMPAT_PRELUDE = String.raw`(function(){
|
|
|
79
90
|
});
|
|
80
91
|
}
|
|
81
92
|
|
|
82
|
-
function compatibleRemote(remote) {
|
|
83
|
-
if (!hasHostCatalog(
|
|
93
|
+
function compatibleRemote(remote, session) {
|
|
94
|
+
if (!hasHostCatalog(session)) return remote;
|
|
84
95
|
if (!remote || (typeof remote !== 'object' && typeof remote !== 'function')) return remote;
|
|
85
96
|
return new Proxy(remote, {
|
|
86
97
|
get: function(target, property) {
|
|
@@ -140,7 +151,8 @@ export const CLIENT_HOST_COMPAT_PRELUDE = String.raw`(function(){
|
|
|
140
151
|
|
|
141
152
|
function compatibleContext(ctx) {
|
|
142
153
|
if (!ctx || typeof ctx !== 'object' || typeof Proxy !== 'function') return ctx;
|
|
143
|
-
var
|
|
154
|
+
var session = optionalHostSession(ctx, ctx.remote);
|
|
155
|
+
var remote = compatibleRemote(ctx.remote, session);
|
|
144
156
|
var locale = compatibleLocale(ctx.locale);
|
|
145
157
|
return new Proxy(ctx, {
|
|
146
158
|
get: function(target, property) {
|
|
@@ -151,7 +163,7 @@ export const CLIENT_HOST_COMPAT_PRELUDE = String.raw`(function(){
|
|
|
151
163
|
if (typeof get !== 'function') return get;
|
|
152
164
|
return function(name) {
|
|
153
165
|
var value = get.call(target, name);
|
|
154
|
-
return name === 'connection' ? compatibleConnection(value,
|
|
166
|
+
return name === 'connection' ? compatibleConnection(value, session) : value;
|
|
155
167
|
};
|
|
156
168
|
}
|
|
157
169
|
var value = Reflect.get(target, property, target);
|
package/lib/doctor.js
CHANGED
|
@@ -303,12 +303,53 @@ function stripYamlComment(line) {
|
|
|
303
303
|
return line
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
function yamlIndent(line) {
|
|
307
|
+
return /^(\s*)/.exec(line)?.[1].length ?? 0
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function rowIsInsideInsert(lines, rowIndex, rowIndent) {
|
|
311
|
+
for (let cursor = rowIndex - 1; cursor >= 0; cursor -= 1) {
|
|
312
|
+
const candidate = lines[cursor]
|
|
313
|
+
if (/^\s*$/.test(candidate)) continue
|
|
314
|
+
const indent = yamlIndent(candidate)
|
|
315
|
+
if (indent >= rowIndent) continue
|
|
316
|
+
return /^\s*-?\s*insert\s*:\s*$/.test(candidate)
|
|
317
|
+
}
|
|
318
|
+
return false
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function inspectVisionRouterPatchRows(lines) {
|
|
322
|
+
let mountRows = 0
|
|
323
|
+
let overrideRows = 0
|
|
324
|
+
|
|
325
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
326
|
+
const idMatch = /^(\s*)-\s+id\s*:\s*['"]?([^'"\s]+)['"]?\s*$/.exec(lines[index])
|
|
327
|
+
if (!idMatch) continue
|
|
328
|
+
const rowIndent = idMatch[1].length
|
|
329
|
+
let pluginName
|
|
330
|
+
for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
|
|
331
|
+
const candidate = lines[cursor]
|
|
332
|
+
if (/^\s*$/.test(candidate)) continue
|
|
333
|
+
const indent = yamlIndent(candidate)
|
|
334
|
+
if (indent <= rowIndent) break
|
|
335
|
+
const nameMatch = /^\s*name\s*:\s*['"]?([^'"\s]+)['"]?\s*$/.exec(candidate)
|
|
336
|
+
if (nameMatch) pluginName = nameMatch[1]
|
|
337
|
+
}
|
|
338
|
+
if (idMatch[2] !== 'vision-router' && pluginName !== PLUGIN_NAME) continue
|
|
339
|
+
if (rowIsInsideInsert(lines, index, rowIndent)) mountRows += 1
|
|
340
|
+
else overrideRows += 1
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return { mountRows, overrideRows }
|
|
344
|
+
}
|
|
345
|
+
|
|
306
346
|
export function inspectProfilePatch(patchPath) {
|
|
307
347
|
if (!existsSync(patchPath)) {
|
|
308
348
|
return {
|
|
309
349
|
path: patchPath,
|
|
310
350
|
exists: false,
|
|
311
351
|
visionRouterRows: 0,
|
|
352
|
+
visionRouterOverrideRows: 0,
|
|
312
353
|
manualVisionRouter: false,
|
|
313
354
|
disablesOfficialDeepSeek: false,
|
|
314
355
|
}
|
|
@@ -317,14 +358,13 @@ export function inspectProfilePatch(patchPath) {
|
|
|
317
358
|
try {
|
|
318
359
|
text = readFileBounded(patchPath).toString('utf8')
|
|
319
360
|
} catch (error) {
|
|
320
|
-
return { path: patchPath, exists: true, visionRouterRows: 0, manualVisionRouter: false, disablesOfficialDeepSeek: false, error: error instanceof Error ? error.message : String(error), oversized: error?.code === 'DOCTOR_INPUT_TOO_LARGE' }
|
|
361
|
+
return { path: patchPath, exists: true, visionRouterRows: 0, visionRouterOverrideRows: 0, manualVisionRouter: false, disablesOfficialDeepSeek: false, error: error instanceof Error ? error.message : String(error), oversized: error?.code === 'DOCTOR_INPUT_TOO_LARGE' }
|
|
321
362
|
}
|
|
322
363
|
const lines = text.split(/\r?\n/).map(stripYamlComment)
|
|
323
|
-
|
|
364
|
+
const { mountRows: visionRouterRows, overrideRows: visionRouterOverrideRows } = inspectVisionRouterPatchRows(lines)
|
|
324
365
|
let disablesOfficialDeepSeek = false
|
|
325
366
|
for (let index = 0; index < lines.length; index += 1) {
|
|
326
367
|
const line = lines[index]
|
|
327
|
-
if (/^\s*name\s*:\s*['"]?dsh-vision-router['"]?\s*$/.test(line)) visionRouterRows += 1
|
|
328
368
|
if (!/^\s*-?\s*id\s*:\s*['"]?llm-deepseek['"]?\s*$/.test(line)) continue
|
|
329
369
|
for (let cursor = index + 1; cursor < Math.min(lines.length, index + 12); cursor += 1) {
|
|
330
370
|
if (/^\s*-\s+id\s*:/.test(lines[cursor])) break
|
|
@@ -338,6 +378,7 @@ export function inspectProfilePatch(patchPath) {
|
|
|
338
378
|
path: patchPath,
|
|
339
379
|
exists: true,
|
|
340
380
|
visionRouterRows,
|
|
381
|
+
visionRouterOverrideRows,
|
|
341
382
|
manualVisionRouter: visionRouterRows > 0,
|
|
342
383
|
disablesOfficialDeepSeek,
|
|
343
384
|
}
|
|
@@ -581,4 +622,4 @@ export function doctorProfiles({ dshHome = resolveDshHome(), profile, fix = fals
|
|
|
581
622
|
&& applicable.every(profileHealthy)
|
|
582
623
|
&& (log.settingsSaveFailures?.length ?? 0) === 0,
|
|
583
624
|
}
|
|
584
|
-
}
|
|
625
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export const GROUNDING_FRAME_SIZE = 1000
|
|
2
|
+
|
|
3
|
+
function finitePositiveInteger(value, name) {
|
|
4
|
+
const number = Number(value)
|
|
5
|
+
if (!Number.isFinite(number) || number <= 0) {
|
|
6
|
+
throw new TypeError(`${name} must be a positive finite number`)
|
|
7
|
+
}
|
|
8
|
+
return Math.max(1, Math.round(number))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build the exact geometry used to letterbox a source raster into the
|
|
13
|
+
* grounding protocol's square frame. The rendered dimensions are integers so
|
|
14
|
+
* callers can resize to these exact values before extending the canvas; the
|
|
15
|
+
* inverse therefore never depends on a backend's resize policy.
|
|
16
|
+
*/
|
|
17
|
+
export function createGroundingFrame(width, height, frameSize = GROUNDING_FRAME_SIZE) {
|
|
18
|
+
const sourceWidth = finitePositiveInteger(width, 'width')
|
|
19
|
+
const sourceHeight = finitePositiveInteger(height, 'height')
|
|
20
|
+
const size = finitePositiveInteger(frameSize, 'frameSize')
|
|
21
|
+
const scale = Math.min(size / sourceWidth, size / sourceHeight)
|
|
22
|
+
const renderedWidth = Math.max(1, Math.min(size, Math.round(sourceWidth * scale)))
|
|
23
|
+
const renderedHeight = Math.max(1, Math.min(size, Math.round(sourceHeight * scale)))
|
|
24
|
+
const left = Math.floor((size - renderedWidth) / 2)
|
|
25
|
+
const top = Math.floor((size - renderedHeight) / 2)
|
|
26
|
+
const right = size - renderedWidth - left
|
|
27
|
+
const bottom = size - renderedHeight - top
|
|
28
|
+
|
|
29
|
+
return Object.freeze({
|
|
30
|
+
frameWidth: size,
|
|
31
|
+
frameHeight: size,
|
|
32
|
+
sourceWidth,
|
|
33
|
+
sourceHeight,
|
|
34
|
+
renderedWidth,
|
|
35
|
+
renderedHeight,
|
|
36
|
+
left,
|
|
37
|
+
top,
|
|
38
|
+
right,
|
|
39
|
+
bottom,
|
|
40
|
+
scaleX: renderedWidth / sourceWidth,
|
|
41
|
+
scaleY: renderedHeight / sourceHeight,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function finiteCoordinate(value) {
|
|
46
|
+
const number = Number(value)
|
|
47
|
+
return Number.isFinite(number) ? number : undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function clamp(value, min, max) {
|
|
51
|
+
return Math.max(min, Math.min(value, max))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Map a box reported in the explicit square grounding frame back to source pixels. */
|
|
55
|
+
export function groundingFrameBoxToSource(box, frame) {
|
|
56
|
+
if (!box || !frame) return undefined
|
|
57
|
+
const x1 = finiteCoordinate(box.x1)
|
|
58
|
+
const y1 = finiteCoordinate(box.y1)
|
|
59
|
+
const x2 = finiteCoordinate(box.x2)
|
|
60
|
+
const y2 = finiteCoordinate(box.y2)
|
|
61
|
+
if ([x1, y1, x2, y2].some((value) => value === undefined)) return undefined
|
|
62
|
+
|
|
63
|
+
const raw = {
|
|
64
|
+
x1: (x1 - frame.left) / frame.scaleX,
|
|
65
|
+
y1: (y1 - frame.top) / frame.scaleY,
|
|
66
|
+
x2: (x2 - frame.left) / frame.scaleX,
|
|
67
|
+
y2: (y2 - frame.top) / frame.scaleY,
|
|
68
|
+
}
|
|
69
|
+
const mapped = {
|
|
70
|
+
x1: Math.floor(clamp(raw.x1, 0, frame.sourceWidth)),
|
|
71
|
+
y1: Math.floor(clamp(raw.y1, 0, frame.sourceHeight)),
|
|
72
|
+
x2: Math.ceil(clamp(raw.x2, 0, frame.sourceWidth)),
|
|
73
|
+
y2: Math.ceil(clamp(raw.y2, 0, frame.sourceHeight)),
|
|
74
|
+
}
|
|
75
|
+
if (mapped.x2 <= mapped.x1 || mapped.y2 <= mapped.y1) return undefined
|
|
76
|
+
return mapped
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Map source-pixel geometry into the square frame, useful for protocol tests. */
|
|
80
|
+
export function sourceBoxToGroundingFrame(box, frame) {
|
|
81
|
+
if (!box || !frame) return undefined
|
|
82
|
+
const x1 = finiteCoordinate(box.x1)
|
|
83
|
+
const y1 = finiteCoordinate(box.y1)
|
|
84
|
+
const x2 = finiteCoordinate(box.x2)
|
|
85
|
+
const y2 = finiteCoordinate(box.y2)
|
|
86
|
+
if ([x1, y1, x2, y2].some((value) => value === undefined)) return undefined
|
|
87
|
+
return {
|
|
88
|
+
x1: frame.left + x1 * frame.scaleX,
|
|
89
|
+
y1: frame.top + y1 * frame.scaleY,
|
|
90
|
+
x2: frame.left + x2 * frame.scaleX,
|
|
91
|
+
y2: frame.top + y2 * frame.scaleY,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { unlink } from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { writeArtifactFile } from './artifact-boundary.js'
|
|
5
|
+
import {
|
|
6
|
+
createGroundingFrame,
|
|
7
|
+
groundingFrameBoxToSource,
|
|
8
|
+
} from './grounding-coordinate-frame.js'
|
|
9
|
+
|
|
10
|
+
const GROUNDING_TOOL_NAMES = new Set(['vision_ground', 'vision_detect'])
|
|
11
|
+
let sharpPromise
|
|
12
|
+
|
|
13
|
+
function loadSharp() {
|
|
14
|
+
if (!sharpPromise) {
|
|
15
|
+
sharpPromise = import('sharp').then((mod) => mod.default ?? mod).catch((error) => {
|
|
16
|
+
sharpPromise = undefined
|
|
17
|
+
throw error
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
return sharpPromise
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function workspaceOf(exec) {
|
|
24
|
+
const cwd = exec?.agent?.session?.header?.cwd
|
|
25
|
+
return typeof cwd === 'string' && cwd !== '' ? cwd : process.cwd()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function artifactsDirOf(config) {
|
|
29
|
+
return typeof config?.artifactsDir === 'string' && config.artifactsDir !== ''
|
|
30
|
+
? config.artifactsDir
|
|
31
|
+
: '.dsh-vision-router/artifacts'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function contextService(ctx, name) {
|
|
35
|
+
try {
|
|
36
|
+
if (typeof ctx?.get === 'function') return ctx.get(name)
|
|
37
|
+
} catch {
|
|
38
|
+
return undefined
|
|
39
|
+
}
|
|
40
|
+
return ctx?.[name]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function readSourceBytes(ctx, core, sessionVisionIndex, exec, image) {
|
|
44
|
+
const source = String(image ?? '')
|
|
45
|
+
if (core?.isAttachmentIdInput?.(source)) {
|
|
46
|
+
const session = exec?.agent?.session
|
|
47
|
+
const ref = sessionVisionIndex?.lookupAttachment?.(session, source.trim())
|
|
48
|
+
if (ref === undefined) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`vision-router: unknown attachment id "${source}" (it must come from an image uploaded in this conversation)`,
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
const attachments = contextService(ctx, 'attachments')
|
|
54
|
+
if (!attachments || typeof attachments.readImage !== 'function') {
|
|
55
|
+
throw new Error('vision-router: the attachment service is not available in this deployment')
|
|
56
|
+
}
|
|
57
|
+
const stored = await attachments.readImage(ref, exec?.signal)
|
|
58
|
+
if (!stored?.data) throw new Error(`vision-router: failed to read attachment ${source}`)
|
|
59
|
+
return Buffer.from(stored.data)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const fs = contextService(ctx, 'fs')
|
|
63
|
+
if (!fs || typeof fs.resolve !== 'function' || typeof fs.readBytes !== 'function') {
|
|
64
|
+
throw new Error('vision-router: the fs service is not available')
|
|
65
|
+
}
|
|
66
|
+
const target = await fs.resolve(source)
|
|
67
|
+
return Buffer.from(await fs.readBytes(target, undefined, 20 * 1024 * 1024))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function buildGroundingFrame(bytes) {
|
|
71
|
+
const sharp = await loadSharp()
|
|
72
|
+
const meta = await sharp(bytes, { failOn: 'none' }).metadata()
|
|
73
|
+
const width = Number(meta.width ?? 0)
|
|
74
|
+
const height = Number(meta.height ?? 0)
|
|
75
|
+
if (width <= 0 || height <= 0) throw new Error('could not read image dimensions')
|
|
76
|
+
const frame = createGroundingFrame(width, height)
|
|
77
|
+
const framed = await sharp(bytes, { failOn: 'none' })
|
|
78
|
+
.resize(frame.renderedWidth, frame.renderedHeight, { fit: 'fill' })
|
|
79
|
+
.extend({
|
|
80
|
+
top: frame.top,
|
|
81
|
+
bottom: frame.bottom,
|
|
82
|
+
left: frame.left,
|
|
83
|
+
right: frame.right,
|
|
84
|
+
background: { r: 0, g: 0, b: 0, alpha: 1 },
|
|
85
|
+
})
|
|
86
|
+
.png()
|
|
87
|
+
.toBuffer()
|
|
88
|
+
return { frame, framed, width, height }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseToolResult(raw) {
|
|
92
|
+
if (typeof raw !== 'string' || raw.trim() === '') return undefined
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(raw)
|
|
95
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : undefined
|
|
96
|
+
} catch {
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function removeInternalFrame(workspace, publishedPath) {
|
|
102
|
+
if (typeof publishedPath !== 'string' || publishedPath === '') return
|
|
103
|
+
const target = path.isAbsolute(publishedPath) ? publishedPath : path.resolve(workspace, publishedPath)
|
|
104
|
+
try {
|
|
105
|
+
await unlink(target)
|
|
106
|
+
} catch {
|
|
107
|
+
// The internal frame is best-effort cleanup. Artifact retention may already
|
|
108
|
+
// have removed it after the delegated tool completed.
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function executeInGroundingFrame({
|
|
113
|
+
ctx,
|
|
114
|
+
core,
|
|
115
|
+
config,
|
|
116
|
+
sessionVisionIndex,
|
|
117
|
+
def,
|
|
118
|
+
execute,
|
|
119
|
+
args,
|
|
120
|
+
exec,
|
|
121
|
+
}) {
|
|
122
|
+
const source = String(args?.image ?? '')
|
|
123
|
+
const bytes = await readSourceBytes(ctx, core, sessionVisionIndex, exec, source)
|
|
124
|
+
const { frame, framed, width, height } = await buildGroundingFrame(bytes)
|
|
125
|
+
const workspace = workspaceOf(exec)
|
|
126
|
+
const artifactsDir = artifactsDirOf(config)
|
|
127
|
+
const internalName = `.grounding-frame-${randomUUID()}.png`
|
|
128
|
+
const framePath = await writeArtifactFile(workspace, artifactsDir, internalName, framed)
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const raw = await execute(
|
|
132
|
+
{
|
|
133
|
+
...(args ?? {}),
|
|
134
|
+
image: framePath,
|
|
135
|
+
// Core's annotation would be in the protocol frame. Re-publish only an
|
|
136
|
+
// original-raster annotation after the deterministic inverse transform.
|
|
137
|
+
annotate: false,
|
|
138
|
+
},
|
|
139
|
+
exec,
|
|
140
|
+
)
|
|
141
|
+
const delegated = parseToolResult(raw)
|
|
142
|
+
if (!delegated || delegated.ok === false) return raw
|
|
143
|
+
|
|
144
|
+
if (def.name === 'vision_ground') {
|
|
145
|
+
const mapped = groundingFrameBoxToSource(delegated, frame)
|
|
146
|
+
if (!mapped) {
|
|
147
|
+
throw new Error('vision_ground: the model box falls entirely outside the letterboxed source raster')
|
|
148
|
+
}
|
|
149
|
+
const result = { ...mapped, width, height }
|
|
150
|
+
if (args?.annotate !== false) {
|
|
151
|
+
if (typeof core?.annotateBoxBuffer !== 'function') {
|
|
152
|
+
throw new Error('vision_ground: core annotation helper is unavailable')
|
|
153
|
+
}
|
|
154
|
+
const annotated = await core.annotateBoxBuffer(bytes, mapped)
|
|
155
|
+
const stem = core?.artifactStemOf?.(source, 'ground') ?? `image-ground-${Date.now()}`
|
|
156
|
+
result.annotatedPath = await writeArtifactFile(workspace, artifactsDir, `${stem}.png`, annotated)
|
|
157
|
+
}
|
|
158
|
+
return JSON.stringify(result)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const elements = []
|
|
162
|
+
for (const item of Array.isArray(delegated.elements) ? delegated.elements : []) {
|
|
163
|
+
const mapped = groundingFrameBoxToSource(item?.box, frame)
|
|
164
|
+
if (!mapped) continue
|
|
165
|
+
elements.push({
|
|
166
|
+
...item,
|
|
167
|
+
number: elements.length + 1,
|
|
168
|
+
box: mapped,
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
const result = { width, height, elements }
|
|
172
|
+
if (args?.annotate !== false && elements.length > 0) {
|
|
173
|
+
if (typeof core?.annotateBoxesBuffer !== 'function') {
|
|
174
|
+
throw new Error('vision_detect: core annotation helper is unavailable')
|
|
175
|
+
}
|
|
176
|
+
const annotated = await core.annotateBoxesBuffer(bytes, elements.map((item) => item.box))
|
|
177
|
+
const stem = core?.artifactStemOf?.(source, 'detect') ?? `image-detect-${Date.now()}`
|
|
178
|
+
result.annotatedPath = await writeArtifactFile(workspace, artifactsDir, `${stem}.png`, annotated)
|
|
179
|
+
}
|
|
180
|
+
return JSON.stringify(result)
|
|
181
|
+
} finally {
|
|
182
|
+
await removeInternalFrame(workspace, framePath)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function wrapGroundingDefinition(ctx, options, def) {
|
|
187
|
+
if (!def || !GROUNDING_TOOL_NAMES.has(def.name) || typeof def.execute !== 'function') return def
|
|
188
|
+
const execute = def.execute
|
|
189
|
+
return {
|
|
190
|
+
...def,
|
|
191
|
+
execute(args, exec) {
|
|
192
|
+
return executeInGroundingFrame({
|
|
193
|
+
ctx,
|
|
194
|
+
...options,
|
|
195
|
+
def,
|
|
196
|
+
execute,
|
|
197
|
+
args,
|
|
198
|
+
exec,
|
|
199
|
+
})
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Give vision_ground / vision_detect one explicit provider-independent
|
|
206
|
+
* coordinate protocol: the model always receives an exact 1000x1000
|
|
207
|
+
* letterboxed raster, while callers always receive Host-canonical source
|
|
208
|
+
* pixels. Core still owns provider selection, fallback, retry and failure
|
|
209
|
+
* semantics; this boundary owns only raster geometry and original-image
|
|
210
|
+
* annotation.
|
|
211
|
+
*/
|
|
212
|
+
export function contextWithGroundingCoordinateFrame(ctx, options = {}) {
|
|
213
|
+
if (!ctx || (typeof ctx !== 'object' && typeof ctx !== 'function')) return ctx
|
|
214
|
+
const sourceTools = ctx.tools ?? contextService(ctx, 'tools')
|
|
215
|
+
if (!sourceTools || typeof sourceTools.register !== 'function') return ctx
|
|
216
|
+
|
|
217
|
+
const tools = new Proxy(sourceTools, {
|
|
218
|
+
get(target, property) {
|
|
219
|
+
if (property !== 'register') {
|
|
220
|
+
const value = Reflect.get(target, property, target)
|
|
221
|
+
return typeof value === 'function' ? value.bind(target) : value
|
|
222
|
+
}
|
|
223
|
+
const register = Reflect.get(target, property, target)
|
|
224
|
+
return (def, ...rest) => register.call(
|
|
225
|
+
target,
|
|
226
|
+
wrapGroundingDefinition(ctx, options, def),
|
|
227
|
+
...rest,
|
|
228
|
+
)
|
|
229
|
+
},
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
return new Proxy(ctx, {
|
|
233
|
+
get(target, property) {
|
|
234
|
+
if (property === 'tools') return tools
|
|
235
|
+
if (property === 'get') {
|
|
236
|
+
const get = Reflect.get(target, property, target)
|
|
237
|
+
if (typeof get !== 'function') return get
|
|
238
|
+
return (name, ...rest) => name === 'tools' ? tools : get.call(target, name, ...rest)
|
|
239
|
+
}
|
|
240
|
+
const value = Reflect.get(target, property, target)
|
|
241
|
+
return typeof value === 'function' ? value.bind(target) : value
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
}
|
|
@@ -28,6 +28,7 @@ import { installTesseractExecFileCompat } from './tesseract-exec-compat.js'
|
|
|
28
28
|
import { installLocalMutationRouteBoundary } from './web-capability-boundary.js'
|
|
29
29
|
import { installScreenshotSourceBoundary } from './screenshot-source-boundary.js'
|
|
30
30
|
import { installVisionToolRuntimeBoundary } from './vision-tool-runtime-boundary.js'
|
|
31
|
+
import { contextWithGroundingCoordinateFrame } from './grounding-coordinate-runtime.js'
|
|
31
32
|
import { installVisionRoutingRuntime } from './vision-routing-runtime.js'
|
|
32
33
|
import { createCapabilityProfileStore } from './vision-capability-probe.js'
|
|
33
34
|
import { installCapabilityBenchmarkService } from './vision-capability-benchmark-service.js'
|
|
@@ -249,11 +250,21 @@ export function applyVisionRuntimeComposition(ctx, config = {}, core) {
|
|
|
249
250
|
logger: logging.logger,
|
|
250
251
|
})
|
|
251
252
|
|
|
253
|
+
// Ground/detect own one explicit raster-coordinate boundary. It wraps only
|
|
254
|
+
// the tool-registration view, before runtime-performance observation. That
|
|
255
|
+
// leaves adapter sampling on the same execution seam while backend preflight
|
|
256
|
+
// remains the final outer Core-visible policy boundary.
|
|
257
|
+
const groundingCoordinateCtx = contextWithGroundingCoordinateFrame(executionCtx, {
|
|
258
|
+
core,
|
|
259
|
+
config: runtimeConfig,
|
|
260
|
+
sessionVisionIndex: sessionVisionRuntime.index,
|
|
261
|
+
})
|
|
262
|
+
|
|
252
263
|
// Only real visual-tool adapter streams are timed, and only while live Auto
|
|
253
264
|
// authority permits future-routing observation. Benchmark/background calls
|
|
254
265
|
// have no visual-tool scope and cannot contaminate this store.
|
|
255
266
|
const performanceCtx = contextWithVisionRuntimePerformance(
|
|
256
|
-
|
|
267
|
+
groundingCoordinateCtx,
|
|
257
268
|
runtimePerformanceStore,
|
|
258
269
|
{
|
|
259
270
|
logger: logging.logger,
|
|
@@ -345,4 +356,4 @@ export function applyVisionRuntimeComposition(ctx, config = {}, core) {
|
|
|
345
356
|
)
|
|
346
357
|
throw error
|
|
347
358
|
}
|
|
348
|
-
}
|
|
359
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vision-router",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
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": {
|
|
@@ -69,14 +69,15 @@
|
|
|
69
69
|
"sharp": "^0.35.3"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
|
-
"test": "node --test tests/adapter-prepare-call-compat.test.js tests/core.test.js tests/capability-advisory.test.js tests/vision-execution-policy.test.js tests/vision-backend-diagnostics.test.js tests/live-model-discovery.test.js tests/live-model-credential-resolution.test.js tests/vision-model-registry.test.js tests/live-model-client-stability.test.js tests/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/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/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/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/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/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-shadow.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",
|
|
72
|
+
"test": "node --test tests/adapter-prepare-call-compat.test.js tests/core.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/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/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/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/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/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-shadow.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",
|
|
73
73
|
"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",
|
|
74
74
|
"test:routing": "node --test tests/vision-routing-product.test.js tests/vision-capability-router.test.js tests/vision-capability-probe.test.js tests/vision-capability-shadow.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",
|
|
75
75
|
"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",
|
|
76
76
|
"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",
|
|
77
77
|
"test:compat": "node --test tests/adapter-prepare-call-compat.test.js tests/attachment-admission-policy.test.js tests/rc6-rc7-compat.test.js tests/rc6-real-settings-persistence.test.js tests/android-attachment-compat.test.js tests/http-compat.test.js tests/pi-ai-bridge-wire-compat.test.js tests/native-image-coexistence.test.js tests/tesseract-node24-boot.test.js tests/dsh-host-capabilities.test.js",
|
|
78
|
-
"test:web": "node --test tests/alpha1-browser-lifecycle-integration.test.js tests/client.test.js tests/client-presentation-boundary.test.js tests/settings-ia-client-prelude.test.js tests/settings-ia-acceptance-regression.test.js tests/remote-settings-bridge.test.js tests/remote-settings-v2-contract.test.js tests/local-remote-settings-permission.test.js tests/v2-settings-ia-integration.test.js tests/logging-ui.test.js",
|
|
78
|
+
"test:web": "node --test tests/alpha1-browser-lifecycle-integration.test.js tests/issue-367-remote-session-inject.test.js tests/client.test.js tests/client-presentation-boundary.test.js tests/settings-ia-client-prelude.test.js tests/settings-ia-acceptance-regression.test.js tests/remote-settings-bridge.test.js tests/remote-settings-v2-contract.test.js tests/local-remote-settings-permission.test.js tests/v2-settings-ia-integration.test.js tests/logging-ui.test.js",
|
|
79
79
|
"test:contract": "node --test tests/manifest-dependencies.test.js tests/bundle-defaults.test.js tests/zero-regression-gate.test.js tests/issue-289-native-nonintervention.test.js tests/runtime-boundary-fixes.test.js tests/runtime-i18n.test.js tests/adapter-prepare-call-compat.test.js tests/rc6-rc7-compat.test.js tests/attachment-admission-policy.test.js tests/dsh-host-capabilities.test.js tests/doctor-host-capabilities.test.js tests/structured-bootstrap.test.js tests/structured-bootstrap-gate.test.js",
|
|
80
|
+
"test:grounding": "node --test tests/grounding-coordinate-frame.test.js tests/grounding-coordinate-runtime.test.js tests/vision-capability-grounding-proof-contract.test.js",
|
|
80
81
|
"test:stress": "node scripts/image-resource-stress.mjs && node --test tests/large-image-resource-integration.test.js tests/resource-retention-lifecycle.test.js"
|
|
81
82
|
},
|
|
82
83
|
"pnpm": {
|
|
@@ -95,4 +96,4 @@
|
|
|
95
96
|
},
|
|
96
97
|
"bundle": { "patch": "./cordis.patch.yml" }
|
|
97
98
|
}
|
|
98
|
-
}
|
|
99
|
+
}
|