dsh-vision-router 1.2.2 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/README.zh.md +3 -1
- package/index.js +9 -0
- package/lib/client.js +138 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,9 +28,11 @@
|
|
|
28
28
|
<p align="center">💬 <strong>QQ community group: 1105463028</strong></p>
|
|
29
29
|
|
|
30
30
|
> [!WARNING]
|
|
31
|
-
> 📌 **Announcement (v1.2.
|
|
31
|
+
> 📌 **Announcement (v1.2.3)**
|
|
32
32
|
>
|
|
33
|
-
> v1.2.
|
|
33
|
+
> v1.2.3 fixes DSH Desktop's re-appearing first-run dialog: the onboarding "seen" flag and the model-guide step now persist in the profile settings file instead of origin-scoped `localStorage`, which a random per-launch port (`--port 0`) wiped on every boot (issue #78).
|
|
34
|
+
>
|
|
35
|
+
> v1.2.2 closed the last attachment-id gap — ids announced for images the host persisted itself (e.g. `read_image` re-uploads, `sha256:…`) now resolve in `vision_describe` and every pixel tool (issue #72) — stopped `vision_present` and other tool-result image blocks from ever locking a text-model session with `UNSUPPORTED_CONTENT` (issue #74; already-locked sessions heal after upgrading), and warned loudly when a stale sharp left over from a pre-v1.2 upgrade would break the pixel tools with `colourspace: parameter space not set` (issue #75).
|
|
34
36
|
>
|
|
35
37
|
> v1.2.1 hardened the pixel loop: all eleven pixel tools now accept uploaded-image attachment ids directly (no more `cannot read …/sha256:…` round trips), artifact filenames carry collision-free fingerprints, `vision_ground` retries degenerate boxes, the model guide replays fully from step 1 (leaving the settings first), and the settings card scrolls smoothly even with hundreds of models per provider.
|
|
36
38
|
|
package/README.zh.md
CHANGED
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
<p align="center">💬 <strong>QQ 用户交流群:1105463028</strong></p>
|
|
29
29
|
|
|
30
30
|
> [!WARNING]
|
|
31
|
-
> 📌 **公告(v1.2.
|
|
31
|
+
> 📌 **公告(v1.2.3)**
|
|
32
|
+
>
|
|
33
|
+
> v1.2.3 现已支持:修复 DSH Desktop 每次启动都重复弹出首次引导的问题——引导「已读」标记与模型引导步骤改存 profile 设置文件(不再依赖按 origin 隔离、每次随机端口重启即清零的 `localStorage`)(issue #78)。
|
|
32
34
|
>
|
|
33
35
|
> v1.2.2 现已支持:补上最后一处附件 ID 缺口——宿主 `read_image` 回挂图片公布的 `sha256:…` ID 现在可被 `vision_describe` 与全部像素工具解析(issue #72);`vision_present` 等工具结果里的图像块不再把文本模型会话锁死在 `UNSUPPORTED_CONTENT`(issue #74,已锁死的历史会话升级后自动修复);检测到 v1.1.x 升级残留的旧版 sharp 时明确告警,把玄学的 `colourspace` 报错变成一眼可见的修复指引(issue #75)。
|
|
34
36
|
>
|
package/index.js
CHANGED
|
@@ -233,6 +233,15 @@ export const Config = z.object({
|
|
|
233
233
|
tool: z.boolean().default(true),
|
|
234
234
|
progressiveTools: z.boolean().default(true),
|
|
235
235
|
autoActivateOnImage: z.boolean().default(true),
|
|
236
|
+
// Client-persisted UI state (issue #78): DSH Desktop serves the Web UI from
|
|
237
|
+
// a random port on every launch, so origin-scoped localStorage forgets the
|
|
238
|
+
// first-run onboarding dialog and it re-appeared on every boot. These keys
|
|
239
|
+
// live in the settings section (the profile settings file) instead; the
|
|
240
|
+
// client still mirrors best-effort copies into localStorage for downgrades.
|
|
241
|
+
// Both are internal to the client bundle — the settings card renders them
|
|
242
|
+
// nowhere, and the server half never reads them.
|
|
243
|
+
onboardingSeen: z.boolean().default(false),
|
|
244
|
+
visionGuideStep: z.string().default(''),
|
|
236
245
|
artifactsDir: z.string().default('.dsh-vision-router/artifacts'),
|
|
237
246
|
rewriteImages: z.boolean().default(true),
|
|
238
247
|
downscale: z.boolean().default(true),
|
package/lib/client.js
CHANGED
|
@@ -554,20 +554,116 @@ window.__ModuleLoader__.load({
|
|
|
554
554
|
}
|
|
555
555
|
|
|
556
556
|
const ONBOARDING_STORAGE_KEY = 'dsh-vision-router:onboarding:model-guide-v2'
|
|
557
|
+
// issue #78: DSH Desktop serves the Web UI from a random port on every
|
|
558
|
+
// launch (--port 0), so origin-scoped localStorage forgets everything
|
|
559
|
+
// between restarts and the first-run dialog re-appeared on every boot.
|
|
560
|
+
// The durable source of truth is now the `vision-router` settings section
|
|
561
|
+
// (the profile settings file, synced through ctx.settingsScope);
|
|
562
|
+
// localStorage survives only as a legacy/migration fallback for
|
|
563
|
+
// pre-v1.2.3 browsers and as a best-effort downgrade channel.
|
|
564
|
+
const ONBOARDING_SETTINGS_KEY = 'onboardingSeen'
|
|
557
565
|
// The walkthrough has three steps: step1 (the session/text model selector
|
|
558
566
|
// on the chat page), step2 (open Settings → Plugins → Vision Router), and
|
|
559
567
|
// step3 (the highlighted vision chain, rendered by the settings card as a
|
|
560
568
|
// callout). Only step1/step2 need persistence; step3 re-derives from the
|
|
561
569
|
// card being on screen.
|
|
562
570
|
const VISION_GUIDE_STORAGE_KEY = 'dsh-vision-router:guide:vision-backend-v2'
|
|
571
|
+
const VISION_GUIDE_SETTINGS_KEY = 'visionGuideStep'
|
|
563
572
|
const VISION_GUIDE_EVENT = 'dsh-vision-router:vision-settings-guide'
|
|
564
573
|
let visionGuidePrompt
|
|
565
574
|
let visionGuideStepMemory
|
|
575
|
+
// Installed by apply(): a narrow, defensive view of the settings scope.
|
|
576
|
+
// { get, set, unset } never throw, and subscribe returns a disposer (or
|
|
577
|
+
// undefined when the scope is unavailable — the legacy paths take over).
|
|
578
|
+
let settingsPersistence
|
|
579
|
+
function installSettingsPersistence(scope) {
|
|
580
|
+
const readSection = () => {
|
|
581
|
+
try {
|
|
582
|
+
const snapshot = scope && scope.getSnapshot && scope.getSnapshot()
|
|
583
|
+
return snapshot && snapshot.value ? snapshot.value : undefined
|
|
584
|
+
} catch {
|
|
585
|
+
return undefined
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
settingsPersistence = {
|
|
589
|
+
get(field) {
|
|
590
|
+
const section = readSection()
|
|
591
|
+
return section ? section[field] : undefined
|
|
592
|
+
},
|
|
593
|
+
set(field, value) {
|
|
594
|
+
try {
|
|
595
|
+
const snapshot = scope && scope.getSnapshot && scope.getSnapshot()
|
|
596
|
+
if (!snapshot || !snapshot.writable || typeof scope.set !== 'function') return
|
|
597
|
+
void scope.set(field, value)
|
|
598
|
+
} catch {
|
|
599
|
+
// Read-only provider or unavailable scope: the localStorage
|
|
600
|
+
// fallback keeps this load functional.
|
|
601
|
+
}
|
|
602
|
+
},
|
|
603
|
+
unset(field) {
|
|
604
|
+
try {
|
|
605
|
+
const snapshot = scope && scope.getSnapshot && scope.getSnapshot()
|
|
606
|
+
if (!snapshot || !snapshot.writable || typeof scope.unset !== 'function') return
|
|
607
|
+
void scope.unset(field)
|
|
608
|
+
} catch {
|
|
609
|
+
// Same fallback reasoning as set().
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
subscribe(listener) {
|
|
613
|
+
try {
|
|
614
|
+
return scope && typeof scope.subscribe === 'function' ? scope.subscribe(listener) : undefined
|
|
615
|
+
} catch {
|
|
616
|
+
return undefined
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function readOnboardingSeen() {
|
|
623
|
+
try {
|
|
624
|
+
if (settingsPersistence && settingsPersistence.get(ONBOARDING_SETTINGS_KEY) === true) return true
|
|
625
|
+
} catch {
|
|
626
|
+
// Fall through to the legacy origin-scoped marker.
|
|
627
|
+
}
|
|
628
|
+
try {
|
|
629
|
+
if (window.localStorage && window.localStorage.getItem(ONBOARDING_STORAGE_KEY) === 'seen') {
|
|
630
|
+
// Legacy value from before the settings-backed flag: migrate it so
|
|
631
|
+
// it survives desktop restarts, then honor it for this load.
|
|
632
|
+
if (settingsPersistence) settingsPersistence.set(ONBOARDING_SETTINGS_KEY, true)
|
|
633
|
+
return true
|
|
634
|
+
}
|
|
635
|
+
} catch {
|
|
636
|
+
// Best effort only.
|
|
637
|
+
}
|
|
638
|
+
return false
|
|
639
|
+
}
|
|
640
|
+
function rememberOnboardingSeen() {
|
|
641
|
+
try {
|
|
642
|
+
if (settingsPersistence) settingsPersistence.set(ONBOARDING_SETTINGS_KEY, true)
|
|
643
|
+
} catch {
|
|
644
|
+
// The dialog can still be dismissed for this page load.
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
if (window.localStorage) window.localStorage.setItem(ONBOARDING_STORAGE_KEY, 'seen')
|
|
648
|
+
} catch {
|
|
649
|
+
// Best effort: the dialog can still be dismissed for this page load.
|
|
650
|
+
}
|
|
651
|
+
}
|
|
566
652
|
|
|
567
653
|
function readVisionGuideStep() {
|
|
568
654
|
try {
|
|
569
|
-
const value =
|
|
655
|
+
const value = settingsPersistence && settingsPersistence.get(VISION_GUIDE_SETTINGS_KEY)
|
|
570
656
|
if (value === 'step1' || value === 'step2') return value
|
|
657
|
+
} catch {
|
|
658
|
+
// Fall through to the legacy origin-scoped marker.
|
|
659
|
+
}
|
|
660
|
+
try {
|
|
661
|
+
const value = window.localStorage && window.localStorage.getItem(VISION_GUIDE_STORAGE_KEY)
|
|
662
|
+
if (value === 'step1' || value === 'step2') {
|
|
663
|
+
// Legacy value: migrate into settings for desktop durability.
|
|
664
|
+
if (settingsPersistence) settingsPersistence.set(VISION_GUIDE_SETTINGS_KEY, value)
|
|
665
|
+
return value
|
|
666
|
+
}
|
|
571
667
|
} catch {
|
|
572
668
|
// Fall through to page-memory state when storage access is blocked.
|
|
573
669
|
}
|
|
@@ -575,6 +671,14 @@ window.__ModuleLoader__.load({
|
|
|
575
671
|
}
|
|
576
672
|
function writeVisionGuideStep(step) {
|
|
577
673
|
visionGuideStepMemory = step === 'step1' || step === 'step2' ? step : undefined
|
|
674
|
+
try {
|
|
675
|
+
if (settingsPersistence) {
|
|
676
|
+
if (visionGuideStepMemory) settingsPersistence.set(VISION_GUIDE_SETTINGS_KEY, visionGuideStepMemory)
|
|
677
|
+
else settingsPersistence.unset(VISION_GUIDE_SETTINGS_KEY)
|
|
678
|
+
}
|
|
679
|
+
} catch {
|
|
680
|
+
// Page-memory state still keeps the guide functional for this load.
|
|
681
|
+
}
|
|
578
682
|
try {
|
|
579
683
|
if (!window.localStorage) return
|
|
580
684
|
if (visionGuideStepMemory) window.localStorage.setItem(VISION_GUIDE_STORAGE_KEY, visionGuideStepMemory)
|
|
@@ -677,9 +781,13 @@ window.__ModuleLoader__.load({
|
|
|
677
781
|
syncVisionGuidePrompt(t)
|
|
678
782
|
window.addEventListener(VISION_GUIDE_EVENT, sync)
|
|
679
783
|
window.addEventListener('popstate', sync)
|
|
784
|
+
// The durable step now lives in the settings section; re-sync when its
|
|
785
|
+
// snapshot changes (e.g. the first read resolves after page load).
|
|
786
|
+
const unsubscribe = settingsPersistence && settingsPersistence.subscribe(sync)
|
|
680
787
|
return () => {
|
|
681
788
|
window.removeEventListener(VISION_GUIDE_EVENT, sync)
|
|
682
789
|
window.removeEventListener('popstate', sync)
|
|
790
|
+
if (typeof unsubscribe === 'function') unsubscribe()
|
|
683
791
|
removeVisionGuidePrompt()
|
|
684
792
|
}
|
|
685
793
|
}
|
|
@@ -687,13 +795,6 @@ window.__ModuleLoader__.load({
|
|
|
687
795
|
let onboardingOverlay
|
|
688
796
|
let onboardingKeyDown
|
|
689
797
|
|
|
690
|
-
function rememberOnboardingSeen() {
|
|
691
|
-
try {
|
|
692
|
-
if (window.localStorage) window.localStorage.setItem(ONBOARDING_STORAGE_KEY, 'seen')
|
|
693
|
-
} catch {
|
|
694
|
-
// Best effort: the dialog can still be dismissed for this page load.
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
798
|
function dismissOnboarding(remember = true) {
|
|
698
799
|
if (remember) rememberOnboardingSeen()
|
|
699
800
|
if (typeof document !== 'undefined' && onboardingKeyDown) {
|
|
@@ -779,16 +880,33 @@ window.__ModuleLoader__.load({
|
|
|
779
880
|
}
|
|
780
881
|
function installOnboarding(t) {
|
|
781
882
|
if (typeof document === 'undefined' || typeof window === 'undefined') return
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
883
|
+
let timer
|
|
884
|
+
const clearTimer = () => {
|
|
885
|
+
if (timer !== undefined) window.clearTimeout(timer)
|
|
886
|
+
timer = undefined
|
|
786
887
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
888
|
+
// Reactive against the settings scope: the settings snapshot arrives
|
|
889
|
+
// asynchronously on a cold page load, so a one-shot localStorage check
|
|
890
|
+
// could show the dialog before the durable "seen" flag lands. Re-sync
|
|
891
|
+
// on every snapshot change and dismiss the overlay if the flag appears.
|
|
892
|
+
const sync = () => {
|
|
893
|
+
if (readOnboardingSeen()) {
|
|
894
|
+
clearTimer()
|
|
895
|
+
if (onboardingOverlay) dismissOnboarding(false)
|
|
896
|
+
return
|
|
897
|
+
}
|
|
898
|
+
if (timer === undefined && !onboardingOverlay) {
|
|
899
|
+
timer = window.setTimeout(() => {
|
|
900
|
+
timer = undefined
|
|
901
|
+
if (!readOnboardingSeen()) showOnboarding(t)
|
|
902
|
+
}, 650)
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
sync()
|
|
906
|
+
const unsubscribe = settingsPersistence && settingsPersistence.subscribe(sync)
|
|
790
907
|
return () => {
|
|
791
|
-
|
|
908
|
+
clearTimer()
|
|
909
|
+
if (typeof unsubscribe === 'function') unsubscribe()
|
|
792
910
|
// Unmounting is not a user choice: drop the dialog without marking
|
|
793
911
|
// the guide seen, so the first-run overview can still auto-open.
|
|
794
912
|
if (onboardingOverlay) dismissOnboarding(false)
|
|
@@ -1894,6 +2012,10 @@ window.__ModuleLoader__.load({
|
|
|
1894
2012
|
|
|
1895
2013
|
function apply(ctx) {
|
|
1896
2014
|
const scope = ctx.settingsScope.bind({ namespace: 'vision-router' })
|
|
2015
|
+
// issue #78: route the onboarding/guide durability through the settings
|
|
2016
|
+
// section (profile settings file) so it survives DSH Desktop's random
|
|
2017
|
+
// per-launch port. Installed before the effects below run.
|
|
2018
|
+
installSettingsPersistence(scope)
|
|
1897
2019
|
const presentedImageUrls = new Map()
|
|
1898
2020
|
const createdImageUrls = new Set()
|
|
1899
2021
|
const loadPresentedImage = (sessionId, attachment) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vision-router",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
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": {
|