@anionex/dsh-vision-toolkit 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +3 -3
- package/README.md +207 -346
- package/README.zh.md +212 -360
- package/docs/requirements-traceability/README.i18n.yaml +2 -2
- package/docs/requirements-traceability/README.md +1 -1
- package/docs/requirements-traceability/README.zh.md +1 -1
- package/lib/client.js +173 -7
- package/lib/client.js.map +1 -1
- package/lib/config.js +4 -0
- package/lib/config.js.map +1 -1
- package/lib/defaults.js +1 -1
- package/lib/defaults.js.map +1 -1
- package/lib/plugin-update.js +1011 -0
- package/lib/plugin-update.js.map +1 -0
- package/lib/runtime-install.js +61 -1
- package/lib/runtime-install.js.map +1 -1
- package/lib/runtime.js +20 -3
- package/lib/runtime.js.map +1 -1
- package/lib/skill.js +7 -3
- package/lib/skill.js.map +1 -1
- package/lib/tools.js +3 -2
- package/lib/tools.js.map +1 -1
- package/lib/types/client/index.d.ts +55 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/defaults.d.ts +1 -1
- package/lib/types/defaults.d.ts.map +1 -1
- package/lib/types/plugin-update.d.ts +111 -0
- package/lib/types/plugin-update.d.ts.map +1 -0
- package/lib/types/runtime-install.d.ts +11 -0
- package/lib/types/runtime-install.d.ts.map +1 -1
- package/lib/types/runtime.d.ts +3 -0
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/skill.d.ts +1 -1
- package/lib/types/skill.d.ts.map +1 -1
- package/lib/types/tools.d.ts.map +1 -1
- package/lib/types/upstream.d.ts +1 -0
- package/lib/types/upstream.d.ts.map +1 -1
- package/lib/types/web.d.ts +13 -1
- package/lib/types/web.d.ts.map +1 -1
- package/lib/upstream.js +20 -8
- package/lib/upstream.js.map +1 -1
- package/lib/web.js +46 -7
- package/lib/web.js.map +1 -1
- package/package.json +12 -14
- package/src/client/index.tsx +229 -6
- package/src/config.ts +4 -0
- package/src/defaults.ts +1 -1
- package/src/plugin-update.ts +1142 -0
- package/src/runtime-install.ts +77 -1
- package/src/runtime.ts +23 -3
- package/src/skill.ts +7 -3
- package/src/tools.ts +4 -2
- package/src/upstream.ts +21 -8
- package/src/web.ts +69 -3
- package/vendor/agent-vision-toolkit/UPSTREAM_MANIFEST.json +3 -3
- package/vendor/agent-vision-toolkit/bin/crop +0 -0
- package/vendor/agent-vision-toolkit/bin/detect +0 -0
- package/vendor/agent-vision-toolkit/bin/glance +0 -0
- package/vendor/agent-vision-toolkit/bin/ground +0 -0
- package/vendor/agent-vision-toolkit/bin/trace +0 -0
- package/vendor/agent-vision-toolkit/skills/vision-tools/scripts/html_shot.py +323 -11
- package/vendor/agent-vision-toolkit/skills/vision-tools/scripts/long_screenshot_ocr.py +0 -0
- /package/assets/{hero.png → hero-v2.png} +0 -0
package/src/runtime-install.ts
CHANGED
|
@@ -300,6 +300,69 @@ function venvPython(root: string): string {
|
|
|
300
300
|
return process.platform === 'win32' ? join(root, 'Scripts', 'python.exe') : join(root, 'bin', 'python')
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Rewrite a staged venv's `pyvenv.cfg` `home`/`executable` to point at a given
|
|
305
|
+
* base directory (the app execution alias directory for the Microsoft Store
|
|
306
|
+
* Python). Pure helper so the transformation is testable cross-platform.
|
|
307
|
+
*/
|
|
308
|
+
export function rewriteVenvConfig(cfg: string, homeDir: string): string {
|
|
309
|
+
return cfg
|
|
310
|
+
.replace(/^home = .*$/m, `home = ${homeDir}`)
|
|
311
|
+
.replace(/^executable = .*$/m, `executable = ${homeDir}\\python.exe`)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Build the Microsoft Store probe environment while preserving Python-variable
|
|
316
|
+
* tombstones; only the user-directory variables must fall back to the host.
|
|
317
|
+
*/
|
|
318
|
+
export function storePythonProbeEnvironment(installEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
319
|
+
return Object.fromEntries(
|
|
320
|
+
Object.entries(installEnv).filter(([key]) => key !== 'HOME' && key !== 'USERPROFILE' && key !== 'LOCALAPPDATA'),
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Windows-only workaround for the Microsoft Store Python. `python -m venv`
|
|
326
|
+
* records `home = C:\Program Files\WindowsApps\...` in the new venv, but the
|
|
327
|
+
* venv launcher (venvlauncher.exe) cannot CreateProcess that `python.exe`
|
|
328
|
+
* directly — the AppModel package execution restriction denies it (error 5) —
|
|
329
|
+
* so the venv's pip bootstrap exits 101. Rewrite the staged venv's pyvenv.cfg
|
|
330
|
+
* to the app execution alias directory, whose `python.exe` is launchable
|
|
331
|
+
* through Store activation.
|
|
332
|
+
*/
|
|
333
|
+
async function rewriteStorePythonVenvHome(
|
|
334
|
+
ctx: Context,
|
|
335
|
+
bootstrap: { command: RuntimeCommand },
|
|
336
|
+
staging: string,
|
|
337
|
+
installEnv: NodeJS.ProcessEnv,
|
|
338
|
+
cwd: string,
|
|
339
|
+
): Promise<void> {
|
|
340
|
+
if (process.platform !== 'win32') return
|
|
341
|
+
// The HOME/USERPROFILE/LOCALAPPDATA overrides make the Store alias resolve to
|
|
342
|
+
// the real Program Files\WindowsApps path; without them sys.executable points
|
|
343
|
+
// back at the alias directory that venvlauncher can launch.
|
|
344
|
+
const probeEnv = storePythonProbeEnvironment(installEnv)
|
|
345
|
+
const probe = await runCollected(
|
|
346
|
+
ctx,
|
|
347
|
+
[bootstrap.command.program, ...bootstrap.command.prefix, '-c', 'import os,sys; print(os.path.dirname(sys.executable))'],
|
|
348
|
+
cwd,
|
|
349
|
+
{ env: probeEnv },
|
|
350
|
+
)
|
|
351
|
+
if (probe.exitCode !== 0 || probe.timedOut) return
|
|
352
|
+
const aliasDir = probe.stdout.trim().split(/\r?\n/)[0]
|
|
353
|
+
if (aliasDir === undefined || aliasDir.length === 0) return
|
|
354
|
+
const cfgPath = join(staging, 'pyvenv.cfg')
|
|
355
|
+
let cfg: string
|
|
356
|
+
try {
|
|
357
|
+
cfg = await readFile(cfgPath, 'utf8')
|
|
358
|
+
} catch {
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
// Only touch the known-broken layout; other interpreters are left untouched.
|
|
362
|
+
if (!/^home = .*Program Files\\WindowsApps.*$/m.test(cfg)) return
|
|
363
|
+
await writeFile(cfgPath, rewriteVenvConfig(cfg, aliasDir))
|
|
364
|
+
}
|
|
365
|
+
|
|
303
366
|
async function dependencyVersions(
|
|
304
367
|
ctx: Context,
|
|
305
368
|
python: RuntimeCommand,
|
|
@@ -543,13 +606,26 @@ async function prepareManaged(
|
|
|
543
606
|
if (!created) {
|
|
544
607
|
const create = await runCollected(
|
|
545
608
|
ctx,
|
|
546
|
-
[bootstrap.command.program, ...bootstrap.command.prefix, '-m', 'venv', staging],
|
|
609
|
+
[bootstrap.command.program, ...bootstrap.command.prefix, '-m', 'venv', '--without-pip', staging],
|
|
547
610
|
stateRoot,
|
|
548
611
|
{ timeoutMs: PREPARE_TIMEOUT_MS, env: installEnv },
|
|
549
612
|
)
|
|
550
613
|
if (create.exitCode !== 0 || create.timedOut) {
|
|
551
614
|
throw new VisionToolkitError('runtime', `Python failed to create the managed runtime: ${create.stderr.trim()}`)
|
|
552
615
|
}
|
|
616
|
+
// `python -m venv` normally bootstraps pip internally; with the Microsoft
|
|
617
|
+
// Store Python that step exits 101 (see rewriteStorePythonVenvHome), so pip
|
|
618
|
+
// is installed explicitly after the venv configuration is repaired.
|
|
619
|
+
await rewriteStorePythonVenvHome(ctx, bootstrap, staging, installEnv, stateRoot)
|
|
620
|
+
const pipBootstrap = await runCollected(
|
|
621
|
+
ctx,
|
|
622
|
+
[venvPython(staging), '-m', 'ensurepip', '--upgrade', '--default-pip'],
|
|
623
|
+
stateRoot,
|
|
624
|
+
{ timeoutMs: PREPARE_TIMEOUT_MS, env: installEnv },
|
|
625
|
+
)
|
|
626
|
+
if (pipBootstrap.exitCode !== 0 || pipBootstrap.timedOut) {
|
|
627
|
+
throw new VisionToolkitError('runtime', `Python failed to bootstrap pip in the managed runtime: ${pipBootstrap.stderr.trim()}`)
|
|
628
|
+
}
|
|
553
629
|
const install = await runCollected(
|
|
554
630
|
ctx,
|
|
555
631
|
[venvPython(staging), '-m', 'pip', 'install', '--disable-pip-version-check', '--no-input', '-r', REQUIREMENTS_PATH],
|
package/src/runtime.ts
CHANGED
|
@@ -411,6 +411,7 @@ export interface HtmlScreenshotRequest {
|
|
|
411
411
|
height?: number
|
|
412
412
|
scale?: number
|
|
413
413
|
waitMs?: number
|
|
414
|
+
fullPage?: boolean
|
|
414
415
|
output?: string
|
|
415
416
|
}
|
|
416
417
|
|
|
@@ -421,6 +422,8 @@ export interface HtmlScreenshotResult {
|
|
|
421
422
|
viewport: { width: number; height: number; scale: number }
|
|
422
423
|
width: number
|
|
423
424
|
height: number
|
|
425
|
+
/** Full document height in CSS pixels; present only for full-page captures. */
|
|
426
|
+
pageHeight?: number
|
|
424
427
|
artifact: ArtifactDescriptor
|
|
425
428
|
}
|
|
426
429
|
|
|
@@ -1631,6 +1634,7 @@ export class VisionToolkitRuntime {
|
|
|
1631
1634
|
const height = integerInRange(request.height, 800, 1, 8192, 'html_screenshot.height')
|
|
1632
1635
|
const scale = integerInRange(request.scale, 1, 1, 4, 'html_screenshot.scale')
|
|
1633
1636
|
const waitMs = integerInRange(request.waitMs, 0, 0, 120000, 'html_screenshot.waitMs')
|
|
1637
|
+
const fullPage = request.fullPage === true
|
|
1634
1638
|
const outputPixels = width * height * scale * scale
|
|
1635
1639
|
if (!Number.isSafeInteger(outputPixels) || outputPixels > this.config.maxImagePixels) {
|
|
1636
1640
|
throw new VisionToolkitError('capacity', `HTML screenshot would create ${outputPixels} pixels, exceeding maxImagePixels ${this.config.maxImagePixels}`)
|
|
@@ -1657,16 +1661,31 @@ export class VisionToolkitRuntime {
|
|
|
1657
1661
|
String(scale),
|
|
1658
1662
|
'--wait-ms',
|
|
1659
1663
|
String(waitMs),
|
|
1664
|
+
...(fullPage ? ['--full-page', '--max-pixels', String(this.config.maxImagePixels)] : []),
|
|
1660
1665
|
], operation)
|
|
1661
1666
|
const parsed = parseHtmlScreenshotOutput(result.stdout)
|
|
1662
1667
|
const expectedWidth = width * scale
|
|
1663
|
-
|
|
1664
|
-
|
|
1668
|
+
if (parsed.outputPath !== staged || parsed.width !== expectedWidth) {
|
|
1669
|
+
throw new VisionToolkitError('output', 'html_screenshot: upstream summary does not match the requested output')
|
|
1670
|
+
}
|
|
1671
|
+
if (fullPage !== (parsed.pageHeight !== undefined)) {
|
|
1672
|
+
throw new VisionToolkitError('output', 'html_screenshot: upstream full-page metadata does not match the request')
|
|
1673
|
+
}
|
|
1674
|
+
const pageHeight = parsed.pageHeight
|
|
1675
|
+
const expectedHeight = pageHeight === undefined ? height * scale : pageHeight * scale
|
|
1676
|
+
if (pageHeight !== undefined && (!Number.isInteger(pageHeight) || pageHeight <= 0)) {
|
|
1677
|
+
throw new VisionToolkitError('output', 'html_screenshot: upstream reported an invalid page height')
|
|
1678
|
+
}
|
|
1679
|
+
const outputPixels = expectedWidth * expectedHeight
|
|
1680
|
+
if (!Number.isSafeInteger(outputPixels) || outputPixels > this.config.maxImagePixels) {
|
|
1681
|
+
throw new VisionToolkitError('capacity', `HTML screenshot would create ${outputPixels} pixels, exceeding maxImagePixels ${this.config.maxImagePixels}`)
|
|
1682
|
+
}
|
|
1683
|
+
if (parsed.height !== expectedHeight) {
|
|
1665
1684
|
throw new VisionToolkitError('output', 'html_screenshot: upstream summary does not match the requested output')
|
|
1666
1685
|
}
|
|
1667
1686
|
const generated = await this.probeGeneratedImage(staged, operation, 'html_screenshot')
|
|
1668
1687
|
if (generated.format !== 'png' || generated.width !== expectedWidth || generated.height !== expectedHeight) {
|
|
1669
|
-
throw new VisionToolkitError('output', 'html_screenshot: generated PNG dimensions do not match the
|
|
1688
|
+
throw new VisionToolkitError('output', 'html_screenshot: generated PNG dimensions do not match the reported output')
|
|
1670
1689
|
}
|
|
1671
1690
|
await commitStagedOutput(staged, finalPath, policy)
|
|
1672
1691
|
const artifact = await describeArtifact(finalPath, policy, {
|
|
@@ -1682,6 +1701,7 @@ export class VisionToolkitRuntime {
|
|
|
1682
1701
|
viewport: { width, height, scale },
|
|
1683
1702
|
width: expectedWidth,
|
|
1684
1703
|
height: expectedHeight,
|
|
1704
|
+
...(pageHeight === undefined ? {} : { pageHeight }),
|
|
1685
1705
|
artifact,
|
|
1686
1706
|
}
|
|
1687
1707
|
} finally {
|
package/src/skill.ts
CHANGED
|
@@ -45,7 +45,7 @@ never enter the model tool set, before or after Skill activation.
|
|
|
45
45
|
| Split and merge a tall screenshot OCR | vision_long_screenshot_ocr |
|
|
46
46
|
| Extract an icon/logo on transparency | vision_extract_foreground |
|
|
47
47
|
| Measure a palette or choose among exact colors | vision_dominant_colors |
|
|
48
|
-
| Render a local HTML implementation to PNG | vision_html_screenshot |
|
|
48
|
+
| Render a local HTML implementation to PNG (viewport or full document) | vision_html_screenshot |
|
|
49
49
|
|
|
50
50
|
vision_glance, vision_ground, vision_detect, and non-split long OCR send the
|
|
51
51
|
validated image bytes to the external vision service configured by the user.
|
|
@@ -86,7 +86,7 @@ be opened or downloaded by the UI and passed to later tools.
|
|
|
86
86
|
- vision_long_screenshot_ocr -> merged Markdown, manifest JSON, boundary audit,
|
|
87
87
|
chunk PNGs, and OCR sidecars
|
|
88
88
|
- vision_extract_foreground -> transparent PNG
|
|
89
|
-
- vision_html_screenshot -> PNG
|
|
89
|
+
- vision_html_screenshot -> PNG (fullPage=true also reports the CSS pageHeight)
|
|
90
90
|
|
|
91
91
|
## Reliable workflows
|
|
92
92
|
|
|
@@ -109,7 +109,8 @@ request. Reuse the same runName with resume=true to retain matching sidecars.
|
|
|
109
109
|
2. vision_dominant_colors for measured colors.
|
|
110
110
|
3. vision_extract_foreground or vision_trace for reusable assets.
|
|
111
111
|
4. Implement a local HTML file.
|
|
112
|
-
5. vision_html_screenshot with the target viewport
|
|
112
|
+
5. vision_html_screenshot with the target viewport; use fullPage=true when the
|
|
113
|
+
complete document is needed without guessing taller viewports.
|
|
113
114
|
6. vision_pixel_diff against the reference.
|
|
114
115
|
7. Inspect the worst region and iterate until the measured differences are
|
|
115
116
|
acceptable.
|
|
@@ -127,6 +128,9 @@ vision_glance with ocr=true when the text content matters.
|
|
|
127
128
|
invent nested or absolute output paths.
|
|
128
129
|
- vision_html_screenshot accepts local .html/.htm files only, not URLs or data
|
|
129
130
|
URIs.
|
|
131
|
+
- vision_html_screenshot keeps the requested viewport for layout; with
|
|
132
|
+
fullPage=true, it captures the complete document and returns pageHeight
|
|
133
|
+
in CSS pixels.
|
|
130
134
|
- Disabling or unloading the plugin cancels its active visual operations before
|
|
131
135
|
unregistering the native tools and skill.
|
|
132
136
|
- Do not infer image contents after a tool error. Fix the path, limits,
|
package/src/tools.ts
CHANGED
|
@@ -540,7 +540,7 @@ export function createVisionTools(
|
|
|
540
540
|
+ WORKSPACE_NOTE,
|
|
541
541
|
parameters: {
|
|
542
542
|
source: { type: 'string', required: true, description: 'Local HTML path only.' }, width: { type: 'integer' }, height: { type: 'integer' },
|
|
543
|
-
scale: { type: 'integer' }, waitMs: { type: 'integer' }, output: { type: 'string', description: 'Artifact filename; .png only.' },
|
|
543
|
+
scale: { type: 'integer' }, waitMs: { type: 'integer' }, fullPage: { type: 'boolean', description: 'Capture the full document height while preserving the requested viewport.' }, output: { type: 'string', description: 'Artifact filename; .png only.' },
|
|
544
544
|
timeoutMs: { type: 'integer', description: TIMEOUT_NOTE },
|
|
545
545
|
},
|
|
546
546
|
output: {
|
|
@@ -548,7 +548,7 @@ export function createVisionTools(
|
|
|
548
548
|
type: 'object', additionalProperties: false, properties: {
|
|
549
549
|
sourcePath: { type: 'string', required: true }, sourceBytes: { type: 'integer', required: true },
|
|
550
550
|
viewport: { type: 'object', additionalProperties: false, required: true, properties: { width: { type: 'integer', required: true }, height: { type: 'integer', required: true }, scale: { type: 'integer', required: true } } },
|
|
551
|
-
width: { type: 'integer', required: true }, height: { type: 'integer', required: true }, artifact: requiredArtifactSchema,
|
|
551
|
+
width: { type: 'integer', required: true }, height: { type: 'integer', required: true }, pageHeight: { type: 'integer' }, artifact: requiredArtifactSchema,
|
|
552
552
|
},
|
|
553
553
|
},
|
|
554
554
|
render: renderJson,
|
|
@@ -559,6 +559,7 @@ export function createVisionTools(
|
|
|
559
559
|
source: args.source,
|
|
560
560
|
...(args.width === undefined ? {} : { width: args.width }), ...(args.height === undefined ? {} : { height: args.height }),
|
|
561
561
|
...(args.scale === undefined ? {} : { scale: args.scale }), ...(args.waitMs === undefined ? {} : { waitMs: args.waitMs }),
|
|
562
|
+
...(args.fullPage === undefined ? {} : { fullPage: args.fullPage }),
|
|
562
563
|
...(args.output === undefined ? {} : { output: args.output }),
|
|
563
564
|
}
|
|
564
565
|
return runtimeFrom(source).htmlScreenshot(request, callOptions(exec, args.timeoutMs, lifecycleSignal))
|
|
@@ -663,6 +664,7 @@ interface HtmlArgs {
|
|
|
663
664
|
height?: number
|
|
664
665
|
scale?: number
|
|
665
666
|
waitMs?: number
|
|
667
|
+
fullPage?: boolean
|
|
666
668
|
output?: string
|
|
667
669
|
timeoutMs?: number
|
|
668
670
|
}
|
package/src/upstream.ts
CHANGED
|
@@ -165,6 +165,7 @@ export interface HtmlScreenshotOutput {
|
|
|
165
165
|
outputPath: string
|
|
166
166
|
width: number
|
|
167
167
|
height: number
|
|
168
|
+
pageHeight?: number
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
const BOX_SUFFIX = /x1:\s*(\d+),\s*y1:\s*(\d+),\s*x2:\s*(\d+),\s*y2:\s*(\d+)\s*$/
|
|
@@ -448,9 +449,14 @@ export function parseDominantColorsOutput(stdout: string): DominantColorsOutput
|
|
|
448
449
|
|
|
449
450
|
/** Parse the local Chrome screenshot summary. */
|
|
450
451
|
export function parseHtmlScreenshotOutput(stdout: string): HtmlScreenshotOutput {
|
|
451
|
-
const wrote = /^wrote\s+(.+?)\s+\((\d+)x(\d+)\)\s*$/.exec(stdout.trim())
|
|
452
|
+
const wrote = /^wrote\s+(.+?)\s+\((\d+)x(\d+)(?:;\s*pageHeight=(\d+))?\)\s*$/.exec(stdout.trim())
|
|
452
453
|
if (wrote === null) throw new VisionToolkitError('output', 'html_screenshot: upstream did not report a written PNG')
|
|
453
|
-
return {
|
|
454
|
+
return {
|
|
455
|
+
outputPath: wrote[1] ?? '',
|
|
456
|
+
width: Number(wrote[2]),
|
|
457
|
+
height: Number(wrote[3]),
|
|
458
|
+
...(wrote[4] === undefined ? {} : { pageHeight: Number(wrote[4]) }),
|
|
459
|
+
}
|
|
454
460
|
}
|
|
455
461
|
|
|
456
462
|
const REQUIRED_TOOLS = ['glance', 'ground', 'detect', 'crop', 'trace'] as const
|
|
@@ -527,20 +533,24 @@ const VISION_MODEL_GUARD = [
|
|
|
527
533
|
].join('\n')
|
|
528
534
|
|
|
529
535
|
const HTML_SCREENSHOT_GUARD = [
|
|
530
|
-
'import runpy,subprocess,sys,tempfile',
|
|
536
|
+
'import os,runpy,subprocess,sys,tempfile',
|
|
531
537
|
'script=sys.argv[1]',
|
|
532
538
|
'sys.argv=[script,*sys.argv[2:]]',
|
|
533
|
-
'
|
|
539
|
+
'original_popen=subprocess.Popen',
|
|
534
540
|
'with tempfile.TemporaryDirectory(prefix="dsh-vision-chrome-") as profile:',
|
|
535
|
-
'
|
|
541
|
+
' original_profile=os.environ.get("DSH_VISION_CHROME_PROFILE")',
|
|
542
|
+
' os.environ["DSH_VISION_CHROME_PROFILE"]=profile',
|
|
543
|
+
' def guarded_popen(command,*args,**kwargs):',
|
|
536
544
|
' command=list(command)',
|
|
537
545
|
' command[1:1]=["--use-mock-keychain",f"--user-data-dir={profile}","--incognito","--disable-background-networking","--proxy-server=http://127.0.0.1:9","--proxy-bypass-list=<-loopback>"]',
|
|
538
|
-
' return
|
|
539
|
-
' subprocess.
|
|
546
|
+
' return original_popen(command,*args,**kwargs)',
|
|
547
|
+
' subprocess.Popen=guarded_popen',
|
|
540
548
|
' try:',
|
|
541
549
|
' runpy.run_path(script,run_name="__main__")',
|
|
542
550
|
' finally:',
|
|
543
|
-
' subprocess.
|
|
551
|
+
' subprocess.Popen=original_popen',
|
|
552
|
+
' if original_profile is None: os.environ.pop("DSH_VISION_CHROME_PROFILE",None)',
|
|
553
|
+
' else: os.environ["DSH_VISION_CHROME_PROFILE"]=original_profile',
|
|
544
554
|
].join('\n')
|
|
545
555
|
|
|
546
556
|
const LONG_OCR_PINNED_GLANCE = [
|
|
@@ -853,6 +863,9 @@ export class UpstreamAdapter {
|
|
|
853
863
|
if (/Missing config VISION_/i.test(result.stderr)) {
|
|
854
864
|
return new VisionToolkitError('config', message)
|
|
855
865
|
}
|
|
866
|
+
if (/maxImagePixels|exceed(?:s|ing).*pixels/i.test(result.stderr)) {
|
|
867
|
+
return new VisionToolkitError('capacity', message)
|
|
868
|
+
}
|
|
856
869
|
if (/not found|only PNG|unsupported|cannot open|empty region|must be|expects|invalid colour|needs at least/i.test(result.stderr)) {
|
|
857
870
|
return new VisionToolkitError('input', message)
|
|
858
871
|
}
|
package/src/web.ts
CHANGED
|
@@ -28,6 +28,13 @@ import {
|
|
|
28
28
|
type VisionToolkitConfig,
|
|
29
29
|
} from './config.ts'
|
|
30
30
|
import type { VisionToolkitHealthResult } from './runtime.ts'
|
|
31
|
+
import {
|
|
32
|
+
PluginUpdateError,
|
|
33
|
+
VisionToolkitPluginUpdateService,
|
|
34
|
+
type PluginUpdateCapability,
|
|
35
|
+
type PluginUpdateCheck,
|
|
36
|
+
type PluginUpdateResult,
|
|
37
|
+
} from './plugin-update.ts'
|
|
31
38
|
import {
|
|
32
39
|
VisionToolkitRuntimeManager,
|
|
33
40
|
type PreparedRuntimeGeneration,
|
|
@@ -62,6 +69,7 @@ export interface VisionToolkitSettingsSnapshot {
|
|
|
62
69
|
upstreamRepository: string
|
|
63
70
|
upstreamVersion: string
|
|
64
71
|
upstreamCommit: string
|
|
72
|
+
update: PluginUpdateCapability
|
|
65
73
|
}
|
|
66
74
|
artifactRouteAvailable: boolean
|
|
67
75
|
}
|
|
@@ -85,7 +93,16 @@ interface CredentialRequest {
|
|
|
85
93
|
value: string
|
|
86
94
|
}
|
|
87
95
|
|
|
88
|
-
|
|
96
|
+
interface CheckUpdateRequest {
|
|
97
|
+
action: 'check-update'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface ApplyUpdateRequest {
|
|
101
|
+
action: 'apply-update'
|
|
102
|
+
expectedVersion: string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type SettingsRequest = SaveRequest | HealthRequest | CredentialRequest | CheckUpdateRequest | ApplyUpdateRequest
|
|
89
106
|
|
|
90
107
|
interface JsonError {
|
|
91
108
|
ok: false
|
|
@@ -109,6 +126,14 @@ export interface WebRuntimeManager {
|
|
|
109
126
|
status(): RuntimeManagerStatus
|
|
110
127
|
}
|
|
111
128
|
|
|
129
|
+
/** Minimal self-update face used by the Web route and its tests. */
|
|
130
|
+
export interface WebPluginUpdater {
|
|
131
|
+
configureWebServer?(host: string, port: number): void
|
|
132
|
+
capability(): Promise<PluginUpdateCapability>
|
|
133
|
+
check(): Promise<PluginUpdateCheck>
|
|
134
|
+
installAndRestart(expectedVersion: string): Promise<PluginUpdateResult>
|
|
135
|
+
}
|
|
136
|
+
|
|
112
137
|
/** Callback invoked when a Settings save makes the first runtime available. */
|
|
113
138
|
export type RuntimeActivated = () => void
|
|
114
139
|
|
|
@@ -195,6 +220,13 @@ function parseRequest(value: unknown): SettingsRequest {
|
|
|
195
220
|
value: secret,
|
|
196
221
|
}
|
|
197
222
|
}
|
|
223
|
+
if (value.action === 'check-update') return { action: 'check-update' }
|
|
224
|
+
if (value.action === 'apply-update') {
|
|
225
|
+
if (typeof value.expectedVersion !== 'string' || value.expectedVersion.trim().length === 0) {
|
|
226
|
+
throw new TypeError('apply-update.expectedVersion must be a non-empty string')
|
|
227
|
+
}
|
|
228
|
+
return { action: 'apply-update', expectedVersion: value.expectedVersion.trim() }
|
|
229
|
+
}
|
|
198
230
|
throw new TypeError(`unsupported action: ${value.action}`)
|
|
199
231
|
}
|
|
200
232
|
|
|
@@ -205,12 +237,24 @@ function publicMessage(error: unknown): string {
|
|
|
205
237
|
|
|
206
238
|
/** Same-origin Settings and health handler. */
|
|
207
239
|
export class VisionToolkitWebBackend {
|
|
240
|
+
private readonly updater: WebPluginUpdater
|
|
241
|
+
|
|
208
242
|
constructor(
|
|
209
243
|
private readonly ctx: Context,
|
|
210
244
|
private readonly manager: WebRuntimeManager,
|
|
211
245
|
private readonly artifacts: ArtifactAccessController,
|
|
212
246
|
private readonly onRuntimeActivated: RuntimeActivated,
|
|
213
|
-
|
|
247
|
+
updater?: WebPluginUpdater,
|
|
248
|
+
) {
|
|
249
|
+
this.updater = updater ?? new VisionToolkitPluginUpdateService(ctx, PLUGIN_VERSION, {
|
|
250
|
+
runtimeReady: () => this.manager.status().ready,
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Supply the active listener address before the Settings route becomes reachable. */
|
|
255
|
+
configureWebServer(host: string, port: number): void {
|
|
256
|
+
this.updater.configureWebServer?.(host, port)
|
|
257
|
+
}
|
|
214
258
|
|
|
215
259
|
private async credential(config: ResolvedVisionToolkitConfig): Promise<CredentialInfo> {
|
|
216
260
|
if (isBuiltInFreeVisionProvider(config.provider)) {
|
|
@@ -225,6 +269,7 @@ export class VisionToolkitWebBackend {
|
|
|
225
269
|
const value = descriptor.value as VisionToolkitConfig
|
|
226
270
|
const resolved = resolveConfig(value)
|
|
227
271
|
const credential = await this.credential(resolved)
|
|
272
|
+
const update = await this.updater.capability()
|
|
228
273
|
return {
|
|
229
274
|
schemaVersion: 1,
|
|
230
275
|
writable: this.ctx.settings.writable,
|
|
@@ -247,6 +292,7 @@ export class VisionToolkitWebBackend {
|
|
|
247
292
|
upstreamRepository: UPSTREAM_REPOSITORY,
|
|
248
293
|
upstreamVersion: UPSTREAM_VERSION,
|
|
249
294
|
upstreamCommit: UPSTREAM_COMMIT,
|
|
295
|
+
update,
|
|
250
296
|
},
|
|
251
297
|
artifactRouteAvailable: this.artifacts.routeAvailable,
|
|
252
298
|
}
|
|
@@ -350,20 +396,39 @@ export class VisionToolkitWebBackend {
|
|
|
350
396
|
case 'credential':
|
|
351
397
|
responseJson(res, 200, { ok: true, value: await this.saveCredential(parsed) })
|
|
352
398
|
break
|
|
399
|
+
case 'check-update':
|
|
400
|
+
responseJson(res, 200, { ok: true, value: await this.updater.check() })
|
|
401
|
+
break
|
|
402
|
+
case 'apply-update':
|
|
403
|
+
responseJson(res, 200, { ok: true, value: await this.updater.installAndRestart(parsed.expectedVersion) })
|
|
404
|
+
break
|
|
353
405
|
}
|
|
354
406
|
} catch (error) {
|
|
355
407
|
const settingsConflict = error instanceof SettingsConflictError
|
|
356
408
|
const credentialConflict = error instanceof CredentialReferenceConflictError
|
|
409
|
+
const updateError = error instanceof PluginUpdateError
|
|
357
410
|
const code = settingsConflict
|
|
358
411
|
? 'settings-conflict'
|
|
359
412
|
: credentialConflict
|
|
360
413
|
? 'credential-conflict'
|
|
414
|
+
: updateError
|
|
415
|
+
? error.code
|
|
361
416
|
: parsed.action === 'health'
|
|
362
417
|
? 'health-failed'
|
|
363
418
|
: parsed.action === 'credential'
|
|
364
419
|
? 'credential-rejected'
|
|
365
420
|
: 'settings-rejected'
|
|
366
|
-
const
|
|
421
|
+
const updateConflict = updateError && ['update-in-progress', 'update-stale', 'update-unavailable', 'already-current'].includes(error.code)
|
|
422
|
+
const updateGateway = updateError && error.code === 'update-check-failed'
|
|
423
|
+
const status = settingsConflict || credentialConflict || updateConflict
|
|
424
|
+
? 409
|
|
425
|
+
: parsed.action === 'health'
|
|
426
|
+
? 503
|
|
427
|
+
: updateGateway
|
|
428
|
+
? 502
|
|
429
|
+
: updateError
|
|
430
|
+
? 500
|
|
431
|
+
: 400
|
|
367
432
|
this.ctx.logger.warn('dsh-vision-toolkit Web action=%s failed: %s', parsed.action, publicMessage(error))
|
|
368
433
|
requestError(res, status, code, publicMessage(error))
|
|
369
434
|
}
|
|
@@ -454,6 +519,7 @@ export function installVisionToolkitWeb(
|
|
|
454
519
|
): void {
|
|
455
520
|
ctx.inject(['webServer'], (webCtx) => {
|
|
456
521
|
webCtx.effect(() => {
|
|
522
|
+
backend.configureWebServer(webCtx.webServer.host, webCtx.webServer.port)
|
|
457
523
|
const detach = artifacts.attachRoute()
|
|
458
524
|
const disposeArtifact = webCtx.webServer.register({
|
|
459
525
|
kind: 'prefix',
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"repository": "https://github.com/Anionex/agent-vision-toolkit",
|
|
4
4
|
"version": "v0.1.0+snapshot.bc9803d",
|
|
5
5
|
"commit": "bc9803d7d6300c864d17460ecbb33540b26638e0",
|
|
6
|
-
"contentSha256": "
|
|
6
|
+
"contentSha256": "aee26acedf76b083addc31acfc8817eb2a2044d144b495f4437adc9e20d18750",
|
|
7
7
|
"files": [
|
|
8
8
|
{
|
|
9
9
|
"path": "CHANGELOG.md",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
"path": "skills/vision-tools/scripts/html_shot.py",
|
|
70
|
-
"bytes":
|
|
71
|
-
"sha256": "
|
|
70
|
+
"bytes": 17573,
|
|
71
|
+
"sha256": "242e62e5d8bc02c4a707c57fe782cefb7f55abc0652792c1881a3f5d36c9302f"
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
"path": "skills/vision-tools/scripts/long_screenshot_ocr.py",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|