dsh-background-by-model 0.4.0 → 0.4.2

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,1160 @@
1
+ import { createRequire } from "node:module";
2
+ import { access, readFile, readdir } from "node:fs/promises";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, join, resolve } from "node:path";
5
+ //#region src/host-contracts.ts
6
+ const SIDEBAR_RIGHT = "dsh-client-ui-sidebar-right";
7
+ /**
8
+ * The contract table.
9
+ *
10
+ * Ordered by group and then by how visible a break is: the model hop first (the
11
+ * plugin's whole purpose), then the surfaces it paints, then the tokens it
12
+ * re-emits, then the regions it decorates.
13
+ */
14
+ const HOST_CONTRACTS = [
15
+ {
16
+ id: "sessions.list",
17
+ group: "model",
18
+ kind: "host-service",
19
+ criticality: "core",
20
+ since: "0.1.7",
21
+ label: {
22
+ en: "Session list service",
23
+ zh: "会话列表服务"
24
+ },
25
+ symptom: {
26
+ en: "no session id can be read, so the background is stuck on rule 1 and never follows the model",
27
+ zh: "读不到会话 id,背景会一直停在规则 1,不再跟着模型切换"
28
+ },
29
+ target: "sessions.list",
30
+ checks: [
31
+ {
32
+ kind: "service",
33
+ id: "sessions"
34
+ },
35
+ {
36
+ kind: "path",
37
+ service: "sessions",
38
+ path: "list",
39
+ expect: "observable"
40
+ },
41
+ {
42
+ kind: "path",
43
+ service: "sessions",
44
+ path: "list.getSnapshot",
45
+ expect: "function"
46
+ }
47
+ ],
48
+ sources: [{
49
+ path: "dsh-api-session-controller/lib/client.js",
50
+ literal: "retainedBy"
51
+ }],
52
+ usedBy: "src/client/modelbg.ts"
53
+ },
54
+ {
55
+ id: "sessions.list.current",
56
+ group: "model",
57
+ kind: "host-service",
58
+ criticality: "label",
59
+ since: "<=0.1.6",
60
+ label: {
61
+ en: "Session list: legacy current id",
62
+ zh: "会话列表:旧版 current 字段"
63
+ },
64
+ symptom: {
65
+ en: "harmless on its own — the 0.1.7 hops below take over",
66
+ zh: "本身无害 —— 0.1.7 上由下面的兜底路径接管"
67
+ },
68
+ target: "list.current",
69
+ checks: [],
70
+ sources: [{
71
+ path: "dsh-api-session-controller/lib/client.js",
72
+ literal: "retainedBy"
73
+ }],
74
+ usedBy: "src/client/modelbg.ts"
75
+ },
76
+ {
77
+ id: "sessions.retainedBy.mainView",
78
+ group: "model",
79
+ kind: "host-service",
80
+ criticality: "label",
81
+ since: "0.1.7",
82
+ label: {
83
+ en: "Main-view retention on the session row",
84
+ zh: "会话行的主视图占用标记"
85
+ },
86
+ symptom: {
87
+ en: "while uiSession is not mounted yet, no session id can be derived",
88
+ zh: "uiSession 尚未挂载时推导不出会话 id"
89
+ },
90
+ target: "byId[].retainedBy.mainView",
91
+ checks: [],
92
+ sources: [{
93
+ path: "dsh-api-session-controller/lib/client.js",
94
+ literal: "retainedBy"
95
+ }],
96
+ usedBy: "src/client/modelbg.ts"
97
+ },
98
+ {
99
+ id: "uiSession.current",
100
+ group: "model",
101
+ kind: "host-service",
102
+ criticality: "core",
103
+ since: "0.1.7",
104
+ label: {
105
+ en: "uiSession service (main view session)",
106
+ zh: "uiSession 服务(主视图会话)"
107
+ },
108
+ symptom: {
109
+ en: "the session id can only be guessed from the list retention",
110
+ zh: "会话 id 只能退回用列表占用标记去猜"
111
+ },
112
+ target: "uiSession.current",
113
+ checks: [{
114
+ kind: "path",
115
+ service: "uiSession",
116
+ path: "current",
117
+ expect: "observable"
118
+ }],
119
+ sources: [{
120
+ path: "dsh-client-ui-session/lib/client.js",
121
+ literal: "uiSession"
122
+ }],
123
+ usedBy: "src/client/modelbg.ts"
124
+ },
125
+ {
126
+ id: "sessions.binding.projections",
127
+ group: "model",
128
+ kind: "host-service",
129
+ criticality: "core",
130
+ since: "<=0.1.6",
131
+ label: {
132
+ en: "Session binding + projections",
133
+ zh: "会话 binding 与 projections"
134
+ },
135
+ symptom: {
136
+ en: "this session's own model selection is unreadable — only the host default is left",
137
+ zh: "读不到本会话自己的模型选择,只剩宿主默认模型可用"
138
+ },
139
+ target: "binding(id).session.projections.faceOf(\"modelSelection\")",
140
+ checks: [{
141
+ kind: "path",
142
+ service: "sessions",
143
+ path: "binding",
144
+ expect: "function"
145
+ }],
146
+ sources: [{
147
+ path: "dsh-api-session-controller/lib/client.js",
148
+ literal: "faceOf"
149
+ }],
150
+ usedBy: "src/client/modelbg.ts"
151
+ },
152
+ {
153
+ id: "modelDirectories.directoryFor",
154
+ group: "model",
155
+ kind: "host-service",
156
+ criticality: "label",
157
+ since: "<=0.1.6",
158
+ label: {
159
+ en: "Model directory (display names)",
160
+ zh: "模型目录(显示名)"
161
+ },
162
+ symptom: {
163
+ en: "the model is shown as provider/model id instead of its display name",
164
+ zh: "模型只会显示 provider/模型 id,读不到显示名"
165
+ },
166
+ target: "modelDirectories.directoryFor(id)",
167
+ checks: [{
168
+ kind: "path",
169
+ service: "modelDirectories",
170
+ path: "directoryFor",
171
+ expect: "function"
172
+ }],
173
+ sources: [{
174
+ path: "dsh-client-ui-model-selection/lib/client.js",
175
+ literal: "modelSelection"
176
+ }],
177
+ usedBy: "src/client/modelbg.ts"
178
+ },
179
+ {
180
+ id: "shell.overlay",
181
+ group: "surface",
182
+ kind: "host-attr",
183
+ criticality: "looks",
184
+ since: "<=0.1.6",
185
+ label: {
186
+ en: "AppFrame overlay + its three columns",
187
+ zh: "主框架浮层与三列"
188
+ },
189
+ symptom: {
190
+ en: "per-part blur and the main-background opacity cannot find the columns at all",
191
+ zh: "分区域模糊与主背景透明度完全找不到那几列"
192
+ },
193
+ target: "[data-shell-overlay]",
194
+ checks: [{
195
+ kind: "attr",
196
+ attr: "data-shell-overlay"
197
+ }],
198
+ sources: [{
199
+ path: "dsh-client-ui-layout/lib/client.js",
200
+ literal: "data-shell-overlay"
201
+ }],
202
+ usedBy: "src/client/wallpaper.ts"
203
+ },
204
+ {
205
+ id: "settings.dialog",
206
+ group: "surface",
207
+ kind: "dom-selector",
208
+ criticality: "looks",
209
+ since: "<=0.1.6",
210
+ label: {
211
+ en: "Settings dialog (aria-modal)",
212
+ zh: "设置弹窗(aria-modal)"
213
+ },
214
+ symptom: {
215
+ en: "the settings panel opacity and blur sliders stop having any effect",
216
+ zh: "设置界面透明度与模糊失效"
217
+ },
218
+ target: "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]",
219
+ checks: [{
220
+ kind: "selector",
221
+ selector: "div[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]",
222
+ optionalWhen: "settingsClosed"
223
+ }],
224
+ sources: [{
225
+ path: "dsh-client-ui-primitives/lib/index.js",
226
+ literal: "aria-modal"
227
+ }, {
228
+ path: "src/client/components/ui.css.ts",
229
+ literal: ".dab-card"
230
+ }],
231
+ usedBy: "src/client/wallpaper.ts"
232
+ },
233
+ {
234
+ id: "settings.card",
235
+ group: "surface",
236
+ kind: "host-style",
237
+ criticality: "looks",
238
+ since: "<=0.1.6",
239
+ label: {
240
+ en: "Option card inside the dialog (.dab-card)",
241
+ zh: "弹窗内的选项卡(.dab-card)"
242
+ },
243
+ symptom: {
244
+ en: "the dialog option-panel blur has nothing to attach to",
245
+ zh: "弹窗内选项卡的模糊没有附着对象"
246
+ },
247
+ target: ".dab-card",
248
+ checks: [{
249
+ kind: "rule",
250
+ match: ".dab-card",
251
+ hint: "settings dialog card class"
252
+ }],
253
+ sources: [{
254
+ path: "src/client/components/ui.css.ts",
255
+ literal: ".dab-card"
256
+ }],
257
+ usedBy: "src/client/wallpaper.ts"
258
+ },
259
+ {
260
+ id: "composer.card",
261
+ group: "surface",
262
+ kind: "host-attr",
263
+ criticality: "looks",
264
+ since: "<=0.1.6",
265
+ label: {
266
+ en: "Composer card",
267
+ zh: "输入框卡片"
268
+ },
269
+ symptom: {
270
+ en: "the composer loses its backdrop — the input blur slider does nothing",
271
+ zh: "输入框失去毛玻璃背板,输入区模糊滑块失效"
272
+ },
273
+ target: "[data-composer-card]",
274
+ checks: [{
275
+ kind: "attr",
276
+ attr: "data-composer-card",
277
+ optionalWhen: "chatNotMounted"
278
+ }],
279
+ sources: [{
280
+ path: "dsh-client-ui-conversation/lib/client.js",
281
+ literal: "data-composer-card"
282
+ }],
283
+ usedBy: "src/client/wallpaper.ts"
284
+ },
285
+ {
286
+ id: "cordis.panel",
287
+ group: "surface",
288
+ kind: "host-attr",
289
+ criticality: "label",
290
+ since: "<=0.1.6",
291
+ label: {
292
+ en: "Cordis panel",
293
+ zh: "Cordis 面板"
294
+ },
295
+ symptom: {
296
+ en: "the Cordis panel keeps the host surface instead of the input slider's alpha",
297
+ zh: "Cordis 面板拿不到输入区滑块的透明度,仍是宿主表面"
298
+ },
299
+ target: "[data-cordis-panel]",
300
+ checks: [{
301
+ kind: "attr",
302
+ attr: "data-cordis-panel",
303
+ optionalWhen: "cordisClosed"
304
+ }],
305
+ sources: [{
306
+ path: "dsh-client-ui-cordis/lib/client.js",
307
+ literal: "data-cordis-panel"
308
+ }],
309
+ usedBy: "src/client/wallpaper.ts"
310
+ },
311
+ {
312
+ id: "rightbar.panel",
313
+ group: "surface",
314
+ kind: "host-attr",
315
+ criticality: "looks",
316
+ since: "0.1.7",
317
+ label: {
318
+ en: "File-preview panel host",
319
+ zh: "文件预览面板宿主"
320
+ },
321
+ symptom: {
322
+ en: "the right panel loses its own opacity/blur card and falls back to the main background",
323
+ zh: "右侧栏失去自己的透明度/模糊控制,退回跟随主背景"
324
+ },
325
+ target: "[data-sidebar-right-panel]",
326
+ checks: [{
327
+ kind: "selector",
328
+ selector: "div[data-sidebar-right-panel]",
329
+ optionalWhen: "noRightPanel"
330
+ }],
331
+ sources: [{
332
+ path: `${SIDEBAR_RIGHT}/lib/client.js`,
333
+ literal: "data-sidebar-right-panel"
334
+ }],
335
+ usedBy: "src/client/wallpaper.ts"
336
+ },
337
+ {
338
+ id: "rightbar.openFlag",
339
+ group: "surface",
340
+ kind: "host-attr",
341
+ criticality: "looks",
342
+ since: "0.1.7",
343
+ label: {
344
+ en: "Right panel open/closed flag",
345
+ zh: "右侧栏开合标记"
346
+ },
347
+ symptom: {
348
+ en: "the panel's backdrop cannot tell open from closed — a frosted plate covers the empty half",
349
+ zh: "分不清右侧栏开着还是关着,空白的右半屏会出现磨砂板"
350
+ },
351
+ target: "[data-sidebar-right-open]",
352
+ checks: [{
353
+ kind: "rule",
354
+ match: "[data-sidebar-right-open]",
355
+ hint: "right panel open flag"
356
+ }],
357
+ sources: [{
358
+ path: `${SIDEBAR_RIGHT}/lib/client.js`,
359
+ literal: "data-sidebar-right-open"
360
+ }],
361
+ usedBy: "src/client/wallpaper.ts"
362
+ },
363
+ {
364
+ id: "dockkit.content",
365
+ group: "surface",
366
+ kind: "host-attr",
367
+ criticality: "core",
368
+ since: "0.1.7",
369
+ label: {
370
+ en: "Dock tab host (the panel's painted surface)",
371
+ zh: "停靠页宿主(面板真正的表面)"
372
+ },
373
+ symptom: {
374
+ en: "nothing carries the panel surface any more — the right-panel card is painted on a container that is always full width",
375
+ zh: "面板表面不再落在任何元素上,右面板卡片会被画在一个恒定满宽的容器上"
376
+ },
377
+ target: "[data-dockkit-content]",
378
+ checks: [{
379
+ kind: "rule",
380
+ match: "[data-dockkit-content]",
381
+ hint: "dock tab host"
382
+ }],
383
+ sources: [{
384
+ path: ".",
385
+ literal: "dockkit-content",
386
+ root: "frontend"
387
+ }],
388
+ usedBy: "src/client/wallpaper.ts"
389
+ },
390
+ {
391
+ id: "dockkit.empty",
392
+ group: "surface",
393
+ kind: "host-attr",
394
+ criticality: "looks",
395
+ since: "0.1.7",
396
+ label: {
397
+ en: "Dock empty seat",
398
+ zh: "停靠空位"
399
+ },
400
+ symptom: {
401
+ en: "an empty preview pane paints the host surface over the wallpaper",
402
+ zh: "空的预览格会用宿主表面盖住壁纸"
403
+ },
404
+ target: "[data-dockkit-empty]",
405
+ checks: [{
406
+ kind: "rule",
407
+ match: "[data-dockkit-empty]",
408
+ hint: "dock empty seat"
409
+ }],
410
+ sources: [{
411
+ path: `${SIDEBAR_RIGHT}/lib/client.js`,
412
+ literal: "data-dockkit-empty"
413
+ }],
414
+ usedBy: "src/client/wallpaper.ts"
415
+ },
416
+ {
417
+ id: "dockkit.float",
418
+ group: "surface",
419
+ kind: "host-attr",
420
+ criticality: "label",
421
+ since: "0.1.7",
422
+ label: {
423
+ en: "Floating dock pane flag",
424
+ zh: "浮动停靠面板标记"
425
+ },
426
+ symptom: {
427
+ en: "floating panes would be painted like docked ones, frosting a panel that should stay the host's",
428
+ zh: "浮动面板会被当作停靠面板上色,本该保持宿主样式的面板被磨砂"
429
+ },
430
+ target: "[data-dockkit-float]",
431
+ checks: [{
432
+ kind: "rule",
433
+ match: "[data-dockkit-float]",
434
+ hint: "floating pane flag"
435
+ }],
436
+ sources: [{
437
+ path: `${SIDEBAR_RIGHT}/lib/client.js`,
438
+ literal: "data-dockkit-float"
439
+ }],
440
+ usedBy: "src/client/wallpaper.ts"
441
+ },
442
+ {
443
+ id: "chat.flow",
444
+ group: "region",
445
+ kind: "dom-selector",
446
+ criticality: "looks",
447
+ since: "<=0.1.6",
448
+ label: {
449
+ en: "Chat message column",
450
+ zh: "对话消息列"
451
+ },
452
+ symptom: {
453
+ en: "the conversation card (and its opacity/blur) cannot find the message column",
454
+ zh: "对话卡片(及其透明度/模糊)找不到消息列"
455
+ },
456
+ target: "[data-chat-flow]",
457
+ checks: [{
458
+ kind: "selector",
459
+ selector: "[data-chat-flow]",
460
+ optionalWhen: "chatNotMounted"
461
+ }],
462
+ sources: [{
463
+ path: "dsh-client-ui-chat/lib/client.js",
464
+ literal: "data-chat-flow"
465
+ }],
466
+ usedBy: "src/client/wallpaper.ts"
467
+ },
468
+ {
469
+ id: "conversation.scroll",
470
+ group: "region",
471
+ kind: "dom-selector",
472
+ criticality: "looks",
473
+ since: "<=0.1.6",
474
+ label: {
475
+ en: "Conversation scrollport",
476
+ zh: "对话滚动区"
477
+ },
478
+ symptom: {
479
+ en: "the marker-less fallback would wrap the whole scrollport in the conversation card",
480
+ zh: "标记缺失时的兜底会把整个滚动区套上对话卡片"
481
+ },
482
+ target: "[data-conversation-scroll]",
483
+ checks: [{
484
+ kind: "selector",
485
+ selector: "[data-conversation-scroll]",
486
+ optionalWhen: "chatNotMounted"
487
+ }],
488
+ sources: [{
489
+ path: "dsh-client-ui-conversation/lib/client.js",
490
+ literal: "data-conversation-scroll"
491
+ }],
492
+ usedBy: "src/client/wallpaper.ts"
493
+ },
494
+ {
495
+ id: "trajectory.root",
496
+ group: "region",
497
+ kind: "dom-selector",
498
+ criticality: "looks",
499
+ since: "<=0.1.6",
500
+ label: {
501
+ en: "Trajectory view root",
502
+ zh: "轨迹页根节点"
503
+ },
504
+ symptom: {
505
+ en: "the trajectory opacity/blur sliders stop reaching the trajectory page",
506
+ zh: "轨迹页的透明度与模糊滑块失效"
507
+ },
508
+ target: "[data-conversation-composer-overlay]",
509
+ checks: [{
510
+ kind: "selector",
511
+ selector: "[data-conversation-composer-overlay]",
512
+ optionalWhen: "trajectoryNotMounted"
513
+ }],
514
+ sources: [{
515
+ path: "dsh-client-ui-trajectory/lib/client.js",
516
+ literal: "data-conversation-composer-overlay"
517
+ }],
518
+ usedBy: "src/client/wallpaper.ts"
519
+ },
520
+ {
521
+ id: "markdown.wideTable",
522
+ group: "region",
523
+ kind: "host-style",
524
+ criticality: "label",
525
+ since: "<=0.1.6",
526
+ label: {
527
+ en: "Wide markdown table clamp",
528
+ zh: "宽表格收拢样式"
529
+ },
530
+ symptom: {
531
+ en: "a wide table bleeds outside the conversation card instead of scrolling inside it",
532
+ zh: "宽表格会溢出到对话卡片外,而不是在卡片内横向滚动"
533
+ },
534
+ target: ".md-table-wide",
535
+ checks: [{
536
+ kind: "rule",
537
+ match: ".md-table-wide",
538
+ hint: "markdown table class"
539
+ }],
540
+ sources: [{
541
+ path: "dsh-client-ui-chat/lib/client.js",
542
+ literal: "md-table-wide"
543
+ }],
544
+ usedBy: "src/client/wallpaper.ts"
545
+ },
546
+ {
547
+ id: "token.bgBase",
548
+ group: "token",
549
+ kind: "host-token",
550
+ criticality: "looks",
551
+ since: "<=0.1.6",
552
+ label: {
553
+ en: "Main background surface",
554
+ zh: "主背景表面"
555
+ },
556
+ symptom: {
557
+ en: "the main-background slider and the right-panel card lose their surface",
558
+ zh: "主背景透明度与右侧栏卡片失去表面色"
559
+ },
560
+ target: "--dsw-alias-bg-base",
561
+ checks: [{
562
+ kind: "computed",
563
+ token: "--dsw-alias-bg-base"
564
+ }],
565
+ sources: [{
566
+ path: "dsh-client-ui-theme/lib/client.js",
567
+ literal: "--dsw-alias-bg-base"
568
+ }],
569
+ usedBy: "src/client/wallpaper.ts"
570
+ },
571
+ {
572
+ id: "token.layers",
573
+ group: "token",
574
+ kind: "host-token",
575
+ criticality: "looks",
576
+ since: "<=0.1.6",
577
+ label: {
578
+ en: "Layered surfaces (cards)",
579
+ zh: "层次表面(卡片)"
580
+ },
581
+ symptom: {
582
+ en: "the card slider, the conversation card and the trajectory slider go blank",
583
+ zh: "卡片滑块、对话卡片与轨迹滑块失去颜色"
584
+ },
585
+ target: "--dsw-alias-bg-layer-1/2/3",
586
+ checks: [
587
+ {
588
+ kind: "computed",
589
+ token: "--dsw-alias-bg-layer-1"
590
+ },
591
+ {
592
+ kind: "computed",
593
+ token: "--dsw-alias-bg-layer-2"
594
+ },
595
+ {
596
+ kind: "computed",
597
+ token: "--dsw-alias-bg-layer-3"
598
+ }
599
+ ],
600
+ sources: [{
601
+ path: "dsh-client-ui-theme/lib/client.js",
602
+ literal: "--dsw-alias-bg-layer-1"
603
+ }],
604
+ usedBy: "src/client/wallpaper.ts"
605
+ },
606
+ {
607
+ id: "token.menuSurface",
608
+ group: "token",
609
+ kind: "host-token",
610
+ criticality: "core",
611
+ since: "0.1.7",
612
+ label: {
613
+ en: "Menu material fill (0.1.7 split)",
614
+ zh: "菜单材质填充(0.1.7 拆分)"
615
+ },
616
+ symptom: {
617
+ en: "the menu panel and its sticky group headings take two different fills — the solid band across the model picker",
618
+ zh: "菜单面板与吸顶分组标题用了两种填充 —— 模型选择器上那道白道"
619
+ },
620
+ target: "--dsw-menu-surface-fill",
621
+ checks: [{
622
+ kind: "rule",
623
+ match: "--dsw-menu-surface-fill",
624
+ hint: "MenuSurface material fill"
625
+ }, {
626
+ kind: "computed",
627
+ token: "--dsw-menu-surface-fill",
628
+ on: ".dab-root"
629
+ }],
630
+ sources: [{
631
+ path: "dsh-client-ui-theme/lib/client.js",
632
+ literal: "--dsw-menu-surface-fill"
633
+ }, {
634
+ path: "dsh-client-ui-primitives/lib/MenuSurface.module.css",
635
+ literal: "var(--dsw-menu-surface-fill)"
636
+ }],
637
+ usedBy: "src/client/utils/color.ts, src/client/wallpaper.ts"
638
+ },
639
+ {
640
+ id: "token.menuAlias",
641
+ group: "token",
642
+ kind: "host-token",
643
+ criticality: "looks",
644
+ since: "<=0.1.6",
645
+ label: {
646
+ en: "Menu overlay fill",
647
+ zh: "菜单浮层填充"
648
+ },
649
+ symptom: {
650
+ en: "dropdowns, the slash menu and popovers keep the host fill while the panel follows the plugin",
651
+ zh: "下拉、斜杠菜单与浮层保持宿主填充,而面板跟随插件,两边不一致"
652
+ },
653
+ target: "--dsw-specific-menu",
654
+ checks: [{
655
+ kind: "rule",
656
+ match: "--dsw-specific-menu",
657
+ hint: "menu overlay fill"
658
+ }, {
659
+ kind: "computed",
660
+ token: "--dsw-specific-menu",
661
+ on: ".dab-root"
662
+ }],
663
+ sources: [{
664
+ path: "dsh-client-ui-theme/lib/client.js",
665
+ literal: "--dsw-specific-menu"
666
+ }, {
667
+ path: "dsh-client-ui-chat/lib/client.js",
668
+ literal: "--dsw-specific-menu"
669
+ }],
670
+ usedBy: "src/client/utils/color.ts, src/client/wallpaper.ts"
671
+ },
672
+ {
673
+ id: "token.sidebarFill",
674
+ group: "token",
675
+ kind: "host-token",
676
+ criticality: "looks",
677
+ since: "<=0.1.6",
678
+ label: {
679
+ en: "Sidebar fill",
680
+ zh: "左侧栏填充"
681
+ },
682
+ symptom: {
683
+ en: "the sidebar opacity slider stops tinting the sidebar",
684
+ zh: "左侧栏透明度滑块失效"
685
+ },
686
+ target: "--dsw-specific-sidebar-fill",
687
+ checks: [{
688
+ kind: "computed",
689
+ token: "--dsw-specific-sidebar-fill"
690
+ }],
691
+ sources: [{
692
+ path: "dsh-client-ui-theme/lib/client.js",
693
+ literal: "--dsw-specific-sidebar-fill"
694
+ }],
695
+ usedBy: "src/client/utils/color.ts, src/client/wallpaper.ts"
696
+ },
697
+ {
698
+ id: "token.inputFill",
699
+ group: "token",
700
+ kind: "host-token",
701
+ criticality: "looks",
702
+ since: "<=0.1.6",
703
+ label: {
704
+ en: "Input/control surface",
705
+ zh: "输入与控件表面"
706
+ },
707
+ symptom: {
708
+ en: "the input slider stops tinting the composer and the controls",
709
+ zh: "输入区透明度滑块失效"
710
+ },
711
+ target: "--dsw-specific-input-major",
712
+ checks: [{
713
+ kind: "computed",
714
+ token: "--dsw-specific-input-major"
715
+ }],
716
+ sources: [{
717
+ path: "dsh-client-ui-theme/lib/client.js",
718
+ literal: "--dsw-specific-input-major"
719
+ }],
720
+ usedBy: "src/client/utils/color.ts, src/client/wallpaper.ts"
721
+ },
722
+ {
723
+ id: "token.schemeFlag",
724
+ group: "token",
725
+ kind: "host-attr",
726
+ criticality: "core",
727
+ since: "<=0.1.6",
728
+ label: {
729
+ en: "Dark-scheme flag on <body>",
730
+ zh: "<body> 上的深色标记"
731
+ },
732
+ symptom: {
733
+ en: "a dark rule can no longer force the dark palette — light surfaces flash through",
734
+ zh: "深色规则无法强制深色调色板,会闪出浅色表面"
735
+ },
736
+ target: "body[data-ds-dark-theme]",
737
+ checks: [{
738
+ kind: "rule",
739
+ match: "data-ds-dark-theme",
740
+ hint: "dark scheme attribute"
741
+ }],
742
+ sources: [{
743
+ path: "dsh-client-ui-theme/lib/client.js",
744
+ literal: "data-ds-dark-theme"
745
+ }, {
746
+ path: ".",
747
+ literal: "data-ds-dark-theme",
748
+ root: "frontend"
749
+ }],
750
+ usedBy: "src/client/wallpaper.ts"
751
+ },
752
+ {
753
+ id: "token.labelCaption",
754
+ group: "token",
755
+ kind: "host-token",
756
+ criticality: "label",
757
+ since: "<=0.1.6",
758
+ label: {
759
+ en: "Caption label token",
760
+ zh: "弱化文字令牌"
761
+ },
762
+ symptom: {
763
+ en: "placeholder text falls back to a hard-coded grey instead of the theme's caption colour",
764
+ zh: "占位文字退回固定灰色,不再跟随主题的弱化色"
765
+ },
766
+ target: "--dsw-alias-label-caption",
767
+ checks: [{
768
+ kind: "computed",
769
+ token: "--dsw-alias-label-caption"
770
+ }],
771
+ sources: [{
772
+ path: "dsh-client-ui-theme/lib/client.js",
773
+ literal: "--dsw-alias-label-caption"
774
+ }],
775
+ usedBy: "src/client/wallpaper.ts"
776
+ }
777
+ ];
778
+ /** The plugin's own copy of the host floor — kept next to the contracts it guards. */
779
+ const DSH_FLOOR = "0.1.7-rc.2";
780
+ /** Localized label lookup for a contract. */
781
+ function labelOf(c, lang) {
782
+ return lang === "zh" ? c.label.zh : c.label.en;
783
+ }
784
+ /** Localized symptom lookup for a contract. */
785
+ function symptomOf(c, lang) {
786
+ return lang === "zh" ? c.symptom.zh : c.symptom.en;
787
+ }
788
+ /** Groups the panel renders, in order. */
789
+ const CONTRACT_GROUPS = [
790
+ {
791
+ id: "model",
792
+ label: {
793
+ en: "Model → rule resolution",
794
+ zh: "模型 → 规则匹配"
795
+ }
796
+ },
797
+ {
798
+ id: "surface",
799
+ label: {
800
+ en: "Surfaces the plugin paints",
801
+ zh: "插件着色的界面区域"
802
+ }
803
+ },
804
+ {
805
+ id: "token",
806
+ label: {
807
+ en: "Design tokens re-emitted",
808
+ zh: "被重新发出的设计令牌"
809
+ }
810
+ },
811
+ {
812
+ id: "region",
813
+ label: {
814
+ en: "Conversation views",
815
+ zh: "对话视图"
816
+ }
817
+ }
818
+ ];
819
+ /** Overall verdict of a report, for the panel's headline. */
820
+ function verdictOf(report) {
821
+ if (report.results.length === 0) return "unknown";
822
+ if (report.summary.fail > 0) return report.summary.pass > 0 ? "partial" : "broken";
823
+ return report.summary.pass > 0 ? "ok" : "unknown";
824
+ }
825
+ /** Markdown rendering of a report — the thing a user pastes into an issue. */
826
+ function reportToMarkdown(report, lang) {
827
+ const zh = lang === "zh";
828
+ const lines = [];
829
+ lines.push(zh ? "## dsh-background-by-model 宿主自检" : "## dsh-background-by-model host check");
830
+ lines.push("");
831
+ lines.push(`- ${zh ? "阶段" : "phase"}: ${report.phase}`);
832
+ if (report.plugin.version !== "") lines.push(`- ${zh ? "插件" : "plugin"}: ${report.plugin.version} (dsh >=${report.plugin.floor})`);
833
+ lines.push(`- dsh: ${report.host.version !== "" ? report.host.version : zh ? "未读到" : "unreadable"}${report.host.compatible === false ? zh ? "(低于插件下限)" : " (below the plugin floor)" : ""}`);
834
+ if (report.host.node !== "") lines.push(`- node: ${report.host.node}`);
835
+ if (report.host.root !== "") lines.push(`- host root: ${report.host.root}`);
836
+ if (report.host.note !== "") lines.push(`- note: ${report.host.note}`);
837
+ if (report.host.model !== void 0) {
838
+ const m = report.host.model;
839
+ lines.push(`- ${zh ? "模型" : "model"}: \`${m.text !== "" ? m.text : "(none)"}\` · ${m.source}${m.note !== "" ? ` · ${m.note}` : ""} · ${zh ? "规则" : "rule"} ${m.rule !== "" ? m.rule : "-"}${m.matched ? "" : zh ? "(兜底)" : " (fallback)"}`);
840
+ }
841
+ lines.push("");
842
+ lines.push(`### ${zh ? "结论" : "Summary"}: ${report.summary.pass} pass / ${report.summary.fail} fail / ${report.summary.skip} n/a`);
843
+ lines.push("");
844
+ for (const r of report.results) {
845
+ const mark = r.status === "pass" ? "PASS" : r.status === "fail" ? "**FAIL**" : r.status === "skip" ? "n/a" : "info";
846
+ lines.push(`- ${mark} \`${r.id}\` — ${r.label} · \`${r.target}\``);
847
+ if (r.status === "fail") {
848
+ lines.push(` - ${zh ? "现象" : "symptom"}: ${r.symptom}`);
849
+ lines.push(` - ${zh ? "原因" : "reason"}: ${r.reason ?? r.detail}`);
850
+ lines.push(` - ${zh ? "期望位置" : "expected in"}: ${r.sources.map((s) => s.path).join(", ")}`);
851
+ } else if (r.detail !== "") lines.push(` - ${r.detail}`);
852
+ }
853
+ return lines.join("\n");
854
+ }
855
+ //#endregion
856
+ //#region src/host-scan.ts
857
+ /**
858
+ * The node half of the host self-check: what the INSTALLED dsh looks like on disk.
859
+ *
860
+ * The browser probe (../client/judge.ts) answers "does the running host still
861
+ * provide this", which is the question a user needs answered. This module answers
862
+ * the complementary one — "does the copy of dsh on this machine still contain the
863
+ * thing we depend on" — and it can do it with no browser, no DOM and no session,
864
+ * which makes it the half that runs in CI:
865
+ *
866
+ * node .dsh-debug/contract-scan.mjs
867
+ *
868
+ * For every declaration in ../host-contracts it
869
+ * 1. reports whether the owning host file exists at all (a package that was
870
+ * renamed or dropped answers here before anything reaches the screen), and
871
+ * 2. reports whether that file still contains the literal the plugin depends
872
+ * on — the check that turns a silent 0.1.8 rename into a red line.
873
+ *
874
+ * It also reads the host's own version from the app-boot manifest, so the panel
875
+ * can show it and can say plainly when the runtime is older than this plugin's
876
+ * declared floor.
877
+ */
878
+ const exists = async (p) => {
879
+ try {
880
+ await access(p);
881
+ return true;
882
+ } catch {
883
+ return false;
884
+ }
885
+ };
886
+ /** Read a package's manifest version. */
887
+ async function versionOf(pkgDir) {
888
+ try {
889
+ const raw = JSON.parse(await readFile(join(pkgDir, "package.json"), "utf8"));
890
+ return typeof raw.version === "string" ? raw.version : "";
891
+ } catch {
892
+ return "";
893
+ }
894
+ }
895
+ /** Trim a `semver` build/prerelease down to the release triple for the floor test. */
896
+ function releaseTriple(v) {
897
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
898
+ if (m === null) return null;
899
+ return [
900
+ Number(m[1]),
901
+ Number(m[2]),
902
+ Number(m[3])
903
+ ];
904
+ }
905
+ /**
906
+ * Is `version` at or above `floor`?
907
+ *
908
+ * Deliberately coarse: this is the panel's advisory "your dsh is older than what
909
+ * this plugin asks for" hint. The host's own gate is `peerDependencies` evaluated
910
+ * with `semver.satisfies(..., { includePrerelease: true })` — a prerelease pair
911
+ * like the floor itself compares equal here rather than being judged by semver's
912
+ * prerelease ordering rules, which would call `0.1.7-rc.2 >= 0.1.7-rc.2` true but
913
+ * `0.1.7-rc.10` below `0.1.7-rc.2` (string vs numeric ordering) and produce a
914
+ * baffling red line.
915
+ */
916
+ function meetsFloor(version, floor) {
917
+ const a = releaseTriple(version);
918
+ const b = releaseTriple(floor);
919
+ if (a === null || b === null) return null;
920
+ for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
921
+ return true;
922
+ }
923
+ /** The candidate module roots, nearest first. */
924
+ function candidateBases() {
925
+ const bases = [];
926
+ if (process.env.DAB_HOST_ROOT !== void 0 && process.env.DAB_HOST_ROOT !== "") bases.push(resolve(process.env.DAB_HOST_ROOT));
927
+ const argv1 = process.argv[1];
928
+ if (typeof argv1 === "string" && argv1 !== "") {
929
+ bases.push(dirname(resolve(argv1)));
930
+ bases.push(resolve(dirname(resolve(argv1)), ".."));
931
+ }
932
+ return bases;
933
+ }
934
+ /**
935
+ * Locate the installed host packages.
936
+ *
937
+ * Tried in order: an explicit `DAB_HOST_ROOT`, module resolution from the running
938
+ * dsh process's entry script, then a walk up from this plugin's own directory (the
939
+ * plugin sits inside `<profile>/node_modules`, whose parent dsh installation is
940
+ * the one that loaded it). Every attempt is recorded so a miss can be reported
941
+ * instead of guessed at.
942
+ */
943
+ async function resolveHostRoots(pluginDir) {
944
+ const tried = [];
945
+ const check = async (base) => {
946
+ tried.push(base);
947
+ return await exists(join(base, "@deepseek-ai", "dsh-app-boot", "package.json"));
948
+ };
949
+ for (const base of candidateBases()) for (const candidate of [
950
+ base,
951
+ join(base, "node_modules"),
952
+ join(base, "..", "node_modules")
953
+ ]) {
954
+ const normalized = resolve(candidate);
955
+ if (await check(normalized)) return {
956
+ roots: rootOf(normalized),
957
+ note: "",
958
+ tried
959
+ };
960
+ }
961
+ for (const from of [join(pluginDir, "lib", "index.js"), join(pluginDir, "src", "index.ts")]) try {
962
+ const manifest = createRequire(from).resolve("@deepseek-ai/dsh/package.json");
963
+ const base = dirname(dirname(manifest));
964
+ if (await check(base)) return {
965
+ roots: rootOf(base),
966
+ note: "",
967
+ tried
968
+ };
969
+ } catch {}
970
+ let dir = resolve(pluginDir);
971
+ for (let depth = 0; depth < 8; depth++) {
972
+ const base = join(dir, "node_modules");
973
+ if (await check(resolve(base))) return {
974
+ roots: rootOf(resolve(base)),
975
+ note: "",
976
+ tried
977
+ };
978
+ const parent = resolve(dir, "..");
979
+ if (parent === dir) break;
980
+ dir = parent;
981
+ }
982
+ return {
983
+ roots: null,
984
+ note: "the installed dsh packages were not found from this plugin; set DAB_HOST_ROOT to the node_modules directory that holds @deepseek-ai/dsh-app-boot",
985
+ tried
986
+ };
987
+ }
988
+ /** Derive every root from a validated `<...>/node_modules` base. */
989
+ function rootOf(base) {
990
+ const packages = join(base, "@deepseek-ai");
991
+ return {
992
+ packages,
993
+ frontend: join(packages, "dsh-web-frontend", "dist", "assets"),
994
+ dsh: join(packages, "dsh")
995
+ };
996
+ }
997
+ /**
998
+ * Search a directory (not recursively) for a literal.
999
+ *
1000
+ * Used by sources whose `path` is `.`: the frontend's asset filenames carry
1001
+ * content hashes, so the check is "is this literal still served", not "is this
1002
+ * exact file still there". Text-ish files only — a literal cannot appear in a
1003
+ * font or an image, and reading 20 of them would only slow the probe down.
1004
+ */
1005
+ async function directoryMentions(dir, literal) {
1006
+ let names;
1007
+ try {
1008
+ names = await readdir(dir);
1009
+ } catch {
1010
+ return {
1011
+ ok: false,
1012
+ detail: "directory missing"
1013
+ };
1014
+ }
1015
+ const searched = [];
1016
+ for (const name of names) {
1017
+ if (!/\.(?:js|mjs|cjs|css|html|json)$/i.test(name)) continue;
1018
+ searched.push(name);
1019
+ try {
1020
+ if ((await readFile(join(dir, name), "utf8")).includes(literal)) return {
1021
+ ok: true,
1022
+ detail: `found in ${name}`
1023
+ };
1024
+ } catch {}
1025
+ }
1026
+ return {
1027
+ ok: false,
1028
+ detail: searched.length === 0 ? "no script or stylesheet in the directory" : `not in any of ${searched.length} file(s)`
1029
+ };
1030
+ }
1031
+ async function checkSource(source, roots, pluginDir) {
1032
+ const root = source.root === "frontend" ? roots.frontend : roots.packages;
1033
+ if (source.path === ".") {
1034
+ const { ok, detail } = await directoryMentions(root, source.literal);
1035
+ return {
1036
+ path: `${root} (*)`,
1037
+ literal: source.literal,
1038
+ status: ok ? "pass" : "fail",
1039
+ detail
1040
+ };
1041
+ }
1042
+ const file = source.path.startsWith("src/") ? join(pluginDir, source.path) : join(root, source.path);
1043
+ if (!await exists(file)) return {
1044
+ path: source.path,
1045
+ literal: source.literal,
1046
+ status: "fail",
1047
+ detail: "file missing"
1048
+ };
1049
+ let text;
1050
+ try {
1051
+ text = await readFile(file, "utf8");
1052
+ } catch (e) {
1053
+ return {
1054
+ path: source.path,
1055
+ literal: source.literal,
1056
+ status: "fail",
1057
+ detail: `unreadable: ${e instanceof Error ? e.message : String(e)}`
1058
+ };
1059
+ }
1060
+ return text.includes(source.literal) ? {
1061
+ path: source.path,
1062
+ literal: source.literal,
1063
+ status: "pass",
1064
+ detail: "literal present"
1065
+ } : {
1066
+ path: source.path,
1067
+ literal: source.literal,
1068
+ status: "fail",
1069
+ detail: `literal ${JSON.stringify(source.literal)} no longer present`
1070
+ };
1071
+ }
1072
+ /**
1073
+ * Scan the installed host against every contract declaration.
1074
+ *
1075
+ * `lang` only selects the label/symptom language of the returned rows.
1076
+ */
1077
+ async function checkHostOnDisk(pluginDir, lang) {
1078
+ const resolved = await resolveHostRoots(pluginDir);
1079
+ const roots = resolved.roots;
1080
+ const version = roots === null ? "" : await versionOf(roots.dsh);
1081
+ const compatible = version === "" ? null : meetsFloor(version, DSH_FLOOR);
1082
+ const results = [];
1083
+ for (const c of HOST_CONTRACTS) {
1084
+ const base = {
1085
+ id: c.id,
1086
+ label: labelOf(c, lang),
1087
+ symptom: symptomOf(c, lang),
1088
+ target: c.target,
1089
+ sources: c.sources,
1090
+ usedBy: c.usedBy,
1091
+ checks: []
1092
+ };
1093
+ if (roots === null) {
1094
+ results.push({
1095
+ ...base,
1096
+ status: "skip",
1097
+ detail: resolved.note
1098
+ });
1099
+ continue;
1100
+ }
1101
+ const verdicts = [];
1102
+ for (const source of c.sources) verdicts.push(await checkSource(source, roots, pluginDir));
1103
+ const failed = verdicts.find((v) => v.status === "fail");
1104
+ base.checks = verdicts.map((v) => `${v.status} · ${v.path} — ${v.detail}`);
1105
+ if (failed === void 0) {
1106
+ results.push({
1107
+ ...base,
1108
+ status: "pass",
1109
+ detail: `${verdicts.length} file(s) checked`
1110
+ });
1111
+ continue;
1112
+ }
1113
+ results.push({
1114
+ ...base,
1115
+ status: "fail",
1116
+ detail: `${failed.path} — ${failed.detail}`,
1117
+ reason: lang === "zh" ? `宿主文件里已经找不到 ${failed.literal}(${failed.path})` : `the host no longer contains ${failed.literal} (${failed.path})`
1118
+ });
1119
+ }
1120
+ const pass = results.filter((r) => r.status === "pass").length;
1121
+ const fail = results.filter((r) => r.status === "fail").length;
1122
+ const skip = results.filter((r) => r.status === "skip").length;
1123
+ return {
1124
+ phase: "node",
1125
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1126
+ plugin: {
1127
+ version: "",
1128
+ floor: DSH_FLOOR
1129
+ },
1130
+ host: {
1131
+ version,
1132
+ compatible,
1133
+ node: process.version,
1134
+ root: roots?.dsh ?? "",
1135
+ note: resolved.note,
1136
+ model: void 0
1137
+ },
1138
+ results,
1139
+ summary: {
1140
+ pass,
1141
+ fail,
1142
+ skip,
1143
+ total: results.length
1144
+ }
1145
+ };
1146
+ }
1147
+ /** Absolute directory of this plugin — the anchor both halves resolve the host from. */
1148
+ function thisPluginDir(importMetaUrl) {
1149
+ return resolve(dirname(fileURLToPath(importMetaUrl)), "..");
1150
+ }
1151
+ /** Every host package directory found under a root (used by the scan's summary). */
1152
+ async function listHostPackages(roots) {
1153
+ try {
1154
+ return (await readdir(roots.packages, { withFileTypes: true })).filter((e) => e.isDirectory() && e.name.startsWith("dsh")).map((e) => e.name).sort();
1155
+ } catch {
1156
+ return [];
1157
+ }
1158
+ }
1159
+ //#endregion
1160
+ export { thisPluginDir as a, DSH_FLOOR as c, reportToMarkdown as d, symptomOf as f, resolveHostRoots as i, HOST_CONTRACTS as l, listHostPackages as n, versionOf as o, verdictOf as p, meetsFloor as r, CONTRACT_GROUPS as s, checkHostOnDisk as t, labelOf as u };