dsh-composer-ux 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1264 -1088
- package/README.en.md +231 -0
- package/README.md +201 -4
- package/README.simple.md +103 -0
- package/lib/client.js +929 -93
- package/lib/client.js.map +4 -4
- package/lib/index.js +1236 -148
- package/lib/index.js.map +4 -4
- package/package.json +22 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,1088 +1,1264 @@
|
|
|
1
|
-
# 更新日志
|
|
2
|
-
|
|
3
|
-
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
|
-
|
|
5
|
-
## [0.
|
|
6
|
-
|
|
7
|
-
>
|
|
8
|
-
>
|
|
9
|
-
>
|
|
10
|
-
>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
`
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
`
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
`
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
-
|
|
237
|
-
|
|
238
|
-
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
- `
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
`
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
`
|
|
366
|
-
`
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
>
|
|
395
|
-
>
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
`
|
|
489
|
-
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
`
|
|
502
|
-
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
与
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
`
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
`
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
- `
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
`
|
|
799
|
-
-
|
|
800
|
-
|
|
801
|
-
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
-
|
|
877
|
-
- `
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
###
|
|
889
|
-
|
|
890
|
-
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
-
|
|
924
|
-
-
|
|
925
|
-
-
|
|
926
|
-
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
- `
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
-
|
|
1011
|
-
-
|
|
1012
|
-
-
|
|
1013
|
-
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
-
|
|
1018
|
-
-
|
|
1019
|
-
-
|
|
1020
|
-
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
-
|
|
1026
|
-
-
|
|
1027
|
-
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
- `
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
-
|
|
1036
|
-
-
|
|
1037
|
-
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
-
|
|
1057
|
-
-
|
|
1058
|
-
|
|
1059
|
-
## [0.
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
-
|
|
1070
|
-
-
|
|
1071
|
-
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
-
|
|
1075
|
-
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
-
|
|
1088
|
-
-
|
|
1
|
+
# 更新日志
|
|
2
|
+
|
|
3
|
+
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
|
+
|
|
5
|
+
## [0.13.0] — 2026-09-30(结果框重启恢复 + 逐轮元数据台账 + 只读查证工具 + 发版门禁三脚本 + 证据登记表)
|
|
6
|
+
|
|
7
|
+
> 起因:用户拿社区插件 [WestFox-AwA/dsh-prompt-optimizer](https://github.com/WestFox-AwA/dsh-prompt-optimizer)
|
|
8
|
+
> (0.7.8 主线)与上一轮对比的结论,逐条确认"建议做的全要做,只读工具查证也做"。
|
|
9
|
+
> 这一版的一句话取舍:**把"看不见的机制"补成"看得见、可回放、可核对"** —— 结果框跨重启还在、
|
|
10
|
+
> 每轮留下一行只含数字的台账、解释层能只读查证、发版前三条门禁自己会红,外加一张诚实的能力登记表。
|
|
11
|
+
|
|
12
|
+
### 新增
|
|
13
|
+
|
|
14
|
+
- **结果框持久化 + 重启恢复**(①):写进 `$DSH_HOME/composer-ux/optimize-dock.json`,下次启动/刷新恢复。
|
|
15
|
+
原子写(临时文件 → rename)、**损坏就改名成 `<名>.corrupt-<时间戳>.json` 留证据**(那份文件里可能是你唯一一份成品)、
|
|
16
|
+
超 64 KB **拒写而不是截半**;信封带版本号,**版本不认识就不解析也不动文件**。读回来一律先净化:
|
|
17
|
+
认不出就当"没有可恢复的结果",宁可空着也不拿脏数据糊界面;**重启前没跑完的那一轮恢复成「已取消」**,
|
|
18
|
+
不假装还在跑(秒表也不走)。新增第七条 exact 路由 `GET/POST /composer-ux/optimize-state`(先过官方信任关卡,
|
|
19
|
+
只读写固定路径那一份)。设置页加「重启后保留结果框」(默认开)与「清空结果框状态」。
|
|
20
|
+
- **逐轮优化台账 + `recap`**(②):每轮往 `$DSH_HOME/composer-ux/optimize-log.jsonl` 追加**只含元数据**的一条
|
|
21
|
+
(档位、草稿字数、上下文规模、条目数、丢弃条数与原因、是否重试/整段照收、工具轮次、耗时、路由、成败)。
|
|
22
|
+
**一个字的原文都不写** —— 模块类型上就没有承载它的字段。超 512 KB 从最旧的行开始丢并留 `rotated` 标记;
|
|
23
|
+
坏行逐行跳过并计数;写失败**绝不抛错**(旁路失败不能拖垮一次已经跑完的调用)。
|
|
24
|
+
新增 `scripts/recap.mjs`:`--last N` / `--session <前缀>` / `--json` / `--clear`。
|
|
25
|
+
- **只读查证工具 `read` / `glob` / `grep`**(⑤,**默认关**):放行条件是开关开 ∧ 拿得到本会话工作目录
|
|
26
|
+
(拿不到就一个工具都不派);围栏是 `realpath` 之后比前缀(符号链接也逃不出去),越界拒绝并记账;
|
|
27
|
+
只有读调用;单文件 200KB / 结果 4000 字 / 遍历 400 文件 / glob 50 命中 / grep 40 命中 / 深度 6 /
|
|
28
|
+
每轮 ≤3 次 / 总 ≤3 轮 / 总 20 秒。**工具内容不能当引文**(`quote` 仍须出自用户原话)。
|
|
29
|
+
- **发版门禁三脚本**(③):`check-tag-target.mjs`(tag 名里的版本 == 该 tag 所指提交里的 `package.json` 版本)、
|
|
30
|
+
`check-pack.mjs`(`npm pack` 文件清单 == `files` 白名单且不夹带 `src/ test/ scripts/ docs/`)、
|
|
31
|
+
`check-docs.mjs`(README 的套件数/条数/变异数 == **最近一次真实运行**的记录;引用的截图本地都在)。
|
|
32
|
+
`npm test` 与 `npm run test:mutations` 改走包装器,把"多少套件/多少条/多少变异"记成可机读事实。
|
|
33
|
+
- **证据登记表**(④):README 中英两版新增「已验证 / 未验证」两列。
|
|
34
|
+
- **设置页两个新开关**:「记录每轮优化台账」(默认开,只写数字)、「优化时允许只读查证项目文件」(默认关)。
|
|
35
|
+
|
|
36
|
+
### 修正
|
|
37
|
+
|
|
38
|
+
- **台账的原因模板里嵌着模型给的引文**(`引文不是原话里的逐字片段:「…」`,最多 40 字)—— 而引文很可能
|
|
39
|
+
就是用户原话的变体,直接落盘等于把用户的文字写进磁盘。现在写台账前**消毒**:刮掉所有成对引号/括号里的内容,
|
|
40
|
+
只留机器前缀(失败原因同样过一遍)。台账仍能回答"为什么丢",但一个字的用户内容都带不出去。**变异 DH 盯着**。
|
|
41
|
+
- **工具路径的接受判据原本只看装配层的 `ok`**,而装配层对非 JSON 有"整段照收"兜底(`fallback: true`、`itemCount: 0`)
|
|
42
|
+
⇒ 模型查完文件写的散文会被当成成品,**"查证后回落"永远不会发生**(上游真机翻过同一条路:打开只读工具就必定出散文)。
|
|
43
|
+
改成"**真的解析出条目**"之后回落才真的走通:换掉系统提示、如实标注 `toolFallback`、保留真实轮次。**变异 DY 盯着**。
|
|
44
|
+
- **macOS 上根目录不归一**(`os.tmpdir()` 给的 `/var/folders/...` 真实路径是 `/private/var/folders/...`):
|
|
45
|
+
围栏内部 realpath 出来的绝对路径与调用方给的根前缀不同 ⇒ 相对路径被切坏(实测切成 `【e.txt】`)、
|
|
46
|
+
`startsWith` 比对假失败。**CI 的 macOS 那一格红出来的**(ubuntu/windows 全绿),修法是工具入口先把根归一;
|
|
47
|
+
并补了一条"根目录本身是符号链接"的常驻护栏 + 变异 DZ。
|
|
48
|
+
- **Windows 上路径分隔符不归一**:`path` 给的是反斜杠,而模型写的模式与文档都用 `/` ⇒ `**/*.ts` 永远匹配不到、
|
|
49
|
+
`grep` 的"路径:行号"断言也红。现在对外(模式匹配 + 工具结果)一律归一成 `/`。
|
|
50
|
+
|
|
51
|
+
### 内部
|
|
52
|
+
|
|
53
|
+
- 测试从 **18 套件 1943 条**涨到 **21 套件 2160 条**;变异从 **109 条**涨到 **128 条,全部咬住**。
|
|
54
|
+
- 新套件:`test/optimize-ledger.mjs`(50)、`test/optimize-state.mjs`(69)、`test/optimize-tools.mjs`(66)。
|
|
55
|
+
- 宿主协议套件新增 5e(台账 17 条)与 5g(只读工具 13 条)两节;"新增 exact 路由必须登记"的关卡同步 6 → 7。
|
|
56
|
+
- 两处如实说明:**原子写的原子性本身没有变异证明**(单线程不可观测);**只读工具的价值(省事/省钱)没有数据**。
|
|
57
|
+
- 计划与设计写死在 `docs/PLAN-0.13.0.md`(含隐私边界与验收判据),便于后续核对。
|
|
58
|
+
|
|
59
|
+
## [0.12.0] — 2026-09-29(面板内逐条流式结果框 + 会话上下文/记忆链 + 提示词优化只留一个入口;并入原 0.11.1 的安全项、typecheck 与三平台 CI)
|
|
60
|
+
|
|
61
|
+
> 起因:用户拿社区插件 [Y1X1n/dsh-prompt-optimizer](https://github.com/Y1X1n/dsh-prompt-optimizer)(0.3.17)
|
|
62
|
+
> 的「提示词优化」与本插件的同名功能对比,问"这个多了哪些东西"。逐条读完两边源码后的结论是:
|
|
63
|
+
> 对方多的是"过程可见 + 可控"的**外壳**(流式双段落面板、10 项设置、会话上下文与记忆链、三平台 CI、
|
|
64
|
+
> 三语文档与推广资产),本插件多的是"机制层保真"(模型只能产出**附逐字引文**的条目、宿主逐条字面
|
|
65
|
+
> 比对、只丢单条、原话原样保留)。
|
|
66
|
+
>
|
|
67
|
+
> **发版节奏改过一次(用户决定)**:原计划分两期 —— 0.11.1 落小项与安全项,0.12.0 做面板内流式结果框、
|
|
68
|
+
> 会话上下文与记忆链、工程面。0.11.1 的实现、测试与试跑都已完成,但用户看到实机后否掉了其中的
|
|
69
|
+
> 「工具行独立 ✨ 优化 按钮」,并要求**放弃 0.11.1 这一期、全部并进 0.12.0**。所以本版 =
|
|
70
|
+
> 原 0.11.1 的全部内容(撤下那枚按钮)+ 0.12.0 的新增 + 工程面;**npm 上不发布 0.11.1**。
|
|
71
|
+
|
|
72
|
+
### 0.12.0 新增
|
|
73
|
+
|
|
74
|
+
- **面板内单轮结果框**(`client/OptimizeDock.tsx` + 纯函数状态机 `client/optimize-dock.ts`):
|
|
75
|
+
优化结果不再"回来自动覆盖输入框",而是落进面板里的结果框。飞行期间输入框**照常能改**;
|
|
76
|
+
成品在框里**可编辑**;点「插入输入框」才替换原文,飞行期间你改过字就先问一次(`Ctrl+Z` 可退);
|
|
77
|
+
「重新优化」按**原样重跑**(不受输入框现状影响);`Esc` 取消**保留已生成内容**;`✕` 才是丢弃。
|
|
78
|
+
状态机是纯函数:编辑 / 取消 / 丢弃 / 去重 / 两步确认全在 `test/quick-commands.mjs` 里单测。
|
|
79
|
+
- **逐条流式**:宿主「优化」路由改成 **SSE**(只有声明 `Accept: text/event-stream` 的客户端才走流式,
|
|
80
|
+
其他客户端仍旧吃到原来那份一次性 JSON —— 对旧客户端完全惰性)。宿主边收模型输出边扫,
|
|
81
|
+
**只发已经闭合、且通过逐字校验的条目**(`item` / `dropped` 事件),最终成品在 `done` 里给完整文本 ——
|
|
82
|
+
"框里显示的一直是通过校验的东西"这条纪律没有因为流式而破。客户端按 `\n\n` 切帧,
|
|
83
|
+
跨 chunk 的半截尾巴留在 rest 里等下一段。
|
|
84
|
+
- **会话上下文(策略分叉)**:设置页新增「携带会话上下文」(**默认开**)。宿主读当前会话最近 4 轮
|
|
85
|
+
(你 4 条 + 回复 4 条,总 1600 字上限、单条 600 字),用一段固定说明喂给模型:
|
|
86
|
+
**只用于消歧义、不要回应它、上下文不算依据(不能从中取引文)**。于是"那个也改一下"有了指代,
|
|
87
|
+
而"引文必须出自你的草稿"这条判据没有被稀释。
|
|
88
|
+
- **记忆链**:你手改过上一轮成品、再点优化时,宿主把**上一版成品**一并带上(`<上一轮成品>`),
|
|
89
|
+
让模型围绕变化点调整而不是从零重写(已确认过的地方不会被改回去)。三种情况不带:
|
|
90
|
+
同一段原文重跑、草稿就是上一轮成品、上一轮失败或取消。
|
|
91
|
+
- **撤下工具行那枚独立「✨ 优化」按钮**:0.11.1 开发期加过它(点一下 = 开面板 + 立刻开跑),
|
|
92
|
+
用户看到实机后明确否掉 —— "我让你集合到快捷指令里的提示词优化按钮里"。
|
|
93
|
+
0.12.0 删掉它:工具行只剩一枚「快捷指令」,优化仍是面板里的「✨ 优化提示词」,
|
|
94
|
+
秒表读数就在面板主按钮上(**信息一点没少**)。相关测试、护栏与文档一并改齐。
|
|
95
|
+
|
|
96
|
+
### 修正(0.12.0 截图验收抓到)
|
|
97
|
+
|
|
98
|
+
- **结果框主按钮在深色主题下不可见(白底白字)**:`dockButtonPrimary` 原本写的是
|
|
99
|
+
`background: var(--dsw-alias-brand-primary)` + `color: var(--dsw-alias-label-inverse, #fff)`,
|
|
100
|
+
而 **`--dsw-alias-label-inverse` 这个令牌根本不存在**(官方叫 `label-primary-inverted`)⇒ 兜底 `#fff`;
|
|
101
|
+
偏偏深色主题里 `brand-primary` 是**近白**(`--dsw-static-neutral-bluish-50` = rgb(249,250,251)),
|
|
102
|
+
于是结果框那颗「插入输入框」只剩一个白色胶囊、字看不见。**浅色主题下反而正常**(那时 brand-primary 是墨色),
|
|
103
|
+
所以测试、类型检查、浅色截图全都放过了它 —— 这是把界面真看一眼(截图验收)才抓到的。
|
|
104
|
+
修法:改用**配对令牌** `--dsw-alias-button-primary-fill` + `--dsw-alias-label-primary-foreground`
|
|
105
|
+
(与面板里那颗「优化提示词」按钮同一对,也是官方 ui-primitives 的成对写法;同文件 0.2.0 的注释里就写着这条教训)。
|
|
106
|
+
护栏:新增 2.0f 节 3 条 —— 两条**对象级**断言两个主按钮的填色/前景令牌成对,
|
|
107
|
+
一条禁止全仓再出现 `label-inverse` 这个不存在的名字(注释先剥掉,因为文件头那条"当初写错了什么"的记录里故意留着它)。
|
|
108
|
+
|
|
109
|
+
### 工程面(原 0.11.1 计划外,0.12.0 补齐)
|
|
110
|
+
|
|
111
|
+
- **`npm run typecheck`**(`tsconfig.json` + 手写类型面 `types/dsh-externals.d.ts`,实测 **0 错误**)。
|
|
112
|
+
为什么只开 `strictNullChecks` 一条严格项:关着它时**联合类型的判别字段不能 narrowing**
|
|
113
|
+
(`if (!parsed.ok) parsed.code` 编译器看不见,实测确认),开了实测只多 15 个错误、都能改完;
|
|
114
|
+
`strict` 全开会一次性抛出几百个历史错误,那是另一场改造,不该混进这一版。
|
|
115
|
+
- **三平台 CI**(`.github/workflows/ci.yml`):ubuntu / windows / macos × node 20,
|
|
116
|
+
`npm ci` → `typecheck` → `build` → `npm test`(先 build 再 test 是刻意的:测试打的是构建产物)。
|
|
117
|
+
- typecheck 一上就抓到**三个真 bug**:① `client.tsx` 少 import `PRICE_AUTO_SYNC_FIELD` ——
|
|
118
|
+
设置页「恢复默认」一点就是 `ReferenceError`(界面上表现为"点了没反应"),而当时 18 套件 1945 条全绿,
|
|
119
|
+
**没有一条用例走过那条路径**;② `host.ts` 重启路由的 `ok` 写了两遍(前面的永远被展开覆盖);
|
|
120
|
+
③ `quick-store.ts` 的 `ensureQuickBook` 返回类型过宽(把"不可能返回 missing"写进类型)。
|
|
121
|
+
- CI 排查过程又抓到**四个真问题**:① `build.mjs` 把内联用的 vendor 包写死成本机 DSH 检出路径
|
|
122
|
+
(别的机器与三平台全部构建失败;已加 npm 包退路,两条路线产物**逐字节相同**,
|
|
123
|
+
顺手把 esbuild 写进产物的本机路径注释归一掉);② `prefetchClipboard()` 直接读 `navigator.clipboard`
|
|
124
|
+
而 **Node 20 没有全局 `navigator`**(Node 22 才加)→ 三平台测试一起崩;
|
|
125
|
+
③ 两个渲染套件向来**向本机 profile 借 react** ⇒ 它们在 CI 上从来没跑过(已改用本仓 devDependencies);
|
|
126
|
+
④ 测试里的源码护栏用了带换行的字面量 ⇒ windows 的 CRLF 检出必红(已在读取处统一归一)。
|
|
127
|
+
|
|
128
|
+
### 安全(原 0.11.1 的内容,随 0.12.0 一起发;与功能无关,是本插件既有的洞)
|
|
129
|
+
|
|
130
|
+
- **「优化提示词」与「快捷指令存储」两条路由补上官方信任关卡**(`connection.requestRejection`)。
|
|
131
|
+
这两条此前**没挂**这道关,而它们一个会消耗用户的模型额度、一个会把用户的提示词库写盘;
|
|
132
|
+
`host.ts` 里自己写着"每条自己开的路由都要先问",2026-09-29 补金额那批时漏了这两条。
|
|
133
|
+
现在 6 条自开路由**全部**挂上(用量 / 价目同步 / 余额 / 重启 / 优化 / 快捷指令存储),
|
|
134
|
+
且关卡摆在**读请求体之前** —— 被拒的请求连 body 都不读。变异 `CN`/`CO` 守着它。
|
|
135
|
+
|
|
136
|
+
### 原 0.11.1:新增(安全配套的小项)
|
|
137
|
+
|
|
138
|
+
- **秒表与阶段**:面板主按钮显示「优化中…(12s)」,结束时状态行报「用时 3.4 秒」。
|
|
139
|
+
**如实说明边界**:宿主现在一次性返回结果,所以能显示的最细阶段就是「等待模型响应」;
|
|
140
|
+
"正在校验装配"这类更细的阶段要等 0.12.0 的流式接口(届时按条目逐条流式显示,见计划)。
|
|
141
|
+
- **斜杠命令保护**:`/goal 帮我写周报` 只把**命令后面的正文**送去模型,写回时把 `/goal` 原样拼回;
|
|
142
|
+
只有命令没有正文时直接提示、**不发请求**(省一次调用)。`/path/to/file` 这类路径不会被误判成命令。
|
|
143
|
+
两端共用同一份判定(`SLASH_COMMAND_RE` + `splitSlashCommand`,落在设置契约里),
|
|
144
|
+
宿主侧另有一道"纯命令不收"的防御。变异 `CP`/`CQ`/`CT` 守着它。
|
|
145
|
+
- **推理强度钳最低档**:优化是"给一条草稿、让它吐条目"的短任务,不指定档位时推理模型会按自己的
|
|
146
|
+
默认档先空转很久(对方 0.3.17 也把这条列为实测收益)。现在按路由**真实暴露**的档位
|
|
147
|
+
(`llm.resolveModelInfo` → `reasoning.efforts`)选最省的那一档,结果缓存 10 分钟;
|
|
148
|
+
**查不到就什么都不传** —— 绝不乱造一个适配器不认的值。变异 `CR` 守着它。
|
|
149
|
+
|
|
150
|
+
### 原 0.11.1:修正
|
|
151
|
+
|
|
152
|
+
- **写回前比对草稿**:优化是一次往返(真机常是几十秒),此前拿到结果就整体覆盖输入框 ——
|
|
153
|
+
用户在这期间又打了字的话,新内容会被无声吃掉。现在拿到结果先比对一次(只比 trim 后的结果,
|
|
154
|
+
敲个空格不算改动),不一致就**不写回**并在状态行如实说明"再点一次会以当前内容为准"。
|
|
155
|
+
变异 `CS` 守着它。
|
|
156
|
+
- **客户端断连即中止**:关掉页面/切走会话时,宿主此前不会中止这次模型调用(只有 180 秒墙钟超时),
|
|
157
|
+
额度照烧。现在用 `res.on('close')` 挂 AbortController,断连即 abort。
|
|
158
|
+
用 `res` 而不是 `req`:`req` 的 close 在请求体读完就触发,那时客户端还连着(对方注释里记着同一个坑)。
|
|
159
|
+
### 内部
|
|
160
|
+
|
|
161
|
+
- 新增 `src/client/optimize-clock.ts`(面板与结果框共用的秒表 hook)、`src/client/optimize-dock.ts`
|
|
162
|
+
(结果框状态机,纯函数)、`src/client/OptimizeDock.tsx`(结果框视图)、`src/prompt-context.ts`
|
|
163
|
+
(会话上下文裁剪)、`src/optimizer-assemble.ts`(逐字校验 + 流式扫描,批处理与流式共用同一份校验)。
|
|
164
|
+
新增纯函数 `SLASH_COMMAND_RE` / `splitSlashCommand`(`settings-contract.ts`)与
|
|
165
|
+
`sameDraft` / `composeOptimizedDraft` / `elapsedSeconds` / `elapsedText` / `insertDecision` /
|
|
166
|
+
`previousForChain`(`client/quick-commands.ts`、`client/optimize-dock.ts`)—— 全部能在 node 里单测。
|
|
167
|
+
- 文档:新增 `README.en.md`(英文版)与 `README.simple.md`(小白版:三步上手 + 常见问题,讲人话),
|
|
168
|
+
中文全版仍是权威;新增一页推广 PPT(`docs/promo/`,生成脚本与产物一起入库)。
|
|
169
|
+
- 类型与 CI:`tsconfig.json` + `types/dsh-externals.d.ts`(手写类型面)+ `npm run typecheck`(0 错误);
|
|
170
|
+
`.github/workflows/ci.yml` 三平台矩阵(ubuntu / windows / macos × node 20)。
|
|
171
|
+
- 测试:`npm test` 18 套件 **1943 条**(0.11.0:1736 条);变异 **108 条,全部咬住**。
|
|
172
|
+
|
|
173
|
+
## [0.11.0] — 2026-09-29(节假日自动获取 + 金额浮层瘦身 + 官方价格页 URL 事故修复)
|
|
174
|
+
|
|
175
|
+
> **发布记录(2026-09-29)**:`npm publish` → **dsh-composer-ux@0.11.0**(shasum
|
|
176
|
+
> `21444a0383dd42ef78818c3bbdd3e038138438b0`,与发布前 `npm pack --dry-run` **逐字一致** ⇒ 发出去的就是
|
|
177
|
+
> 体检过的那份;9 个文件,与 0.10.0 / 0.6.2 同一形状);git tag **`v0.11.0`**(提交 `adb8690`,
|
|
178
|
+
> 与 npm 产物同一棵树)已推送;GitHub Release:
|
|
179
|
+
> [v0.11.0](https://github.com/fangwen9527/dsh-composer-ux/releases/tag/v0.11.0)(HTTP 201,id 398751005)。
|
|
180
|
+
> 本机 profile 先从 git 钉版(`#adb8690`)试跑,用户确认界面无误后再切回 **npm 精确版本 `"0.11.0"`**。
|
|
181
|
+
> 注意:**本版有宿主半改动,必须重启 DSH 才会加载**。
|
|
182
|
+
|
|
183
|
+
> 起因:用户重启到 0.10.0 后说"**图里这些说明放到设置页面,使点开胶囊变得更简洁**;**添加一个
|
|
184
|
+
> 自动检测国家法定节假日与星期天的功能**"。一问一答定下四件事:① 只搬"说明"三行,两行单价留在浮层;
|
|
185
|
+
> ② 节假日走**联网自动获取 + 内置兜底**(源:`holiday-cn`,数据带国务院办公厅通知原文链接);
|
|
186
|
+
> ③ **补班日照样算谷价**(官方原文说的是"周六、周日全天谷价",与 `dsh-cost-meter` 一致);
|
|
187
|
+
> ④ 复用现有"自动同步"那把开关,不新增开关。
|
|
188
|
+
> 顺带说明一点:**周末本来就是自动的**(0.10.0 就有,周六周日全天谷价),这次补的是"法定节假日"那一半。
|
|
189
|
+
|
|
190
|
+
### 修正(先看这条:0.10.0 的「同步官方价」当时已经坏了)
|
|
191
|
+
|
|
192
|
+
- **官方英文价格页在"不带结尾斜杠"的路径上开始返回另一页**(2026-09-29 真机复核时抓到):
|
|
193
|
+
`https://api-docs.deepseek.com/quick_start/pricing` 现在返回 **200 OK + 4.8 万字节的"快速开始"
|
|
194
|
+
内容、没有价格表**,于是 `parseOfficialPricingPage` 返回 `undefined`、同步直接报"抓官方价格页失败"。
|
|
195
|
+
好在纪律是"**抓不到就不覆盖本地价**",所以只是同步不工作,**没有把价格算错**(本地价目仍是 0.10.0
|
|
196
|
+
那份正确的快照)。修法两条:
|
|
197
|
+
1. 两个官方页 URL 都补上结尾斜杠(中文页本来就是 302 跳到带斜杠的路径,英文页则是**静默返回另一页**);
|
|
198
|
+
2. 新增 `officialPricingCandidates()`:每一页按"带斜杠 → 不带斜杠"依次尝试,**解析成功才算到手**
|
|
199
|
+
(只在抓不到/解析不出时换候选,绝不拿半份数据凑合)。站点以后再对调这两个形状,同步不用改代码
|
|
200
|
+
就能自愈 —— 有两条变异条目(`CL`/`CM`)守着它。
|
|
201
|
+
复核:改完后两页都解析得出,12 格价格与我们写死的现役档**逐格相同**(`node scripts/live-smoke.mjs`)。
|
|
202
|
+
|
|
203
|
+
### 新增
|
|
204
|
+
|
|
205
|
+
- **法定节假日自动获取(`src/holiday-sync.ts` + 宿主半同步路由)**。中国的法定节假日**算不出来**:
|
|
206
|
+
清明/端午/中秋走农历,放几天与哪天调休补班是国务院办公厅每年年底才公布的政策决定。所以本插件
|
|
207
|
+
的做法是"**日期表来自公开数据源,判定规则留在自己手里**":
|
|
208
|
+
- **数据源**:`NateScarlet/holiday-cn`(MIT)每年一个 `{year}.json`,字段
|
|
209
|
+
`{"name":"中秋节","date":"2026-09-25","isOffDay":true}`,并自带 `papers` 指向**国务院办公厅
|
|
210
|
+
通知原文**(2026 那份指到 `www.gov.cn/zhengce/zhengceku/202511/content_7047091.htm`),可自行核对。
|
|
211
|
+
主用 GitHub raw、失败退 jsDelivr 镜像(同一份文件的两个入口)。
|
|
212
|
+
- **只取 `isOffDay:true`**:`isOffDay:false` 是**调休补班日**(2026 年就有 09-20 周日、10-10 周六),
|
|
213
|
+
官方公告写的是"**周六、周日**全天谷价",所以补班日**照样全天谷价** —— 把补班日当工作日算高峰
|
|
214
|
+
会让那两天的金额**翻倍**。这条是用户 2026-09-29 拍板的,变异条目 `CD` 守着它。
|
|
215
|
+
- **抓取范围 = 今年 + 明年**(按**北京**年判,跨年那一刻用 UTC 年会算错,变异条目 `CE`)。
|
|
216
|
+
2027 那份现在只有 `{"year":2027,"days":[]}`(国务院年底才发),**"还没公布"不是"失败"**:
|
|
217
|
+
界面说的是"20xx 年的安排还没公布",不是"网络坏了"。
|
|
218
|
+
- **优先级:你手填的 > 自动获取的 ∪ 内置表**。手填**整份覆盖**(你的意思不该被后台悄悄改掉);
|
|
219
|
+
自动那份与内置表**取并集**而不是替换 —— 自动获取只覆盖"今年+明年",到了 2027 年 1 月,2026 年
|
|
220
|
+
中秋/国庆那几天就不在里面了,拿它替换会让**2026 年 9 月的旧会话被重新按高峰价显示(2 倍)**。
|
|
221
|
+
- **开关复用**「自动同步」那把(默认仍**关**):同一个语义"这个插件可以自己出网",没必要两个开关。
|
|
222
|
+
打开后每个年份最多 **30 天**复核一次(进程启动先查一次 + 每 30 分钟查一次"到期没有");
|
|
223
|
+
也可以随时点设置页的「获取法定节假日」立即取一次(手动点击 = 明确的同意,不受 30 天窗口限制)。
|
|
224
|
+
- **纪律**:抓不到就什么都不改(两个入口都失败 → `ok:false`,本地表原样不动);抓到后写设置时
|
|
225
|
+
立刻 `invalidateMoney()`(不然后台取回新表、峰谷却仍按旧表判)。
|
|
226
|
+
- **设置页「计价说明」块**:把金额浮层里那三行静态说明搬了过来 —— 刊例价快照日期、价格档机制
|
|
227
|
+
("每笔用量按发生时刻的档结算")、峰谷判定(工作日 09:00–12:00 / 14:00–18:00 为高峰、不含法定
|
|
228
|
+
节假日、周末与节假日全天谷价)、非 DeepSeek 口径(未含中转加价、以各家账单为准)。
|
|
229
|
+
节假日那块还多了"当前生效:N 个日期 —— 来源:自定义 / 自动获取(时间)/ 内置"以及可展开的
|
|
230
|
+
**生效日期清单**。
|
|
231
|
+
|
|
232
|
+
### 变更
|
|
233
|
+
|
|
234
|
+
- **金额浮层瘦身**(用户要的"点开胶囊更简洁"):原来最后三行的去留**各不相同**:
|
|
235
|
+
- **峰谷判定段**:设置页本来就有一份同义文字 → 从浮层删掉(纯去重);
|
|
236
|
+
- **刊例价快照行**:整行搬去设置页;"这份价是**你自己填的**"缩成单价行上的 `· 已自定义` 小标
|
|
237
|
+
(只在你覆盖过价时出现,默认不占字);
|
|
238
|
+
- **价格档说明**:改成**只在非默认时出现** —— 本会话用到的档 ≠ 当前生效的档才显示。这条不能
|
|
239
|
+
简单删:跨了 9-10 调价的会话,浮层上的单价是今天的价、金额却是当时的价,不说反而是误导。
|
|
240
|
+
- 浮层现在只剩:各 route 分列、合计、两档小计、三分项、缓存命中率、**峰谷状态行**、**两行单价**。
|
|
241
|
+
- 设置页「金额」卡从五块变六块(多「计价说明」);「同步价目」改名「同步」,多第三个按钮
|
|
242
|
+
「获取法定节假日」,自动同步开关的文案改成「自动同步(价格每天一次、节假日每 30 天复核)」。
|
|
243
|
+
- 节假日输入框改成**只放手填那一份**(留空 = 用自动获取/内置),并给了灰字说明 —— 以前它会把
|
|
244
|
+
内置表预填进去,让"留空 = 用默认"这件事看不出来。
|
|
245
|
+
|
|
246
|
+
### 验证
|
|
247
|
+
|
|
248
|
+
- `npm test`:**18 个套件 / 1736 条全绿**(新增 `test/holiday-sync.mjs` 86 条:解析、脏输入、
|
|
249
|
+
北京年边界、开关门控、并集上限、优先级、镜像兜底、失败不改动,以及**真宿主半**的路由 / 用量路由 /
|
|
250
|
+
自动 tick 端到端)。
|
|
251
|
+
- 变异测试 `node test/mutation-guards.mjs`:**89/89 全部被咬住**(新增 `CD`–`CM` 共 10 条)。
|
|
252
|
+
- `node scripts/live-smoke.mjs`:官方两页 12 格价格与现役档逐格相同(URL 修好后恢复通过);
|
|
253
|
+
节假日源实测 2026 年拿到 33 天、**补班日不在表里**、内置那 10 天口径一致;余额端点 401。
|
|
254
|
+
- 真机:本机 profile 装的 0.11.0 由用户重启后生效(本版**有宿主半改动**,必须重启)。
|
|
255
|
+
|
|
256
|
+
## [0.10.0] — 2026-09-29(节假日 / 峰谷提醒 / 余额 / 官方价同步 / 非 DeepSeek 定价 / 价格历史档)
|
|
257
|
+
|
|
258
|
+
> **发布记录(2026-09-29)**:`npm publish` → **dsh-composer-ux@0.10.0**(shasum `701cbed53887a7ba75dd94206997f89c1720fa7c`);
|
|
259
|
+
> git tag **`v0.10.0`**(提交 `38d0d0d`,与 npm 产物内容一致)与补打的 **`v0.9.1`**(`77cc306`)已推送;
|
|
260
|
+
> GitHub Release:[v0.10.0](https://github.com/fangwen9527/dsh-composer-ux/releases/tag/v0.10.0)。
|
|
261
|
+
> npm 上从 **0.6.2** 直接跳到这里(0.7.0~0.9.1 未在 npm 发布,内容都在本版里);
|
|
262
|
+
> 本机 profile 已从"钉 git commit"改成从 **npm 装 0.10.0**,组合预检 `[A]` 干净。
|
|
263
|
+
> 注意:**本版有宿主半改动,必须重启 DSH 才会加载**。
|
|
264
|
+
|
|
265
|
+
> 起因:用户要求"联网搜一下最新峰谷价格,解析 `dsh-context` 与 `dsh-cost-meter` 这两个插件
|
|
266
|
+
> 怎么算钱,与我们的对比,并补上我们没有的功能"。一问一答定下范围:**A 节假日、B 峰谷提醒、
|
|
267
|
+
> C 余额、D 官方价同步、E 非 DeepSeek 定价**;随后他又选了 **F 价格历史档**("旧账要按当时的价
|
|
268
|
+
> 固定",最大那一档)。顺手修掉三处**会算错钱**的既有错误。
|
|
269
|
+
|
|
270
|
+
### 修正(先看这五条)
|
|
271
|
+
|
|
272
|
+
- **浮层里那行"未定价"说明以前写着 markdown 的 `**未定价**`**(第 7 轮的真渲染测试抓到):`note()`
|
|
273
|
+
是纯文本渲染,用户会在明细浮层里**看到字面的星号**。已改成「未定价」。同一处还有一条**旧断言
|
|
274
|
+
把 bug 焊死**(`test/client-registration.mjs` 里断言的正是 `未定价**:那是非 DeepSeek 模型`),
|
|
275
|
+
一并改成断言修好后的文本 + 禁止 markdown 记号。
|
|
276
|
+
- **`deepseek-reasoner` 一度被映到 Pro(第 5 轮核查时发现并修掉)**。官方更新日志(2026-04-24,中英
|
|
277
|
+
两版都写了):`deepseek-chat` 与 `deepseek-reasoner` **都指向 `deepseek-v4-flash`** —— 前者是
|
|
278
|
+
非思考模式、后者是思考模式,与 `deepseek-v4-pro` 没有对应关系(这两个名字已于 2026-07-24 停用)。
|
|
279
|
+
0.10.0 第一版把 reasoner 映到了 Pro ⇒ history 里叫 `deepseek-reasoner` 的用量会**按 Pro 价算**
|
|
280
|
+
(flash 的 3 倍)。现在两个名字都映到 `deepseek-flash`,并有按官方规则写的行为断言
|
|
281
|
+
(`test/pricing.mjs`)+ 变异条目 `CB`。
|
|
282
|
+
- **flash 的人民币列不再由美元折算**。旧表 `2.05 / 8.18 / 1.02 / 4.09 / 0.041 / 0.020` 是拿官方
|
|
283
|
+
美元列 ×6.82 凑出来的,而官方人民币页直接给 `2 / 8 / 1 / 4 / 0.04 / 0.02` —— **高报约 2.3%**。
|
|
284
|
+
现在两列都是官方原值(Flash 两列之比约 6.667、Pro 约 6.82,本来就不是同一个汇率)。
|
|
285
|
+
- **`deepseek-v4-flash` 不再单列一行**。官方脚注明说旧名仍可调用、但由 V4.1-Flash 服务
|
|
286
|
+
**并按 Flash 价计费**,而它还占着一行 8 月旧价(谷 `1.5 / 0.05 / 4.5`)→ **未命中高报 50%**。
|
|
287
|
+
现在它是别名(含 `deepseek-v4-flash-vision-exp` / `deepseek-v4.1-flash` / `deepseek-chat`),
|
|
288
|
+
内置价表只剩**官方现役的两个模型**。
|
|
289
|
+
- **`cacheWriteTokens` 不单独计价(核对,不是改动)**:官方价表只有"缓存命中/未命中"两行输入价,
|
|
290
|
+
而实测本机投影缓存里 200 处样本全是 0,所以它只进显示口径。这条写进了 `pricing.ts` 的注释,
|
|
291
|
+
免得下次有人"顺手补上"。
|
|
292
|
+
|
|
293
|
+
### 新增
|
|
294
|
+
|
|
295
|
+
- **价格历史档(`PRICE_ERAS`)**:官方 2026-09-10 12:00(北京)调过 Flash 的价
|
|
296
|
+
(未缓存输入 0.22→0.15、输出 0.66→0.60、命中 0.007→0.003,美元/1M),历史金额不该跟着现在变。
|
|
297
|
+
于是价目表按**生效时刻**分档(`legacy` 峰谷制之前 / `peak-2026-08` / `flash-2026-09-10`),
|
|
298
|
+
折叠时给每条用量打上那一刻的档位 id,route key 因此多一维 —— 同一模型在调价前后是**两条**
|
|
299
|
+
分列,各按当时的价结算,永不互相污染。`test/usage-fold.mjs` 第 10 节与 `test/pricing.mjs`
|
|
300
|
+
第 10 节各钉一遍。
|
|
301
|
+
- **中国法定节假日全天谷价**(`DEFAULT_PEAK_HOLIDAYS`):2026 中秋 09-25~27、国庆 10-01~07
|
|
302
|
+
(国务院办公厅 2026 年安排)。设置页「金额」里有一个文本框可整份覆盖(一行一个 `YYYY-MM-DD`,
|
|
303
|
+
坏行逐条点名报错,不静默丢)。另外补上**周末全谷价的生效点** `2026-08-22T16:00Z`
|
|
304
|
+
—— 在那之前的周六(2026-08-22)**是有高峰的**,历史账要按那时的规则算。
|
|
305
|
+
- **峰谷提醒**(`src/client/peak-alert.ts`):明细页常显"现在哪一档、还有多久切换"
|
|
306
|
+
(`空闲档 · 2 小时 15 分钟后转高峰`,节假日/周末会写明原因),胶囊的 `title` 也带上;
|
|
307
|
+
可选浏览器系统通知(需授权),去重放**模块级**按切换点记 —— 放组件里会因重挂而连发。
|
|
308
|
+
- **账号余额**(`src/balance.ts` + 宿主路由):查官方 `GET /user/balance`,
|
|
309
|
+
**API Key 只在宿主半**从 `llm-deepseek` 的 `apiKeyEnv`(默认 `DEEPSEEK_API_KEY`)经
|
|
310
|
+
`credentials.resolve()` 取;**端点白名单只放行 `api.deepseek.com`** —— 你的 baseURL 被指向
|
|
311
|
+
第三方时**一个请求都不发**,绝不把 Key 交出去。余额用分项相加(不用平台自己的 `total_balance`),
|
|
312
|
+
拿不准就不显示(不显示 0、也不显示旧值)。
|
|
313
|
+
- **一键同步价目**(`src/price-sync.ts` + `src/official-pricing.ts`):
|
|
314
|
+
「同步官方价」抓官方中英文两页(各约 24 KB)解析成一个新档,**有变化才新增档**
|
|
315
|
+
(原地改档 = 把历史账重算);「同步第三方价目」抓 models.dev(5.2 MB → 压缩后约 450 KB,
|
|
316
|
+
落 `$DSH_HOME/storages/composer-ux/prices.json`)。两条都守同一条纪律:
|
|
317
|
+
**抓失败绝不覆盖本地价**(`test/price-sync.mjs` 用抛错 / 非 2xx / 正文过短 / 只有一页成功
|
|
318
|
+
四种情形钉住)。
|
|
319
|
+
- **内置第三方价目快照**(`src/provider-prices.ts`,由 `scripts/gen-provider-prices.mjs` 从 models.dev 生成):11 个 provider / 346 个模型,快照日期写在文件头。**没点过同步时**它就是非 DeepSeek 模型的兜底;点过一次「同步第三方价目」后,那份最新数据会盖住同名条目(界面会分别标明"内置快照价"还是已同步)。快照里**故意不收 `deepseek`** —— models.dev 那边只有平坦谷价,留着迟早被误用成 DeepSeek 单价、把峰价静默算成谷价。
|
|
320
|
+
- **自动同步官方价(可选,默认关)**:设置页「金额」里一个复选框。打开后宿主半每天最多自动抓一次官方价格页(进程启动先查一次,之后每 30 分钟查一次"距上次成功同步是否够 24 小时"),失败只写日志、界面仍显示上次成功的时间。"该不该出网"由纯函数 `autoSyncDue` 判定(单测 + 变异各钉一条)—— 会自动出网的开关不该默认开,更不该看漏用户的开关。
|
|
321
|
+
- **联网复核脚本**(`scripts/live-smoke.mjs`,手动跑、不进 `npm test`):单测里的出网路径全部用假 fetcher(测试不该依赖网络),所以"能抓到价"在单测里只是没被证伪。这个脚本用**构建用的同一份源码**打真实网络,把三件事变成实测:① 官方价格页解析出的**人民币六格**与写死的现役档**逐格对比**(等于每天给价目表对账);② models.dev 压缩后的表与**内置快照逐条对比**,并确认 `deepseek` 整块被丢掉;③ 余额端点的白名单与真实状态码(用假 Key,只打印状态码,不打印 Key),且全程**不写**任何文件。2026-09-29 实测:官方两模型六格全一致、内置快照 **346/346 条**与实时一致、余额端点按预期返回 401。
|
|
322
|
+
- **非 DeepSeek 模型定价**:按 `(provider, model)` 查价(精确 → `PROVIDER_ALIASES` 别名 →
|
|
323
|
+
按模型 id 全局唯一匹配,**多个 provider 同名就拒绝猜**);覆盖价键支持 `provider:model`;
|
|
324
|
+
第三方路由没有峰谷两档,设置页给它们渲染**平坦价**一档。**认不出价就说"未定价"**
|
|
325
|
+
(单价全 0 + 界面上写明怎么补价),**不再把 DeepSeek 的 flash 价静默套到别人头上**。
|
|
326
|
+
- **安全**:新增的三条宿主路由(用量分列 / 价目同步 / 余额)都补上了官方那道
|
|
327
|
+
`connection.requestRejection(request)` 关卡(Host/Origin 围栏 + 浏览器令牌)——
|
|
328
|
+
此前只有重启路由挂了它,而 `webServer` 是可以绑 `0.0.0.0` 的。
|
|
329
|
+
|
|
330
|
+
### 内部
|
|
331
|
+
|
|
332
|
+
- **`test/settings-render.mjs` 补一节"长期用户那套非默认设置"**(第 13 轮,69 → 78 条):前面几节要么用默认值、
|
|
333
|
+
要么只改一两个字段,而真实用户是一堆字段都被改过 —— 那正是最容易画错的地方(某个分支把别人的文案顶掉、
|
|
334
|
+
或者开关状态没跟设置走)。这一节同时钉住:覆盖价(含 `provider:model` 键)、自定义节假日(3 天)、
|
|
335
|
+
**自动同步开着时复选框要勾上**(此前只验过"默认关"那一半)、余额关掉(复选框不勾 + 「刷新」按钮禁用)、
|
|
336
|
+
提前量跟着设置走(15 分钟而非默认 5)、第三方那行渲染成「平坦价」一档、内置行仍是峰 / 谷两档,
|
|
337
|
+
以及渲染文本里**不许出现 markdown 记号**(沿用浮层那条纪律)。
|
|
338
|
+
- **修好 `test/check-sections.mjs` 在 0.1.7 上的 ENOENT**(第 9 轮):0.1.7 把 `$DSH_HOME/settings.yaml`
|
|
339
|
+
改名成了 `settings.yaml.imported`,而这个"升级前先看一眼"的只读工具只试老名字,于是一直报 ENOENT
|
|
340
|
+
(交接文档里记成"已知红")。现在两个名字都试,找不到就把试过的路径都打出来。本机实测已能打印
|
|
341
|
+
七栏迁移结果(键位 / 右键菜单 / 快捷指令 / 设置面板 / 默认终端 / OpenCode / 统计行)。
|
|
342
|
+
- **`test/cost-panel-render.mjs`:把「金额浮层」也真渲染出来测**(19 条,已进 `npm test`)。
|
|
343
|
+
浮层是 0.10.0 改动最大、却只有源码字符串断言的一处,而它正是"诚实性"的落点:未定价要不要写
|
|
344
|
+
`¥0.00`、内置快照价有没有标明、价格历史档有没有说出来、分项加起来是不是恰好等于合计。
|
|
345
|
+
手法与 `settings-render` 相同(借 profile 的 react + `react-dom/server`),另加三处**内存里**的替换:
|
|
346
|
+
`createPortal` 换恒等函数 + 假 `document`/`window`(SSR 没有 DOM,而浮层在渲染期就读
|
|
347
|
+
`window.innerWidth` 算位置)、`open`/`hover`/`anchor` 的初值换成"已展开"(浮层条件是
|
|
348
|
+
`open && anchor !== null`,而 `anchor` 只在点击时才设上)、`useSessionCost` 的返回值换成夹具
|
|
349
|
+
(钩子本身由 `client-registration` 覆盖)。**锚点找不到就报错退出**,不静默放行。
|
|
350
|
+
第一版有 12 条红:两条是夹具/断言写错(未定价那条 route 我给了 0 token,被组件按
|
|
351
|
+
`tokenTotal > 0` 过滤掉了)、一条是我的断言太严(分项里的 `¥0.00` 是"没定价所以乘出来是 0",
|
|
352
|
+
关键是**头号数字**不能是 `¥0.00`),其余全是那个 markdown 真缺陷。
|
|
353
|
+
- **`scripts/dsh-shape-check.mjs`:升级 DSH 后的形状核对**。本插件依赖一批宿主内部形状,它们
|
|
354
|
+
**名字或形状一变不会报错、只会静默降级**:`sessions` 服务(`ctx.inject([...])` 里缺了它整条用量
|
|
355
|
+
路由根本不注册)、`sessions.list()`、`Session.id` / **`Session.seq`**(拿不到 `seq` 时 `liveSeq`
|
|
356
|
+
是 `undefined`,增量重读悄悄失效、面板停在旧数字上)、`sessionQuery.readSession()` 与其返回体里的
|
|
357
|
+
`events`、**事件信封上的 `seq`/`time`**、设置服务的 `describe/update/replace/mutate` 与
|
|
358
|
+
`settings/document-updated`(以及"这个版本没有 `get(ns)`")、`webServer.register(route: WebRoute)`
|
|
359
|
+
与 `kind: 'exact'`、`connection.requestRejection()`、以及服务器**能绑 `0.0.0.0`**(这正是每条路由
|
|
360
|
+
都必须过信任关卡的原因)。脚本**读 DSH 源码**逐条核对并打印 `文件:行号`,2026-09-29 实测 20 条全过。
|
|
361
|
+
写这个脚本本身抓到我两处路径/正则记错(`webServer` 在 `packages/host/webserver`、
|
|
362
|
+
`requestRejection` 在 `packages/client/connection/src/rpc-host.ts`、写入口签名带 `async`)——
|
|
363
|
+
正是它要防的那类"凭记忆写死名字"。
|
|
364
|
+
- **`alive` ref 必须在 effect 体里重置**(复查时发现,3 处同病):客户端三处"卸载后丢弃响应"的写法是
|
|
365
|
+
`useEffect(() => () => { alive.current = false }, [])` —— StrictMode(以及某些 HMR 重挂)会走
|
|
366
|
+
"挂载 → 清理 → 再挂载",而 ref 在这一轮里是**同一个对象**,于是 `alive` 永远是 `false`,
|
|
367
|
+
之后所有响应被静默丢弃(表现为"同步永远停在同步中…""余额一直空白""胶囊永远停在本地估算那个数")。
|
|
368
|
+
本机 DSH 客户端当前**没有**开 StrictMode(已查 `packages/client/ui-renderer`),所以还没暴露,
|
|
369
|
+
但这是一颗定时炸弹。现在三处都改成在 effect 体里 `alive.current = true` 再返回 cleanup;
|
|
370
|
+
护栏见 `test/client-registration.mjs` 第 14 节,变异条目 `CA`。
|
|
371
|
+
- **写完设置的人自己通知失效,不靠设置变更事件**(复查时发现的静默错处):0.1.7 的
|
|
372
|
+
`settings/document-updated` 只在 `describe()` 里比对 raw 变化后发出
|
|
373
|
+
(`packages/settings/settings/src/index.ts`),而我们自己用 `mutate` 写设置时**并不调
|
|
374
|
+
`describe()`** —— 设置页开着时会被界面刷新顺带触发,但**设置页没开着**(典型场景:后台的
|
|
375
|
+
「自动同步官方价」跑完)那条事件根本不会发;而折叠缓存按 `seq` 去重,改规则不会让旧事件重折,
|
|
376
|
+
于是金额会**静静地停在旧价格档**上、界面上完全看不出来。现在:模块级
|
|
377
|
+
`moneyInvalidators` 注册表 + `invalidateMoney()`,用量路由登记回调(回收时摘掉),
|
|
378
|
+
`syncOfficial()` 的两条收尾与 `syncModelsDev()` 写盘后都直接通知失效;事件只作补充。
|
|
379
|
+
测试见 `test/quick-commands.mjs` 第 13 节(**源码级**护栏:那条路由会真出网、测试不该打网络),
|
|
380
|
+
变异条目 `BZ` 保证它咬得住。
|
|
381
|
+
- `pricing.ts` 的 `resolvePrice(model, options)` 增加 `provider` / `era` / `providers` / `holidays`,
|
|
382
|
+
返回体增加 `era` / `source` / `unpriced` / `provider`;新增 `eraAt` / `eraIdAt` / `eraById`、
|
|
383
|
+
`isPeakAt` 的节假日与周末边界、`peakPhaseAt`(切换点 + 倒计时)、`formatCountdown`、
|
|
384
|
+
`parseHolidays`、`isDayKey`、`isDeepSeekRoute`、`providerRateOf`、`parseProviderPrices`、
|
|
385
|
+
`parsePriceEras`。
|
|
386
|
+
- `usage-fold.ts` 的 `PeakAt` 换成 `TierAt`(多收 `provider` / `model`,回 `{peak, era}`),
|
|
387
|
+
`RouteUsage` 多一个 `era`;`UsageCache` 多一个 `clear()` —— 判定规则(节假日/价档)一变,
|
|
388
|
+
宿主半必须整份作废缓存,否则水位去重会让金额一直停在旧规则上(第 11 节钉住这条)。
|
|
389
|
+
- 新增测试:`test/official-pricing.mjs`(45 条,真页面夹具)、`test/balance.mjs`(80 条)、
|
|
390
|
+
`test/price-sync.mjs`(48 条);`test/settings-render.mjs` 也进了 `npm test`。
|
|
391
|
+
|
|
392
|
+
## [0.9.1] — 2026-09-28(新增:设置页「金额」栏可改峰谷单价;峰谷改成按每笔用量发生的时间判定)
|
|
393
|
+
|
|
394
|
+
> 起因:用户要求"在插件的设置页面添加一个金额胶囊的设置条目,让我能调峰谷价格之类的"。
|
|
395
|
+
> 一问一答定下范围:**只做按模型的峰/谷单价**(内置三个模型常显 + 可自加任意模型名),
|
|
396
|
+
> 不做整体系数、不改币种、不动时段规则、不加这一栏的总开关。做的时候又发现一个既有缺陷、
|
|
397
|
+
> 按用户选择一并修掉:**峰谷原本按"你看面板的那一刻"判定**(昨晚跑的会话今天上午看会整份
|
|
398
|
+
> 按高峰价显示,差 2 倍),改成按**每条用量事件自己的时间戳**判定 —— 连折叠态胶囊那个数字
|
|
399
|
+
> 也一并准时。
|
|
400
|
+
|
|
401
|
+
### 新增
|
|
402
|
+
|
|
403
|
+
- **设置页第 8 张卡「金额」**(`src/client/CostCard.tsx`):按模型改**高峰 / 空闲两档**的
|
|
404
|
+
三项单价(未缓存输入 / 缓存命中 / 输出,元每 1M tokens)。内置 `deepseek-flash`、
|
|
405
|
+
`deepseek-v4-flash`、`deepseek-v4-pro` 三行常显(并标出别名 `deepseek-chat` /
|
|
406
|
+
`deepseek-reasoner`),另有「再加一个模型」可填任意模型名(中转、自建路由的名字不在官方
|
|
407
|
+
价表里,不覆盖就只能按 `deepseek-flash` 估价)。改价**即时生效**(胶囊与明细页当场跟着变),
|
|
408
|
+
不需要刷新页面、不需要重启。
|
|
409
|
+
- **留空=沿用官方刊例价**:输入框的灰字占位就是官方价(`officialTripleOf`),填了才算覆盖。
|
|
410
|
+
一整档三项都清空 → 那一档消失;一个模型两档都空 → 那一整行消失;整表全空 → 设置里这个
|
|
411
|
+
键直接删掉(`withOverrideValue` / `withoutPricingModel`,纯函数 + 逐条测试)。
|
|
412
|
+
- **非法文本不静默清空**:`1,02`、`2元`、`-1` 会**留在框里标红**(`parsePriceText` 三态),
|
|
413
|
+
不写进设置 —— 静默清空等于"价格悄悄回到官方价",而用户以为改成功了。
|
|
414
|
+
- **写入是合并 + 认回声**:连改几格只发一次设置写入(300ms 合并),并用规范化比对分辨
|
|
415
|
+
"设置里来的新值"与"自己刚写进去的回声",否则回声会把还没写完的第二格编辑冲掉。
|
|
416
|
+
- 「恢复默认」也会清掉这张表(否则恢复默认之后金额还按旧价算)。
|
|
417
|
+
- 存储上它是**一个字段装一棵树**(`priceOverrides`,键是用户自加的模型名)。schemastery 的
|
|
418
|
+
对象 schema 表达不了动态键,所以这一项是顶层 `z.any()` + volatile;能不能写进设置文档
|
|
419
|
+
取决于"字段 volatile / 类型容得下 / 写入校验不在它下面挑刺"三条同时成立,
|
|
420
|
+
`test/client-registration.mjs` 第 12 节把 DSH 设置服务那三步校验**复刻进来跑一遍**。
|
|
421
|
+
|
|
422
|
+
- **峰谷按每笔用量发生的时间判定**(宿主半,`src/usage-fold.ts`):官方按请求发生时刻计费,
|
|
423
|
+
而 0.8.0 拿"打开面板的那一秒"当所有用量的时刻 —— 昨晚(空闲档)跑的会话今天上午看会整份
|
|
424
|
+
按高峰价显示。现在折叠时用事件的 `time` 判档、按 `(provider, model, 档位)` 拆桶,
|
|
425
|
+
**金额也由宿主半算好**(用户覆盖价一并读自设置),客户端只负责显示。
|
|
426
|
+
- **同一步的替换增量沿用该步第一次判定的档**:否则一次跨过 09:00 的请求会被拆成两档,
|
|
427
|
+
凭空多出一个"高峰用量"。
|
|
428
|
+
- 事件既没有 `request/header` 时间、自己也没有 `time` 时,判定函数收到 `NaN`,
|
|
429
|
+
由宿主半退回"现在"(=旧行为),绝不静默把所有用量判成空闲档。
|
|
430
|
+
- 明细页据此多两行「高峰档 / 空闲档」的 token 与金额,并把两档生效单价分别列出来。
|
|
431
|
+
|
|
432
|
+
- **折叠态胶囊改为向宿主半取"逐笔准时"的金额**(`src/client/session-cost.ts`):
|
|
433
|
+
客户端投影只有累计 token 桶、**没有任何时间信息**,所以这个数字只能从宿主半拿。
|
|
434
|
+
取数是"最短 600ms 间隔 + 尾随一次 + 单飞 + 旧响应丢弃":尾随保证流结束后的那个数字是准的,
|
|
435
|
+
到手之前/取不到时用本地的"按当前档位估算"顶上(胶囊从不空着,面板里会说明当前是哪种口径)。
|
|
436
|
+
|
|
437
|
+
- **宿主半的增量折叠缓存**(`createUsageCache`):金额胶囊要跟着流式用量刷新,而
|
|
438
|
+
`sessionQuery.readSession()` 会深拷贝整份日志并重新校验(几千条事件要几百毫秒),
|
|
439
|
+
每次都重读不可接受。现在按会话缓存折叠器,用 `session/event` 增量喂,取价是 O(1);
|
|
440
|
+
首次(或发现 `seq` 落后)才完整读一次。**播种窗口**(读日志期间追加的事件)进缓冲区、
|
|
441
|
+
读完按 `seq` 补上 —— 不这么做的话水位会跳过快照里那些中间事件,金额**永久少算**
|
|
442
|
+
(`feed()` 的 seq 幂等,补也补不进去)。这条由 `test/usage-fold.mjs` 第 8 节用"读的时候
|
|
443
|
+
顺手追加两条事件"的假 read 钉住。
|
|
444
|
+
|
|
445
|
+
### 测试
|
|
446
|
+
|
|
447
|
+
- `test/pricing.mjs` 81 → **107 项**(新增第 9 节:文本三态、空壳回收、删整行、占位价、
|
|
448
|
+
`peak` 参数直选档位、别名键覆盖价);
|
|
449
|
+
- `test/usage-fold.mjs` 40 → **64 项**(新增第 7 节逐笔分档、第 8 节增量缓存与播种窗口);
|
|
450
|
+
- `test/client-registration.mjs` 117 → **141 项**(新增第 11 节护栏与第 12 节"设置服务真能
|
|
451
|
+
收下这张表");`test/settings-render.mjs` 43 → **56 项**(真渲染第 8 张卡:19 个输入框、
|
|
452
|
+
空值必须是空串、占位=官方价、已填值原样显示、自定义行给「删除」);
|
|
453
|
+
- `test/mutation-guards.mjs` 57 → **65 条**(新增 BG–BM:非法文本当清空、空壳不收、
|
|
454
|
+
忽略显式档位、替换增量重判档、播种不缓冲、落后不重读、空框写成 0)。
|
|
455
|
+
|
|
456
|
+
## [0.9.0] — 2026-09-28(新增:官方持久终端六件套 `terminal_*` 常驻挂载)
|
|
457
|
+
|
|
458
|
+
> 起因:用户了解到「DSH 客户端内置了 bash 之类的终端」,要求核实并把官方那套接进插件。
|
|
459
|
+
> 核实结论是**方向对、但要分四层**:模型侧 shell 工具、交互式 PTY 工具包、GUI 右侧栏终端、
|
|
460
|
+
> 以及 `bash.exe` 本体是否内置 —— 前三层官方都有(GUI 那一层此刻就在界面里活着:运行时 Slot
|
|
461
|
+
> 里 `sidebar.right.pane.tab` 的占位者包含 `@deepseek-ai/dsh-client-ui-sidebar-terminal`,
|
|
462
|
+
> `active: true`),但**第四层不是**:应用目录里没有任何 `bash.exe`,Windows 上靠 `PATH` 解析
|
|
463
|
+
> (本机解析到 Git for Windows)。详见 README「官方持久终端」。
|
|
464
|
+
|
|
465
|
+
### 新增
|
|
466
|
+
|
|
467
|
+
- **常驻挂载官方 6 个持久终端工具**(`terminal_open` / `terminal_read` / `terminal_send` /
|
|
468
|
+
`terminal_signal` / `terminal_close` / `terminal_list`):`cordis.patch.yml` 在插件行之后
|
|
469
|
+
插入**三行**(见下),并把 `@deepseek-ai/dsh-tool-terminal` 写进 `package.json` 的
|
|
470
|
+
`dependencies`(区间 `>=0.1.7-rc.1 <0.1.8`)。不需要设置项,也不需要手动开关。
|
|
471
|
+
- **三件套缺一不可,而且必须在同一层**:① `@deepseek-ai/dsh-terminal`(提供
|
|
472
|
+
`ctx.terminals`)② `@deepseek-ai/dsh-terminal-bash`(注册 `type=shell` 的 PTY 后端)
|
|
473
|
+
③ `@deepseek-ai/dsh-tool-terminal`(那 6 个模型工具)。
|
|
474
|
+
- **为什么不照抄社区 patch 只挂 ③**:这个包官方标为**可选**,没有任何 bundle 默认挂它;
|
|
475
|
+
更关键的是 **0.1.7-rc.2 的桌面版没随包发布它** —— `app.asar` 里有 `dsh-terminal`、
|
|
476
|
+
`dsh-terminal-bash`、`dsh-api-terminal-controller`、`dsh-client-ui-sidebar-terminal`,
|
|
477
|
+
唯独没有 `dsh-tool-terminal`(直接写那一行会在启动时解析失败 ⇒ 整棵树挂 ⇒ **白屏**,
|
|
478
|
+
与 0.6.2 那次同形状)。所以 ③ 写成插件自己的依赖。
|
|
479
|
+
- **① ② 是安装目录里就有的官方包**(按包名引用即可解析,像 dsh-mobile 挂
|
|
480
|
+
`dsh-host-directory-picker-browse` 那样),**而且不能给它们加自检守卫** —— 守卫走
|
|
481
|
+
profile 目录的 `createRequire`,解析不到安装目录里的包,加了会把服务永远禁掉。
|
|
482
|
+
- **③ 的自检式守卫**(`disabled: !!js`):自己 `createRequire().resolve()` 一次包名,
|
|
483
|
+
解析不到就把自己禁掉。DSH 的兼容性预检只处理 peer 版本冲突,**「包解析不到」它不管**
|
|
484
|
+
(`compatibility-preflight.ts:63-66`),所以这道自检是「缺包 ⇒ 功能不存在」而不是
|
|
485
|
+
「缺包 ⇒ 白屏」的唯一保障;而 disabled 行加载器根本不初始化
|
|
486
|
+
(`config-schema/document.ts:113`)。表达式必须 `try/catch` 包住(`disabled` 表达式抛错
|
|
487
|
+
算「条目失败」而不是「被禁用」,`app-boot/src/index.ts:914`),这一行也不能带
|
|
488
|
+
`group: true`(会迫使加载器初始化 disabled 行)。
|
|
489
|
+
- **② 的 shellPath 得自己探**:官方后端默认 `shellPath: '/bin/bash'`(`dsh-terminal-bash`
|
|
490
|
+
的 `config.ts:54`,Windows 上解析不到),所以用同步 `!!js` 从 PATH 上那份 git 反推
|
|
491
|
+
`…/Git/bin/bash.exe`,外加 Program Files / LOCALAPPDATA / msys64 等落点;探不到就留空
|
|
492
|
+
并回落官方 pwsh 方言(`resolvePwshPath`)。`timeoutMs` 对齐官方 minimal 预设的 300000。
|
|
493
|
+
- **客户端零改动**:官方客户端已注册这 6 个工具的 `tool.call.toolview` 渲染位。
|
|
494
|
+
- **与「默认终端」互补**:那一栏决定模型用哪个 shell,这一档决定模型有没有持久会话;
|
|
495
|
+
档位选 `PowerShell` 时这 6 个工具照常在。
|
|
496
|
+
|
|
497
|
+
### 真机验证发现并修掉的问题(0.9.0 未发布,直接修在本版)
|
|
498
|
+
|
|
499
|
+
- **「挂上了」≠「生效了」:只挂 ③ 会让它静默 pending**。首版只插了 ③ 一行,重启后模型
|
|
500
|
+
工具表里**什么都没有**。查明:③ 的源码写着
|
|
501
|
+
`const inject = ["terminals", "tools", "systemPrompt"]`,而提供 `terminals` 的 ① 在官方
|
|
502
|
+
组合里只出现在 `sdk-minimal` 与 web-app 的 `minimal` 预设里(standard 预设与 profile 层
|
|
503
|
+
都没有)⇒ cordis 把 ③ 挂在 pending 等依赖,**不报错、不白屏、一个工具都不注册**。
|
|
504
|
+
这与 0.6.2 白屏是**同一形状**(usage-meter 也是 pending),只是那次发生在客户端半、被 web
|
|
505
|
+
启动审计抓住;宿主半的 pending 没人审计,所以是静默的。修法:补齐 ① ②,三行同层。
|
|
506
|
+
- **「装了」≠「生效了」(第二坑):守卫的基准目录依赖了 DSH 内部上下文**。补齐三行后再重启,
|
|
507
|
+
工具表**还是没有**。这次用 `plugin_manager list_plugins`(每条给 `enabled` + `fiberPhase`)
|
|
508
|
+
一眼看到:`composer-ux` / `composer-ux-terminal` / `composer-ux-terminal-bash` 都是
|
|
509
|
+
`active`,唯独 `composer-ux-tool-terminal` 是 **`enabled:false, fiberPhase:null`**
|
|
510
|
+
—— **是被 ③ 自己那道自检守卫禁掉的**。原因:守卫拿 `ctx.get('profileContext')?.dir` 当基准,
|
|
511
|
+
而它在 `!!js` 的求值上下文里拿不到 ⇒ 表达式按「装不上」处理 ⇒ 行被禁用。
|
|
512
|
+
失败方向是安全的(没白屏),但功能静默消失。
|
|
513
|
+
修法:基准改成**只靠通用 Node API 推** —— `DSH_HOME`(有就用)否则
|
|
514
|
+
`os.homedir() + '/.dsh'`(这正是 DSH 自己算 home 的方式),再在 `profiles/node_modules`
|
|
515
|
+
与 `profiles/*/node_modules` 里找;`profileContext` / `DSH_PROFILE_DIR` 降级为**额外**候选,
|
|
516
|
+
而且各自包一层 try/catch —— 它抛错也不能把整个守卫带崩。测试里加了「基准发现」一组用例
|
|
517
|
+
与一条「真环境一致性」用例(守卫的答案必须和测试自己按同样候选查出来的真相一致)。
|
|
518
|
+
- **诊断手法(值得复用)**:`Config.listConfigs` 只报声明与 schema,**不报激活状态**;
|
|
519
|
+
`Service.listService` 能看到 `terminals` 的**能力目录**,但那不代表它在你那一层可用
|
|
520
|
+
(它扫全树,扫到的是 minimal 预设里那份声明)。真正判据有两把:
|
|
521
|
+
① 去包里读 `inject`,再对照官方哪个 bundle / preset 挂了这个服务;
|
|
522
|
+
② **`plugin_manager list_plugins` 的 `fiberPhase`** —— 这是唯一能直接看出「行到底活没活」
|
|
523
|
+
的探针(`active` vs `null`),本轮两个坑都是靠它定位的。
|
|
524
|
+
- **第三个坑(真正的根因):运行时版本不是桌面壳版本,而 DSH 已升级到 `0.2.0-rc.1`**。
|
|
525
|
+
Electron 壳自称 `0.1.7-rc.2`,但应用里所有官方包都是 **`0.2.0-rc.1`**
|
|
526
|
+
(`getDshRuntimeVersion()` 读的是 `@deepseek-ai/dsh-app-boot` 的版本)。而
|
|
527
|
+
`plugin-compatibility.ts` 的判据正是「包的 `@deepseek-ai/dsh-*` peer 区间 vs 运行时版本」,
|
|
528
|
+
不满足就**直接给这一行打 `disabled`**(patch 里看不见,`list_plugins` 里是
|
|
529
|
+
`enabled:false / fiberPhase:null`)。按 0.1.7-rc.2 装的工具包因此在 0.2.0-rc.1 上被整行判掉
|
|
530
|
+
—— 这才是「三行挂上了、工具一个都不出现」的真因。修法:装同版本的
|
|
531
|
+
`@deepseek-ai/dsh-tool-terminal@0.2.0-rc.1`,依赖窗口写成
|
|
532
|
+
`>=0.1.7-rc.1 <0.1.8 || >=0.2.0-rc.1 <0.3.0`(两个窗口各带 pre 比较器,否则 pre 版本匹配不上)。
|
|
533
|
+
- **探测必须排除 WSL**:第一版按 PATH 顺序命中了 `C:\Windows\System32\bash.exe`(WSL 启动器),
|
|
534
|
+
真机 PTY 起成了 WSL bash(`pwd` → `/mnt/d/1zcode/dsh插件`、内核 WSL2)。插件自己那条 bash 工具
|
|
535
|
+
本就硬排除 WSL,YAML 里漏了;现在用 `path.basename/dirname` 判掉 `…\Windows\System32\bash.exe`
|
|
536
|
+
与 `…\Microsoft\WindowsApps\bash.exe`。
|
|
537
|
+
- **更正开发过程中的一个误判**:`process.getBuiltinModule` 在 `!!js` 里**是**可用的 ——
|
|
538
|
+
先前那次失败是预检判的,不是表达式抛的(探测表达式确实用它读到了文件系统)。
|
|
539
|
+
- **端到端已实测通过**(真 PTY;且 profile patch 的改动会被 `dsh-hmr` 即时重放,**不用重启**):
|
|
540
|
+
`terminal_open` → `terminal_send` → `terminal_read` → `terminal_close` 全通;
|
|
541
|
+
`uname -a` = `MINGW64_NT-10.0-26200 … Msys`、`$0` = `/usr/bin/bash`、
|
|
542
|
+
`pwd` = `/d/1zcode/dsh插件`、`grep`/`head` 都在 `/usr/bin`。
|
|
543
|
+
- **部署通道的教训**:手工复制进 `profile/node_modules/<pkg>` 的文件会被 DSH 在版本升级时
|
|
544
|
+
「重新物化 git 依赖」整包冲掉(本轮 `lib/*` 被还原成钉住的 0.6.2 commit,实测)。
|
|
545
|
+
行级改动放 profile 的 `cordis.patch.yml`(用户层,会保留);要改 `lib/` 只能让包本身带上。
|
|
546
|
+
|
|
547
|
+
### 测试
|
|
548
|
+
|
|
549
|
+
- 新增 `test/terminal-mount.mjs`(41 项):结构断言(三行齐全且在同一个 insert 里、服务与
|
|
550
|
+
后端**没有**守卫、全文只有 ③ 一处 `disabled`、守卫包名与行名一致、两行都不带
|
|
551
|
+
`group: true`、后端给了 `backendType`/`timeoutMs`、守卫基准不许依赖 DSH 内部上下文)
|
|
552
|
+
+ **用加载器同一个求值器**(`new Function('ctx','expr','with (ctx) { return eval(expr) }')`,
|
|
553
|
+
出自 `@deepseek-ai/cordis-plugin-loader` 的 `interpolate`)真跑三条表达式:探测在
|
|
554
|
+
「有 bash / 无 bash / 非 Windows / fs 抛错」四种环境下给出预期值且两条自洽;
|
|
555
|
+
守卫在「包在当前 profile / 包只在共享层 / `ctx.get` 抛错 / `DSH_PROFILE_DIR` 兜底 /
|
|
556
|
+
`DSH_HOME` 顶上 / 哪都没有 / `readdirSync` 抛错 / 权限错 / node 内建拿不到」九种环境下
|
|
557
|
+
「该禁就禁、该放就放、永远不抛」;另有一条**真环境一致性**用例:守卫的答案必须与
|
|
558
|
+
测试自己按同样候选查出来的真相一致。
|
|
559
|
+
- `test/mutation-guards.mjs` 新增 `AQ`–`BF` 十七条:拆掉外层 `try/catch`、守卫极性写反、
|
|
560
|
+
挂载行包名漂移、加 `group: true`、依赖改通配、依赖钉死单版本、多挂一行服务、
|
|
561
|
+
**删掉服务行**、**方言回落改成 bash**、**shellPath 写死 `/bin/bash`**、
|
|
562
|
+
**给服务行也加守卫**、注释掉守卫、**`ctx.get` 少了自保**、**守卫基准退回空数组**、
|
|
563
|
+
**探测不再排除 WSL**、**依赖窗口丢掉 0.2.0**。
|
|
564
|
+
|
|
565
|
+
## [0.8.0] — 2026-09-28(新增「金额」:输入框下方显示本会话费用)
|
|
566
|
+
|
|
567
|
+
> 起因是用户装了社区插件 [`dsh-plugin-usage-meter`](https://github.com/fancr-code/dsh-plugin-usage-meter),
|
|
568
|
+
> 但它在本机 **DSH 0.1.7 上打不开网页**(客户端半 `inject` 了 0.1.6 时代的设置服务 `settingsScope`,
|
|
569
|
+
> 0.1.7 已换成 `configForms`,没人提供它 ⇒ 该客户端条目永远 pending ⇒ 网页启动审计抛错 ⇒ 白屏)。
|
|
570
|
+
> 用户的要求是"把这个插件的功能搬到我的插件里",所以本插件按需求移植的是**它的金额能力**,
|
|
571
|
+
> 而不是把它装回来。
|
|
572
|
+
|
|
573
|
+
### 新增
|
|
574
|
+
|
|
575
|
+
- **输入框下方的极简金额胶囊**(`src/client/CostChipEntry.tsx`):不点时只占一个词的位置(`¥0.42`),
|
|
576
|
+
点开是贴在它上方的浮层(React portal,因为输入卡片会裁掉溢出内容;官方几颗胶囊也都是浮层)。
|
|
577
|
+
浮层里有本会话三分项(未缓存输入 / 缓存命中 / 输出)的 token 数与花费、合计、缓存命中率、
|
|
578
|
+
生效单价、模型名与峰谷档,以及"估算、未含中转加价"的说明。
|
|
579
|
+
- **计价与格式**(`src/pricing.ts`):DeepSeek 官方刊例价(三个模型 × 高峰/空闲 × 人民币/美元)、
|
|
580
|
+
模型别名(`deepseek-chat`/`deepseek-reasoner`/`deepseek-v4-flash-vision-exp`)、峰谷判定
|
|
581
|
+
(UTC 周一至周五 01–04、06–10)、费用拆分与金额/Token 格式化。
|
|
582
|
+
- **数据全部来自官方,不新增任何采集**:token 桶读官方 `tokenUsage` 投影(与官方那行统计同源),
|
|
583
|
+
模型读官方 `modelSelection` 投影(`request/header` 事件),所以两个数字不会各算一套。
|
|
584
|
+
- **按 route 分列**(用户追加要求:要把 OpenCode / Zen 那条 route 的用量也算进来、并且看清各花多少):
|
|
585
|
+
折叠态那一个数字仍是客户端按官方投影算的估值;**点开时向宿主半的只读路由
|
|
586
|
+
`/composer-ux/usage?sessionId=…` 取一次逐请求归因**(`src/usage-fold.ts` 折叠,`session.events`
|
|
587
|
+
客户端读不到),分列到手后**面板总额改成分列之和** —— 所以"各行加起来等于总额"永远成立;
|
|
588
|
+
日志与投影对不上(会话刚写入)时退回投影口径并如实说明,不把两套数字并排摆着让人猜。
|
|
589
|
+
两条容易写错的规则都钉在 `test/usage-fold.mjs` 里:**同一 `(turn, step)` 的上报是替换不是累加**
|
|
590
|
+
(直接累加会重复计费),**usage 归属它之前最近一条 `request/header`**(归错就把 Zen 的花费记到官方头上);
|
|
591
|
+
另有官方 `llm/retry-started` 的重开替换槽语义。
|
|
592
|
+
- **事件形状按本版官方 `SessionEventMap` 的真实声明,不按记忆**:2026-09-28 真机踩过一次坑 ——
|
|
593
|
+
最初照抄的社区实现折的是 `assistant/chunk` 事件,而**本版 DSH 根本没有这个事件类型**(usage 藏在
|
|
594
|
+
`assistant/attempt` 的 `stream` 里,或 `assistant/message` 的 `data.usage` 上),于是分列一行都出不来,
|
|
595
|
+
面板只显示"还没有可归因的用量"。改用官方 `sessionQuery.readSession(sessionId)`(带 data 的完整日志)
|
|
596
|
+
取事件、按官方 `lastAssistantStreamChunk(stream,'usage')` 取 usage 之后才通。这条教训与更正后的出处口径
|
|
597
|
+
(折叠对齐官方 `token-meter` 的 `usage-projection`,社区实现只贡献了"按 provider/model 分列"这个目标)
|
|
598
|
+
都写在 `src/usage-fold.ts` 文件头。
|
|
599
|
+
- **同一次真机核对还揪出面板一个算错的数**:折叠态"缓存命中率"原本用 `命中 ÷(未缓存 + 命中)`,
|
|
600
|
+
漏掉缓存写入,于是屏幕上与官方那行统计对不上(官方 98.013%、面板 100%)。已改成与官方同分母的
|
|
601
|
+
**计费输入**(未缓存 + 缓存读 + 缓存写)。
|
|
602
|
+
- **改完这一条,命中率**仍然**显示 100% —— 因为键名在调用处又写错了一次**(2026-09-28 第二次真机核对):
|
|
603
|
+
修好的那个 `billedInputTokens()` 只认 `uncachedInputTokens`,而调用处传的是 `{ inputTokens: … }`,
|
|
604
|
+
于是分母只剩"缓存读 + 缓存写",命中率恒等于 100%(旁边官方胶囊是 98.206%)。教训是"两个键名都认"
|
|
605
|
+
必须落在**每一处传值**上,而不只是某个函数内部;现在调用处传 `uncachedInputTokens`,
|
|
606
|
+
并由 `test/client-registration.mjs` 钉住"调用处的键名"与"和输入框下面那一行同一套函数"。
|
|
607
|
+
- **胶囊字号照抄官方 `.root`(修"比旁边的字大")**:官方那颗统计胶囊的字号与行高**不在 `.pill` 上**
|
|
608
|
+
(`.pill` 里只有 `font: inherit`,那是为了抵消 button 的 UA 字体),而在它爸爸 `.root` 上
|
|
609
|
+
(`calc(var(--dsh-content-font-size-secondary, 13px) - 1px)` / `calc(20px + …delta-secondary…)`)。
|
|
610
|
+
本条目是同一个 dock 槽位里的**另一条**记录,不在这颗 `.root` 里,只写 `font: inherit` 就继承了
|
|
611
|
+
输入框那一层(`--dsh-content-font-size`,默认 14px)—— 真机上看就是"插件输入框下方的字比官方的大"。
|
|
612
|
+
现在把 `.root` 那两条显式写在按钮上(并拆掉 `font:` 简写,否则简写会把字号重置回继承),
|
|
613
|
+
与旁边那颗胶囊同字号同字重,且随用户的字号设置一起变;亮度保持官方 `.pill` 的
|
|
614
|
+
"静止 `label-tertiary`、hover/展开 `label-secondary`"。
|
|
615
|
+
- **点开后补上三分项行**:分列模式下除各 route 与合计,再列出「未缓存输入 / 缓存命中 / 输出」的
|
|
616
|
+
token 与花费。三行的钱**按各 route 自己的单价分别累加**(不是拿合计的桶乘一个单价),
|
|
617
|
+
所以"三行相加 = 合计"在两条 route 单价不同时也**精确成立**(2026-09-28 实测
|
|
618
|
+
¥5.17 + ¥5.58 + ¥3.78 = ¥14.53,与合计逐位一致)。
|
|
619
|
+
- **计价不看 provider**:一律按**模型名**查 DeepSeek 官方价目表 —— 所以走 `go`(OpenCode Zen)
|
|
620
|
+
的用量一样进这个金额、一样按官方价估算。代价是 Zen 自己可能有加价/订阅,屏幕上的数字
|
|
621
|
+
**不等于**你付给 Zen 的钱(浮层里写明"未含中转加价,实际扣费以各家账单为准")。
|
|
622
|
+
|
|
623
|
+
### 出处(MIT)
|
|
624
|
+
|
|
625
|
+
价目表、模型别名、峰谷规则、`costOf`/`formatMoney`/`formatTokens` 的**分档规则**来自
|
|
626
|
+
`dsh-plugin-usage-meter@1.9.1`(MIT,Copyright (c) 2026 fancr-code)。`test/pricing.mjs` 把那份实现的
|
|
627
|
+
几个函数**原样抄进来当基准**,逐样本对拍:刊例价表逐项、峰谷判定(一周 593 个时刻)、
|
|
628
|
+
定价解析(模型 × 币种 × 时段 × 覆盖价,共 216 组)、费用(30 组)、金额(40 组)、Token 格式(16 组)。
|
|
629
|
+
|
|
630
|
+
### 与出处有意不同的两处(都钉在测试里)
|
|
631
|
+
|
|
632
|
+
- **金额至少保留两位小数**:出处用 `replace(/0+$/,'')` 收尾,于是"第二位小数恰好是 0"的值会少一位
|
|
633
|
+
(`¥0.1`)、六位全为零的值更会只剩一个小数点(`¥0.`)。我们按官方金额惯例至少两位(`¥0.10` / `¥0.00`)。
|
|
634
|
+
- **覆盖价的币种语义写死在注释里**:出处是"有覆盖价时先整体重建人民币档、美元再从人民币折算",
|
|
635
|
+
写成"在美元列上套覆盖价再折算"会差一个汇率(≈15 倍)而屏幕上只是个数字。这条差点写错,所以
|
|
636
|
+
`test/pricing.mjs` 第 4 节专门断言它。
|
|
637
|
+
|
|
638
|
+
### 这一版**还没做**的部分(用户已确认要做,随后接入)
|
|
639
|
+
|
|
640
|
+
- **账户余额**:官方 `remote.account.getBalance()` 就有(充值余额 + 赠金余额,用你已登录的账号,
|
|
641
|
+
**不需要任何 Key**),比出处那套"自己拿 API Key 发 `/user/balance`"更省事。
|
|
642
|
+
- **今日 / 本月累计**:需要宿主半聚合本地会话日志(出处是扫日志,且有"平台 dashboard 按日费用"
|
|
643
|
+
那条非官方接口 —— 你走 `go` 中转时官网账单本来也不含 Zen 的用量,所以本地聚合才是对的口径)。
|
|
644
|
+
- **设置栏**(第 8 张卡):开关 / 币种(¥/$)/ 单价覆盖 / 折叠态是否带余额。当前这一版是固定人民币、
|
|
645
|
+
只用内置刊价快照、常驻显示。
|
|
646
|
+
|
|
647
|
+
## [0.7.0] — 2026-09-28(新增「统计行」:缓存命中三位小数)
|
|
648
|
+
|
|
649
|
+
> 起因是用户看到了社区插件 [dsh-cache-precision](https://github.com/Cheng-cheng9669/dsh-cache-precision),
|
|
650
|
+
> 要求"把这个功能加到我的插件里"。那个仓库做两件事:把输入框下方统计行里的「缓存命中 12%」改写成三位小数,
|
|
651
|
+
> 并把那一行放宽(免得变长的数字被 `...` 截断)。本插件**只移植了第一件**,第二件做完真机核对后被用户拍板撤掉
|
|
652
|
+
> (前提不成立,见下"撤掉的那一半")。**只动客户端半,刷新页面即可生效**(宿主半只是多了一个设置字段)。
|
|
653
|
+
|
|
654
|
+
### 新增
|
|
655
|
+
|
|
656
|
+
- **三位小数**(`src/client/stats-line.ts` + `src/client/stats-dom.ts` + `src/client/StatsLineEntry.tsx`):
|
|
657
|
+
`缓存命中 12%` → `缓存命中 12.346%`。数字口径与官方**同一份**
|
|
658
|
+
(`缓存读 ÷(未缓存输入 + 缓存读 + 缓存写)`),所以屏幕上两个百分比永远对得上。
|
|
659
|
+
- **设置页第 7 栏「统计行」**(`src/client/SettingsSection.tsx` 末尾那张 `FoldCard`):**只有一个开关**
|
|
660
|
+
(就是标题行上那一个),**默认开** —— 用户要的是"装完立刻看得到效果"。
|
|
661
|
+
- 它是**第一栏不参与"碰过才开"迁移**的:上面五栏默认关,是因为它们已经发布过、一律默认关会在升级那一刻
|
|
662
|
+
把用户正在用的键位 / 请求头 / 终端当场关掉;这一栏是新能力,没有痕迹可依,所以按普通布尔字段处理
|
|
663
|
+
(与 `headerEnabled` 同写法,只是默认值相反)。这一条区别在两处显式钉住:
|
|
664
|
+
`src/settings-contract.ts` 的注释与 `test/quick-commands.mjs` 的 4 条断言(含变异 `AH`)。
|
|
665
|
+
- **为什么只有一个开关**:早期版本有「三位小数」/「加宽统计行」两个子开关,加宽撤掉后只剩一个功能,
|
|
666
|
+
再留一个同义的子开关就是"两个开关做同一件事"。按本插件对「OpenCode 请求头」那一栏的既有处理
|
|
667
|
+
(那里的注释:「原来的『附加请求头』本来就是『这一栏要不要生效』,直接搬到了标题行」)收成一个。
|
|
668
|
+
护栏:`test/stats-line.mjs` 断言 `DEFAULT_SETTINGS` 里没有任何第二个 `stats*` 键,
|
|
669
|
+
`test/client-registration.mjs` 与 `test/settings-render.mjs` 各有一条"别又长回来"的断言。
|
|
670
|
+
|
|
671
|
+
### 与参考实现有意不同的四处(每处都对应一条护栏)
|
|
672
|
+
|
|
673
|
+
- **① 不撒谎**:参考实现是 `toFixed(3)`,于是命中率只要 ≥ 99.9995% 就会被四舍五入成 `100.000%`,
|
|
674
|
+
而它**并没有满**。官方那段 `formatCacheHitPercent()` 专门绕开了这件事(源码注释原话
|
|
675
|
+
"without rounding a partial hit to 100%"),所以本插件是"用它的位数 + 官方的诚实性":
|
|
676
|
+
常态三位,一旦会凑成 `100.000%` 就**继续加位**,宁可显示 `99.9999%`。阶梯实测:
|
|
677
|
+
`99.995% → 99.995`、`99.999% → 99.999`、`99.9995% → 99.9995`(三位会成 100.000)、`99.9999% → 99.9999`。
|
|
678
|
+
- 判据以**官方源码为基准**逐例交叉验证:`test/stats-line.mjs` 第 5 节把
|
|
679
|
+
`packages/client/ui-chat/src/client/chat/token-format.ts`(rc.1)那 4 个函数**原样抄进来当基准**,
|
|
680
|
+
对 400 个分母全量 + 大分母边界 + 3000 组伪随机**逐例比对 `digits = 0` 档**,全部一致。
|
|
681
|
+
这一档同时就是"关掉开关时写回去的东西",所以"关掉"不是"什么都不写",而是把官方原版写回。
|
|
682
|
+
- **② 定位走官方属性,不走类名**:参考实现往上找 `className` 里含 `_root` 的祖先。DSH 的 CSS Modules
|
|
683
|
+
类名带哈希(升级就会变),所以本插件改用官方稳定属性 `[data-composer-stats]`
|
|
684
|
+
(`StatsPills.tsx` 的根元素,紧凑与详细两档都有)。
|
|
685
|
+
- **③ 不扫全页**:参考实现挂在 `document.body` 上跑 TreeWalker + MutationObserver,等于给整个页面
|
|
686
|
+
挂监听(每个流式 token 都会喂它)。本插件先渲染一个 `display:none` 的锚点落在**同一个** composer dock 里,
|
|
687
|
+
由它往上找"同时装着我和统计行"的最近祖先(**上限 6 层**),Observer 只盯这一小块;找不到就什么都不做
|
|
688
|
+
—— 宁可功能不生效,也不留一个全页监听。多 composer 的页面上也只动自己这一个。
|
|
689
|
+
- **④ 同步 `aria-label`**:官方那颗胶囊的无障碍名字是 `` `${total} · ${cacheHitText}` ``。参考实现只改可见
|
|
690
|
+
文本节点,于是**读屏用户听到的还是整数**,与屏幕上的三位小数对不上。本插件两处一起改。
|
|
691
|
+
|
|
692
|
+
### 撤掉的那一半:为什么没有移植「加宽统计行」
|
|
693
|
+
|
|
694
|
+
参考实现还给那一行写行内 `max-width`(放宽 260px,上限 `100vw - 48px`)。**真机核对后由用户拍板撤掉**
|
|
695
|
+
(2026-09-28),因为它的前提在 DSH 0.1.7 上不成立 —— 整条证据链都能在源码里读出来:
|
|
696
|
+
|
|
697
|
+
- 约束统计行的是**外层容器**,而 `--dsh-chat-content-width`(= `clamp(680px, 列宽*0.64, 920px)`)只作用在
|
|
698
|
+
**消息列与输入卡片**上:`ui-conversation` 的 `.card` 与 `.composerHero` 才有 `max-width`;
|
|
699
|
+
真正包着统计行的 `.dock` / `.composerStack` **没有任何宽度上限**(只有 `max-width: 100%`)。
|
|
700
|
+
- `.composerHero` 是**唯一**带聊天列宽度的那个容器,而它只在**空白会话**(hero)生效;统计行却只在
|
|
701
|
+
**活动会话**里渲染(`InputBar` 里那个 dock 槽要求 `variant === 'composer' && sessionId !== undefined`)。
|
|
702
|
+
两者**永不同时出现** ⇒ 统计行存在时,那一行的可用宽度是**整个会话列**,本来就比聊天列宽得多。
|
|
703
|
+
- 于是参考实现那个上限(聊天列 + 260px)反而**比可用宽度小**:平时它不生效(内容远没那么长),
|
|
704
|
+
内容极长时还会让那一行比官方**更早**截断。也就是说它无事可做、最坏还更差。
|
|
705
|
+
- 撤掉的实现留在测试里当护栏:`test/stats-dom.mjs` 第 3 节断言"只改文本、不写任何行内样式",
|
|
706
|
+
且断言契约里不再导出加宽常量;变异 `AL`/`AM` 把那一半重新加回来时必须变红(实测都咬住)。
|
|
707
|
+
|
|
708
|
+
### 变更
|
|
709
|
+
|
|
710
|
+
- **作用范围只到输入框下面那一行**(用户拍板):两处统计弹窗(点开统计行的弹窗、每轮用量弹窗)里的百分比
|
|
711
|
+
保持官方原样。它们用的是 `<dt>缓存命中</dt><dd>49.4%</dd>` 结构、拼起来是 `缓存命中49.4%`(中间没有空白),
|
|
712
|
+
与"整段必须就是 `缓存命中 xx%`"这条判据天然不冲突 —— 这条判据有 5 条"不许碰"的断言盯着。
|
|
713
|
+
- 注意弹窗自己用的是官方**1 位小数**档,所以只改那一行确实会带来"下面 12.346%、弹窗 12.3%"的口径差;
|
|
714
|
+
这是用户明确选择的范围,不是漏改。
|
|
715
|
+
- `activeSections()` 增加 `stats` 一项;「恢复默认」清掉 `statsEnabled`(清掉后回落到默认 = 开,
|
|
716
|
+
与上面五栏"清掉即关"方向相反,但"清掉 = 回到从没碰过的样子"这条定义两边都成立)。
|
|
717
|
+
|
|
718
|
+
### 护栏与验证(本轮全部真跑)
|
|
719
|
+
|
|
720
|
+
- 新增 `test/stats-line.mjs`(**52 项**,已进 `npm test`):小数语义定值 + 四个桶的口径 + 空输入 +
|
|
721
|
+
「不撒谎」属性扫描(1500+ 组"没满"的比例里没有一组显示成 100、位数一律 ≥ 3)+ 与官方源码逐例一致 +
|
|
722
|
+
文本节点与 `aria-label` 两个字符串变换(含 5 条"不许碰"、1 条"连续两次调用结果一致",防公共正则留
|
|
723
|
+
`lastIndex` 状态)+ 开关合成与定位常量。
|
|
724
|
+
- 新增 `test/stats-dom.mjs`(**17 项**,已进 `npm test`):用**按官方源码复刻的假 DOM** 钉住 DOM 那半 ——
|
|
725
|
+
定位(平铺 / 槽位套了一层包裹元素 / 第 7 层才是 body 时放弃)、改写(可见文本 + `aria-label`,
|
|
726
|
+
弹窗的 `<dt>`/`<dd>` 与同排另一个 composer 一个都不碰、紧凑档与英文界面同样认、值相同时不写)、
|
|
727
|
+
以及"不碰那一行的样式"。为此把 DOM 助手从 `StatsLineEntry.tsx` 拆成独立的 `src/client/stats-dom.ts`
|
|
728
|
+
(不 import React、顶层不碰 `document`),React 那层只剩"什么时候扫、扫完怎么收尾"。
|
|
729
|
+
- `npm test` **797 → 885 passed / 0 failed**(41+17+**298**+133+199+**52**+**17**+**102**+26);
|
|
730
|
+
`test/settings-render.mjs` **28 → 43 passed / 0 failed**(新增"七张卡都在"+ 统计行卡 5 项 × 3 档)。
|
|
731
|
+
- `test/mutation-guards.mjs` 新增 8 条,**全部咬住**:`AF`(退化成 `toFixed(3)`)/`AG`(判据放宽成"任何 xx%")/
|
|
732
|
+
`AH`(栏开关默认改成关)/`AI`(定位改成扫全页)/`AJ`(不同步 `aria-label`)/
|
|
733
|
+
`AK`(定位去掉层数上限 ⇒ 不同子树时一路走到 body)/`AL`(撤掉的加宽又写回行内样式)/`AM`(加宽常量重新引入)。
|
|
734
|
+
- 顺手修好一条**长期失配**的旧锚点:变异 `X` 的 `also` 还写着 `adoptSettings(ctx.configForms, 'get')`,
|
|
735
|
+
而代码早已被 `peekService` 包起来 —— 于是 `mutation-guards.mjs` 每跑必报"有变异没被咬住",
|
|
736
|
+
分不清是"这条护栏松了"还是"别的哪条真漏了"。锚点已对着当前源码校正,现在整套 **38 条变异全绿**。
|
|
737
|
+
- ✅ **真机验证(用户重启后当场做的,不是推断)**:
|
|
738
|
+
- 宿主进程 **16:47:07** 启动 > 产物 15:48 覆盖 ⇒ 跑的是新产物;
|
|
739
|
+
- 活宿主 schema 里 `statsEnabled` 已存在(`default: true` + `volatile`)⇒ 开关真能写;
|
|
740
|
+
- 活槽位 `conversation.composer.dock` 的 occupants = 官方 `stats`(order 0)+ 本插件
|
|
741
|
+
`composer-ux-stats-line`(order 99),**两个都 active、官方那一格没被顶替**(靠 `Slots` 探针读的);
|
|
742
|
+
- 屏幕上那一行显示 `缓存命中 97.964%` → 稍后 `97.773%`(三位小数、随用量在变);
|
|
743
|
+
- `dsh-cache-precision` **没有安装** ⇒ 不存在两个插件抢同一段文本。
|
|
744
|
+
- 产物与哈希:`lib/index.js` = `08ae98dae8774737cd241a31eded8b5b`、
|
|
745
|
+
`lib/client.js` = `53aba71728a1196ffd8f090b2e2f22af`(上一版发布版分别是 `46EEFE79…` / `9017EBC3…`)。
|
|
746
|
+
两个 profile(`desktop` 与 `web`)都已用 `rm` + `cp` 覆盖(`profiles/<p>/node_modules/dsh-composer-ux/lib/`
|
|
747
|
+
里的文件与 pnpm store 是同一 inode,`cp -f` 会**写穿** inode 污染 store —— 2026-09-25 真出过),
|
|
748
|
+
覆盖后确认 store 里那两个 0.6.2 的旧 blob 仍是 `9017EBC3…` / `46EEFE79…`、链接数已归 1。
|
|
749
|
+
- ⚠️ 真机确认后**仍待用户点头才发布**(用户选的是"确认了我再发 0.7.0"),
|
|
750
|
+
所以此刻 `npm publish` / GitHub Release / git tag **尚未执行**。
|
|
751
|
+
|
|
752
|
+
## [0.6.2] — 2026-09-25(浮层磨砂修复 + 设置页「刷新」按钮 + 默认终端「接管前自检」)
|
|
753
|
+
|
|
754
|
+
> 三块内容合成一版:①用户截图报的「快捷指令面板透明看不清」;②桌面版里「重启 DSH」不可能生效,补一个轻量替代;③
|
|
755
|
+
> 「默认终端」在官方安装的桌面版里会把会话的 shell 打死,补一道**接管前自检**(并顺带定位到两条官方缺陷)。
|
|
756
|
+
> ①与②只动**客户端半**(刷新页面即可生效);③含**宿主半**(装完需**重启 DSH**)。
|
|
757
|
+
|
|
758
|
+
### 修复
|
|
759
|
+
|
|
760
|
+
- **浮层只画了「半透明」、漏了官方成对使用的「毛玻璃」**。DSH 的菜单面是**一对**令牌:
|
|
761
|
+
`--dsw-specific-menu`(半透明填充)+ `backdrop-filter: var(--dsw-menu-backdrop-filter)`
|
|
762
|
+
(实测 `blur(40px) saturate(150%)`)。官方每一个绘制前者的浮层都**两者齐用**,形如
|
|
763
|
+
`.panel:before{background:var(--dsw-specific-menu);backdrop-filter:var(--dsw-menu-backdrop-filter)}`;
|
|
764
|
+
本插件只写了 `background` ⇒ 半透明在、磨砂不在 ⇒ 浮层成了**一块没磨砂的玻璃**,
|
|
765
|
+
背后正文原样透出来、与面板里的文字叠在一起。
|
|
766
|
+
- 判定方法(下次照抄):在 `app.asar` 的平台 CSS 里搜令牌名,看官方组件是不是**成对**出现;
|
|
767
|
+
只有填充、没有 blur 的就是漏抄。
|
|
768
|
+
- 两处一起修:`menu`(自定义档右键菜单)与 `quickPanel`(快捷指令面板)。只修一处会出现
|
|
769
|
+
「一个浮层清楚、另一个还在透」。
|
|
770
|
+
|
|
771
|
+
### 变更(同批对齐官方高层级表面)
|
|
772
|
+
|
|
773
|
+
- 浮层描边改走 **elevation 发丝线**:`border: 0` + `box-shadow: var(--dsw-elevation-prominent)` +
|
|
774
|
+
`--dsw-elevation-stroke-color: var(--dsw-alias-border-l1)`。官方高层级表面一律 `border: 0`
|
|
775
|
+
(描边由 elevation 自带);本插件原先**既有实线边框、又有 elevation 那 0.5px 描边**,
|
|
776
|
+
等于同一个边缘画了两条。显式钉 `l1` 是为了保持升级前的观感
|
|
777
|
+
(`--dsw-elevation-stroke-color` 的全局默认是 `border-l4`)。
|
|
778
|
+
- 圆角改用官方令牌 `var(--dsw-radius-lg)`(= **16px**;原为写死的 10 / 12)。
|
|
779
|
+
- **刻意不跟官方的一处**:`z-index` 保留 `9999`(官方浮层是 1100)。本插件浮层挂在 `shell.overlay`
|
|
780
|
+
这个独立层叠上下文里,要与别的插件抢层,降到 1100 只会被别人的浮层盖住(见 `src/client/panel.ts` 文件头)。
|
|
781
|
+
|
|
782
|
+
### 新增
|
|
783
|
+
|
|
784
|
+
- **设置页抬头右端增加「刷新」按钮**(在「重启 DSH」左边;`SettingsSection.tsx` 的 `RefreshButton`)。
|
|
785
|
+
起因:用户在**官方安装的桌面版**里按「重启 DSH」没反应。查证后这不是偶发故障、而是**形态不匹配**(见下),
|
|
786
|
+
于是需要一个轻量替代。它只做 `location.reload()`:不发请求、不碰宿主、**不打断正在跑的会话**
|
|
787
|
+
(会话在宿主侧活着,刷新只重建界面)。而插件的**客户端半**(设置页 / 右键菜单 / 键位拦截 /
|
|
788
|
+
快捷指令面板)本来就随页面 bundle 走 —— 重新加载页面就能生效,这正是「让改动生效」在桌面版里
|
|
789
|
+
唯一还走得通的路。
|
|
790
|
+
- 排布:轻的在前(刷新 → 重启 → GitHub);重启处于「确认中 / 重启中」时刷新一并禁用 ——
|
|
791
|
+
否则一次刷新会把用户手上那条待确认的横幅冲掉。
|
|
792
|
+
- 护栏:`test/settings-render.mjs` 新增第 6 节 —— **真渲染**出页面,再比较两枚按钮的先后,
|
|
793
|
+
并按交接文档 §7.25 的教训不满足于"出现过"。该套件 24 → **28 项**。
|
|
794
|
+
- **接管「默认终端」之前先自检**(`src/terminal/tool.ts` 的 `probeBashExecution` + `src/terminal/host.ts` 的门控
|
|
795
|
+
+ `src/terminal/contracts.ts` 的 `probeFailed` 状态文案)。起因:官方安装的**桌面版**在**受限文件策略**下,
|
|
796
|
+
命令要过沙箱 runner,而 runner 是用 `process.execPath`(= `DeepSeek Harness.exe`,不是 node)起的,
|
|
797
|
+
实测每条命令 `0xC0000142`(DLL 初始化失败)**零输出**;照旧接管会得到
|
|
798
|
+
「`restrict(pwsh)` 已生效 + 自带的 bash 又跑不动」= 该会话**一个能用的 shell 都没有**(比不接管更糟)。
|
|
799
|
+
- 探针走的是**与真工具完全相同的执行路径**(借道 `createBashTool(...).execute(...)`:同一份 argv 构造、
|
|
800
|
+
同一个沙箱 `confine`、同一个 `subprocess.spawn`),且不带 `sandbox_permissions` —— 自检不该弹审批。
|
|
801
|
+
- 不通过就**不接管**(不 restrict、不注册、不加提示词段),保持 PowerShell,并把原因写进状态行
|
|
802
|
+
(「已启用,但自检未通过:… —— 为避免把会话的 shell 打死,暂不接管」)。
|
|
803
|
+
- `danger-full-access` **不做**探针:那条路会跳过 `confine()`,同一台桌面版上实测正常
|
|
804
|
+
(`uname -a` → `MINGW64_NT-10.0-26200 … Msys`)。结论按 `路径|模式` 缓存,换路径/换模式才重探。
|
|
805
|
+
- 护栏:`test/terminal-policy.mjs` 新增 7 组共 23 项(该套件 176 → **199 项**),
|
|
806
|
+
`test/mutation-guards.mjs` 新增 `AB`(拆掉门控)/`AC`(不看模式)/`AD`(门控退回部署默认)/
|
|
807
|
+
`AE`(不再补 `ELECTRON_RUN_AS_NODE`)——**四条都咬住**。
|
|
808
|
+
- **打包形态的 spawn 环境补 `ELECTRON_RUN_AS_NODE=1`**(`createBashTool` 的 `electron` 选项,默认看
|
|
809
|
+
`process.versions.electron`):Windows 上受限模式的沙箱 runner argv[0] 是 `process.execPath`
|
|
810
|
+
(`dsh-sandbox-local/lib/index.js:539`),桌面版里那是 Electron 的 exe;用 Electron 跑脚本必须带这个变量,
|
|
811
|
+
否则 runner 根本起不来(实测零输出 / `0xC0000142`)。补上之后,受限模式下探针拿到的是 **bash 自己的报错**
|
|
812
|
+
(`couldn't create signal pipe, Win32 error 5`),状态行不再是含糊的"零输出"。
|
|
813
|
+
对 `bash.exe` 本身无副作用(它不认这个变量)。⇒ 它**不会**让 Git Bash 在受限模式下可用,只是把诊断说准。
|
|
814
|
+
- 🔎 **同轮定位到两条官方缺陷**(与本插件无关,已写成可直接提交的报告:
|
|
815
|
+
工作区 `给官方的缺陷报告-桌面版受限沙箱.md`):① 打包版受限模式下沙箱 runner 用 `process.execPath`
|
|
816
|
+
启动却没人给 `ELECTRON_RUN_AS_NODE`(影响**任何**经沙箱 spawn 的命令);② MSYS2(Git Bash)在
|
|
817
|
+
Windows ACL 沙箱下启动即崩(`couldn't create signal pipe, Win32 error 5`),而同一沙箱下
|
|
818
|
+
`cmd` / `pwsh` 实测都正常。
|
|
819
|
+
- ⚠️ **真机首验就抓到我实现里的一个 bug**:门控最初调 `sandboxPolicy.resolve({})`,拿到的是
|
|
820
|
+
**部署默认**(base bundle:`process.env.DSH_PERMISSION_MODE ?? 'workspace-write'`),而工具执行时用的是
|
|
821
|
+
**该会话自己**的策略(`{ session: exec.agent.session }`)⇒ 一个 `danger-full-access` 的会话被误判成受限、
|
|
822
|
+
自检按预期失败、整栏被白白撤掉(真机上确实发生了一次:工具表里 `bash` 消失)。已改成逐会话解析
|
|
823
|
+
(`confinedModeOf(deps, agent)`),并加"门控把 session 交给了 sandboxPolicy.resolve"这条断言 + 变异 `AD` 钉住。
|
|
824
|
+
- ✅ **真机验证(同一轮,用户配合)**:会话切到受限策略后,门控按预期调到探针、探针拿到
|
|
825
|
+
`0xC0000142`(零输出)——**§7.32 的根因被独立复现**;而这次是**探针替会话死了一次**:插件不接管、
|
|
826
|
+
状态行写「已启用,但自检未通过…」、模型侧保住 PowerShell(`terminalEffective: pwsh`)。
|
|
827
|
+
这正是这道护栏要的效果:不必先把自己的 shell 打死才知道那条路是死的。
|
|
828
|
+
- 完整静态兼容性核对见工作区 `兼容性测试-composer-ux-0.6.1-vs-desktop-0.1.7-rc.2.md`;
|
|
829
|
+
机制背景见交接文档 §7.32 与 §7.34。
|
|
830
|
+
|
|
831
|
+
### 说明(⚠️ 发版前必须补做)
|
|
832
|
+
|
|
833
|
+
- **「重启 DSH」在官方安装的桌面版里不可能生效**(2026-09-25 查证,属机制性限制、不是 bug):
|
|
834
|
+
它重放的是「`node <bin.js>` 或裸 `dsh`」(`src/restart.ts` 的 `launchCommand`),而桌面版宿主是
|
|
835
|
+
**Electron 应用**(`DeepSeek Harness.exe`),两个条件都不成立;`~/.dsh/launcher/_boot.log` 里
|
|
836
|
+
反复出现「无法定位 dsh CLI(dsh 安装目录未找到)」正与此吻合,且重启助手写下的诊断日志
|
|
837
|
+
自 **2026-09-23** 起一条都没有。**本轮没有改它的行为**(用户只选了"加刷新")。
|
|
838
|
+
- **本机的活 profile 是 `profiles/desktop`,不是 `profiles/web`**(详见交接文档 §7.32):
|
|
839
|
+
用户这次「默认终端」开关的写入落在 `profiles/desktop/cordis.patch.yml`。所以本机交付要覆盖
|
|
840
|
+
**两个**已安装副本的 `lib/client.js`(`profiles/desktop/…` 才是活的那份,`profiles/web/…` 一并覆盖以免踩错)。
|
|
841
|
+
- **构建与测试本轮已真跑**(不再是"手工同步"):`node build.mjs` → `npm test` = **774 passed / 0 failed**;
|
|
842
|
+
`node test/settings-render.mjs` = **28 passed / 0 failed**。仓库与两个 profile 的 `lib/client.js`
|
|
843
|
+
md5 一致 = `9017EBC3EBAA2F02427C8BF415461389`(`lib/index.js` 未变,仍是 `955C23B7…`)。
|
|
844
|
+
⚠️ **上面这组数字与哈希是"加自检探针之前"那一轮的**。探针 + 环境变量两处改动(<sup>2026-09-25</sup>)之后重跑:
|
|
845
|
+
`npm test` = **797 passed / 0 failed**(41+17+294+133+**199**+87+26)、`test/settings-render.mjs` = 28;
|
|
846
|
+
`lib/client.js` md5 **仍是** `9017EBC3EBAA2F02427C8BF415461389`(客户端半一字未动),
|
|
847
|
+
`lib/index.js` md5 **已变为** `46EEFE792622BE82B9C34A8524BAB12D`(宿主半:探针 + 逐会话门控 + 打包版 env)
|
|
848
|
+
⇒ 交付时两个 profile 要按新的 `lib/index.js` 覆盖(**只换 client.js 不够了**)。
|
|
849
|
+
- ⚠️ **覆盖产物有个 pnpm 硬链接的坑**(2026-09-25 实测踩到):两个 profile 的
|
|
850
|
+
`node_modules/dsh-composer-ux/lib/index.js` 曾经**共用同一个 inode**(pnpm 硬链接,link count=4,
|
|
851
|
+
其中还包含 pnpm store 里的 blob)⇒ 直接 `cp -f` 会**穿透 inode 写入**,一次就改了全部链接、
|
|
852
|
+
连 store 都被污染(以后 `pnpm install` 该包会装到改过的内容)。
|
|
853
|
+
**正确做法**:先 `rm` 再 `cp`(断开硬链接,各持独立 inode),并在改动后核对
|
|
854
|
+
store blob 的 md5 仍是原始值(`955C23B76D69D90DA8484D5F17B19DA6`);
|
|
855
|
+
store 文件名是**内容 sha512**(`files/<前2位>/<后128位>`),内容被改过就对不上名。
|
|
856
|
+
|
|
857
|
+
## [0.6.1] — 2026-09-24
|
|
858
|
+
|
|
859
|
+
> 起因是一次真机故障:设置页的开关"点了没反应",而界面上**一句提示都没有**。查到最后是两层独立原因,
|
|
860
|
+
> 两层都在这一版里处理掉。**含宿主半改动**(启动时新增一次孤儿锁回收),装完需要**重启 DSH**。
|
|
861
|
+
|
|
862
|
+
### 修复
|
|
863
|
+
|
|
864
|
+
- **volatile 标记改成逐字段(不再标根),对齐官方形状**。这是"设置改完不生效"的根因:
|
|
865
|
+
schemastery 3.18.4 一旦看到**根**带 volatile,就把**整棵解析结果**包成**一个根引用**
|
|
866
|
+
(`Config({}).enabled === undefined`,值只在 `.get()` 里);而 loader 把配置变更提交回
|
|
867
|
+
**运行中引用**的那条路(`_commitVolatile`)是按**叶子路径**设计的。官方插件
|
|
868
|
+
(`ui-theme` / `locale` / `llm-deepseek` / `ui-conversation` …)**一律逐字段标,没有一个标根**。
|
|
869
|
+
改完的形状是"普通对象壳 + 叶子引用",真机验证:`Config({})` 不再是引用,`parsed.enabled` 是叶子引用。
|
|
870
|
+
- 顺带把两条护栏补成"字段级":`host-settings-generations` 新增「每个字段都带标记」「根节点不标」,
|
|
871
|
+
变异 Z 改为动**方法分支**(3.18.4 下标记走 `.volatile()`,动 meta 兜底那行已经不参与执行、
|
|
872
|
+
护栏会"咬不住"——这是升级后的新事实,之前那条变异因此失效)。
|
|
873
|
+
- **孤儿写入锁会让整个 profile 的设置写入静默失效**。DSH 用 `<profile>/package.json.lock` 串行化跨进程写入,
|
|
874
|
+
且规定争用方**永不删除**已存在的锁(孤儿锁由操作者处理);而 `restart-webui.bat` 的 `taskkill /T /F`
|
|
875
|
+
硬杀只要落在一次写入中间,锁就永久留下 —— 此后**每一次**设置写入都在 2 秒后超时,读得到、写不进,
|
|
876
|
+
界面只表现为"点了没反应"(2026-09-23 连撞两次,两次都是重启留下的)。
|
|
877
|
+
- 新增 `src/settings-lock.ts`:宿主半启动时读一次锁里的 PID,**只在确认该进程已不存在时**删除;
|
|
878
|
+
内容认不出 / PID 还活着 / 权限不足无法判定 → 一律不动(误删活锁会让两个写入者交错提交同一个 profile patch)。
|
|
879
|
+
判据是纯函数,逐条钉在测试第 12 节(19 条)。
|
|
880
|
+
- **设置页不再"静默装死"**:写入被宿主拒绝、或**写入被接受但运行时的值没变**时,把原因显示在设置卡顶部
|
|
881
|
+
(`writeNotice`,与「重启 DSH」横幅同一位置)。以前这两种情况都只往控制台写一行,用户能看到的只有"开关不动"。
|
|
882
|
+
|
|
883
|
+
### 文档
|
|
884
|
+
|
|
885
|
+
- README「设置持久化」补「⚠️ 设置写不进去 / 点了没反应:两种成因与恢复」(含手工回收锁的一行命令)。
|
|
886
|
+
- README 更正一处过期描述:0.1.7 起设置存在 **profile 的 `cordis.patch.yml`**,不再是 `$DSH_HOME/settings.yaml`。
|
|
887
|
+
|
|
888
|
+
### 测试
|
|
889
|
+
|
|
890
|
+
- `test/quick-commands.mjs` 新增第 12 节:孤儿锁判据 + 真实文件系统上的四条路径(无锁 / 认不出 / 持有者活着 / 持有者已死)。
|
|
891
|
+
- 适配 DSH 随包的 **schemastery 3.18.4**:它开始认 `meta.volatile` 并在解析时把节点包成**引用**
|
|
892
|
+
(3.18.2 没有这个分支)。于是"从 schema 解析结果直接读普通值"的断言(`quick-commands` 第 6 节、
|
|
893
|
+
`host-header-mirror` 第 15 节、`host-settings-generations` 第 1 节)改为先解引用;断言要分的那几件事没有变。
|
|
894
|
+
|
|
895
|
+
## [0.6.0] — 2026-09-22(**未单独发布**,内容并入 0.6.1)
|
|
896
|
+
|
|
897
|
+
> **适配 DSH 0.1.7**(对 `dsh-v0.1.7-alpha.1` 逐项核对)。同一个产物同时支持 0.1.6 与 0.1.7:
|
|
898
|
+
> 两代的设置服务形状不同,代码按**能力探测**分支,不维护两份产物。
|
|
899
|
+
> **本版含宿主半改动**(注册方式、字段表、事件名),装完必须**重启 DSH** 才生效。
|
|
900
|
+
>
|
|
901
|
+
> ⚠️ **0.1.7 自己的升级动作会搬走 `~/.dsh/settings.yaml`**:它把该文件改名成 `settings.yaml.imported`
|
|
902
|
+
> 并逐节导入 profile patch(**一次性、部分导入过的文件不会重跑**)。本插件这一段在 0.1.7 上
|
|
903
|
+
> **导不进去**(那时插件还没有 Config,服务报 `No configurable plugin entry`),所以旧值只会留在
|
|
904
|
+
> 那个 `.imported` 文件里;0.1.6 又会重新生成一份干净的 `settings.yaml`。
|
|
905
|
+
> 结果就是:**升级到 0.1.7 之前,请先备份 `settings.yaml`(或那个 `.imported`),旧值需要手动迁回。**
|
|
906
|
+
|
|
907
|
+
### 破坏性变更
|
|
908
|
+
|
|
909
|
+
- **删掉「设置面板 → 导航滚动」这一栏**:DSH 0.1.7 的官方设置页已给导航列自带 `overflow-y: auto`
|
|
910
|
+
(`ui-settings-general` 的 `.navList`),本插件再注入一套就是重复实现。整项删除:字段 `panelScroll`、
|
|
911
|
+
`html.dsh-ux-panel-scroll` 那组注入样式、标题行那个小开关、栏判据里的那一条。**「边缘缩放」照旧保留。**
|
|
912
|
+
- 迁移影响:只开过「导航滚动」、从没拖过尺寸的旧文档会被判成"这一栏从没碰过" ⇒ 该栏回到默认关。
|
|
913
|
+
这是刻意的:导航滚动已经不存在,那一栏剩下的唯一功能是缩放,而这些人从没用过它。
|
|
914
|
+
- 旧文档里残留的 `panelScroll` 不会让升级失败:schemastery 对**未声明键是原样放行**(已实测),
|
|
915
|
+
所以不需要为它留一个宽容字段。
|
|
916
|
+
|
|
917
|
+
### 适配(DSH 0.1.7)
|
|
918
|
+
|
|
919
|
+
- **两代设置服务都认领**(客户端半):0.1.6 及以前是 `ctx.settingsScope.bind({ namespace })`,
|
|
920
|
+
0.1.7 起是 `ctx.configForms.get(namespace)`。两者**不能同时写进静态 `inject`** —— 各自在对方那一代
|
|
921
|
+
永远不出现,插件会静默等依赖、**整块不挂载**(设置页、键位、右键菜单、快捷指令一起消失)。
|
|
922
|
+
所以静态 inject 只留 `slots`,两个名字各用 `ctx.inject` 等一次,再加一次"直接读一次"兜底。
|
|
923
|
+
- **宿主半按能力分支注册**:`settings.register` 存在才注册(0.1.6);0.1.7 的命名空间由导出的 `Config` 推导。
|
|
924
|
+
- **"自己那一行"换了读法**:0.1.7 没有 `settings.get()`。自己的行从 Config 读(volatile 引用要解引用),
|
|
925
|
+
别人的行(只读 llm-pi-ai 的 providers.headers)从 `describe()` 读;两代差异收口在 `makeReader()`。
|
|
926
|
+
- **事件名换代**:0.1.6 发 `settings/updated`,0.1.7 发 `settings/document-updated`(旧名在 0.1.7 里
|
|
927
|
+
**整个不存在**)。两个都听,任一世代只会有一个触发。
|
|
928
|
+
- **manifest 的 `dsh.client.inject` 改成包行名**:`["slots","settingsScope"]` → `["@deepseek-ai/dsh-client-ui-settings"]`
|
|
929
|
+
(0.1.7 起这个字段的语义是"本行物化前必须先到位的包行",写服务名没有意义)。
|
|
930
|
+
- **volatile 标记靠数据、不靠方法**:宿主半把 schemastery **内联**进产物(取自构建时的 DSH 检出),
|
|
931
|
+
所以"在 0.1.6 上构建出来的包"里没有 `.volatile()`(它是 schemastery 3.18.3 才加的)。
|
|
932
|
+
改为直接写 `schema.meta.volatile = true` —— 0.1.7 的设置服务读的正是这个标记,与构造它的库版本无关。
|
|
933
|
+
0.1.7 里**只有 volatile 字段会被服务、也只有 volatile 路径能被写入**,所以当时把整棵 Config 标了 volatile
|
|
934
|
+
(**这一做法在 0.6.1 里被推翻:改成逐字段标记** —— 根标记在 schemastery 3.18.4 下会让"写入不生效",见上方 0.6.1 段)。
|
|
935
|
+
|
|
936
|
+
### 提示词优化改成「条目 + 逐字依据」(机制取自对方 0.6 线)
|
|
937
|
+
|
|
938
|
+
- **背景(核对过的实话)**:0.5.x 那三档文本逐字取自对方 `lib/index.js` 的 **`LEGACY_V011`** ——
|
|
939
|
+
该常量自己的注释写着"逐字节取自 0.1.1 的 system 提示词",且只在**回退策略** `v011-ptc` 下使用;
|
|
940
|
+
对方默认策略是 `STRATEGY_DEFAULT = 'v6'`(`buildSystem()` 走 `LANG_ONLY_SYSTEM` /
|
|
941
|
+
`STRATEGY_V5_SYSTEM` + `CLOSING_SELFCHECK` + 收件人契约)。**即:抄到的是对方历史回退路径上的那一层。**
|
|
942
|
+
更根本的问题是:自由改写器下"模型替用户发明一条需求"在结构上无法被发现,
|
|
943
|
+
提示词里写多少遍"不许新增"都只是空话。
|
|
944
|
+
- **机制照对方 0.6 线重写**(`po06/lib/interpreter.js` 的 `validateProvenance`、
|
|
945
|
+
`po06/lib/compiler.js` 的固定节序 + 预算丢弃):
|
|
946
|
+
- 模型不再自由改写,只产出条目;`rewrite` / `requirement` / `quality` **必须**附一段在用户原话里
|
|
947
|
+
**逐字存在**的引文,宿主做字面子串比对,**对不上就只丢那一条**并记账(单点不得废整轮——
|
|
948
|
+
这条纪律是对方 2026-09-22 真机回归换来的);找不到逐字匹配时退一步做**空白归一**比对,
|
|
949
|
+
免得折行/多空格复述白白丢条目。
|
|
950
|
+
- `rewrite` 按引文在原话里的**位置**回填(右侧先动,下标不串位),没被覆盖的原话**原样保留**,
|
|
951
|
+
其余条目按固定节序追加在末尾、每条带自己的逐字依据 —— 于是"凭空加需求"拿不出依据。
|
|
952
|
+
- 篇幅预算按档位给(普通 1.4 倍 / 高级 2.6 倍 / 极端 4 倍,另有绝对上限),超预算按固定顺序丢
|
|
953
|
+
**可选的节**、必保节永不丢,并把"省略了几条"写进成品(降级要出声,不静默截断)。
|
|
954
|
+
- 空产出**重试一次**(话术取自对方 `retryEmpty`,它来自对方真机"思考完成却没有产出")。
|
|
955
|
+
- **两条兜底**:模型没按信封输出 → 按旧行为**整段照收**(保证这次改造不会让原本能用的优化变成失败);
|
|
956
|
+
输出像信封但半截坏掉 → **直接失败**,绝不把坏 JSON 写进输入框(这是真机上最伤人的一种"优化")。
|
|
957
|
+
- **新增**:设置页每一档的提示词**可编辑**(`optimizerPromptBasic/Advanced/Extreme`,留空 = 用内置),
|
|
958
|
+
带「载入内置版」「恢复内置」,并显示插件固定追加的那段输出契约;状态行如实回报
|
|
959
|
+
`N 条补全 · 丢弃 M 条 · 重试过一次 · 自定义提示词`。**JSON 输出契约不由自定义提示词控制**
|
|
960
|
+
(它是"逐字依据"能被校验的前提)。
|
|
961
|
+
- **未采纳(如实记录)**:对方的会话上下文读取(回合 0–10 / 全文)、只读工具(read/glob/grep)、
|
|
962
|
+
发送前拦截 + 原话不改 + 随行包注入、每会话隔离与 `po06.json` 状态文件 —— 这些都要新的服务接线或
|
|
963
|
+
会改掉本插件"点按钮 → 回填输入框"的既有交互,本次不动。
|
|
964
|
+
- 提示词仍是三档(普通 / 高级 / 极端)与原有温度(0.2 / 0.3 / 0.3),**对外接口未变**:
|
|
965
|
+
`POST /composer-ux/optimize` 的请求体与 `ok/text/error/provider/model/truncated` 字段照旧,
|
|
966
|
+
新增字段(`itemCount` / `dropped` / `warnings` / `fallback` / `retried` / `promptSource` / `budget` / `chars`)
|
|
967
|
+
都是**附加**的,老客户端不读也不会坏。
|
|
968
|
+
|
|
969
|
+
### 测试
|
|
970
|
+
|
|
971
|
+
- 新增 `test/settings-service-adopt.mjs`(23 项):用真产物跑三种服务组合(只有 `settingsScope` /
|
|
972
|
+
只有 `configForms` / 两个都没有),并钉住"静态 inject 只能写 `slots`"。
|
|
973
|
+
- 新增 `test/host-settings-generations.mjs`(14 项):两代服务形状各跑一遍宿主半,钉住
|
|
974
|
+
`Config.meta.volatile`、0.1.7 形状下"没有 register 也不抛"、"Config 读不出来要退回 describe"。
|
|
975
|
+
- 变异护栏 21 → 26 条:新增 W(静态 inject 绑回某一代)、X(0.1.7 的认领被删)、
|
|
976
|
+
Y(register 不按能力调用)、Z(volatile 标记丢失)、AA(Config 空值不退回 describe)。
|
|
977
|
+
- 提示词优化这一块:`test/quick-commands.mjs` 从 3 节扩到 4 节(新增「依据校验与宿主装配」),
|
|
978
|
+
逐条钉住"引文对不上 → 只丢那一条并记账"「原文没被改写覆盖的部分原样保留」「半截 JSON 绝不写回」
|
|
979
|
+
「没给信封 → 整段照收兜底」「空产出重试一次」「自定义提示词不顶掉输出契约」等 50+ 项。
|
|
980
|
+
|
|
981
|
+
## [0.5.0] — 2026-09-21
|
|
982
|
+
|
|
983
|
+
> **本版含宿主半新能力**(六个折叠卡的栏开关进了 settings schema),装完必须**重启 DSH** 才会作用到会话上。
|
|
984
|
+
> 只动客户端半的改动(设置页版式、右键菜单卡那两处)刷新页面即可。
|
|
985
|
+
|
|
986
|
+
### 新增
|
|
987
|
+
|
|
988
|
+
- **两层开关:总开关 + 每栏开关**(用户要求「插件的每个条目都增加一个开关按钮默认关闭,总开关默认打开」)。
|
|
989
|
+
- 设置页顶部「启用输入增强」= **总开关,默认开**(原本就是),它是一道总闸;六张折叠卡的**标题行右端各一个卡级开关,默认关**。生效条件是 `enabled && 该栏开关`,判据只有一个 `activeSections()`,不在各组件里各写一遍。
|
|
990
|
+
- **卡内已有的开关全部搬到标题行**(用户要求),其中**右键菜单那 7 项后来又搬回了卡内**(见下一条):设置面板的「导航滚动 / 边缘缩放」变成标题行的两个小开关;默认终端的三档变成标题行的 `compact` 胶囊;**OpenCode 请求头那一栏没有新键** —— 原来的 `headerEnabled` 本来就是"这一栏要不要生效",直接当卡级开关,不造同义的第二个键。
|
|
991
|
+
- **右键菜单卡:点哪档只显示哪档的说明 + 那 7 项只在「自定义」档出现**(用户看过真实界面后提的两条)。
|
|
992
|
+
- 原来三档说明 + 剪贴板授权注意事项一共 6 段**全铺开**,一屏文字、还要人自己找哪段跟自己选的那档有关;现在 `官方 / 浏览器 / 自定义` **各一段互斥渲染**,「自定义」那段后面才接它专用的 Chrome / Edge、Firefox 剪贴板授权说明。
|
|
993
|
+
- 那 7 个条目开关(撤销 / 重做 / 剪切 / 复制 / 粘贴 / 删除 / 全选)先做成标题行的「**7 项 ▼**」条(7 个滑块平铺会把标题行挤爆),后来改成**平铺在「自定义」档里**:这 7 项本来就只对「自定义」档有意义,摆在标题行等于在任何档位都能改一堆当时不生效的东西。strip 那一套(按钮 + 展开条 + `dsh-ux-stripButton` / `dsh-ux-cardStrip` 两个类)**整套删掉**,并留了"不许复活"的护栏(变异 Q 咬住)—— 设置页布局没有自动化测试,护栏只能保到这里,挤不挤得下由用户的眼睛判。
|
|
994
|
+
- 顺手删掉标题行下面那行**重复当前档位**的正文 `hint`(`MENU_MODES[].hint` 仍是三枚胶囊的悬停提示,只是不再有第二处正文)。
|
|
995
|
+
- 标题行因此从"整行一个 `<button>`"改成"展开按钮 + 控件区"两个兄弟节点:`<button>` 里塞按钮是无效 HTML,点击还会冒泡成"展开整张卡"。
|
|
996
|
+
- **关掉一栏 = 这一块完全不介入**,栏内的值全部保留(打开即原样恢复):键位 → DSH 原生键位;右键菜单 → 不介入;快捷指令 → 输入框那枚按钮消失、面板收掉、**附加批次直接给空**(键位发送与官方发送按钮两条路共用这一个闸);设置面板 → 不滚动、不拖大小;OpenCode 请求头 → 停止注入并撤销已写入的头;默认终端 → 宿主半撤销已下发的 `pwsh` 压制并回写"未启用"。
|
|
997
|
+
- **未启用时的观感**:概览行加「未启用 · 」前缀、卡片描边变虚线、展开区压暗;栏内控件仍可编辑(先配好再打开)。
|
|
998
|
+
- **「默认关」对老用户是迁移出来的,不是硬编码**(用户拍板 A 方案)。
|
|
999
|
+
- 判据是**"这一栏的值不等于从没碰过的样子"**(改过键位 / 选过浏览器或自定义菜单档 / 有面板尺寸记录 / 终端档位不是自动或填过路径)。全新安装(什么痕迹都没有)才是六栏全关。
|
|
1000
|
+
- ⚠️ **不能按"键存在"判断**:`settings.get()` 给的是**已解析**的值(schema 默认值 + 组合 base + 用户层),从没碰过的人的文档里也照样有 `sendKey: 'Enter'`、`panelScroll: true` —— 按"键存在"判断会让全新安装六栏全开,与"默认关"正好相反。这条被测试当场逮住(「全新安装(空文档)→ 五栏全关」变红),判据改成"出现过 **且** 不等于默认值"。
|
|
1001
|
+
- 同理,**宿主半自持字段不许当判据**(`terminalCandidates` / `terminalStatus` / `terminalEffective` / `headerApplied*` 是插件自己写的,全新安装也会出现),有专门一条测试盯着。
|
|
1002
|
+
- **五栏开关在 schema 里是 `.required(false)`、故意不给默认值**:schemastery 对缺省的可选键会**整个省掉**,所以"文档里没有这个键"与"明确关掉"能分开 —— 迁移要用的就是这个区别。有测试专门钉住"缺省 ≠ false"(谁把它改成 `.default(false)`,迁移会静默失效)。
|
|
1003
|
+
- **「快捷指令」栏多一条文件判据**:0.3.0 起条目搬到了 `quick-prompts.json`,用过的人和没用过的人的设置文档可以一模一样。所以宿主半启动时读一次书本文件,**有非内置内容就把 `quickEnabled: true` 写回文档**(写回之后两端都只需要看那一个布尔)。只在文档里还没有这个键时写:用户明确关掉之后不会被重新打开(有测试)。
|
|
1004
|
+
- **升级前可以先自查**:新增 `test/check-sections.mjs` —— 拿真实的 `settings.yaml` 跑一遍迁移,打印六栏会变成什么,**只读**、不写任何文件。本机实测输出:六栏全开(「快捷指令」靠文件判据)。
|
|
1005
|
+
- **「设置 → 输入体验 → 默认终端」**(只对 Windows 生效):把模型用的终端工具从 PowerShell(`pwsh`)换成 **Git Bash**。
|
|
1006
|
+
- **三档**:自动(探测到 Git Bash 就用)/ Git Bash / PowerShell(保持 DSH 默认、本插件完全不介入)。默认「自动」。
|
|
1007
|
+
- **「裸本体」不列为候选**:同根下既有 `bin\bash.exe` 又有 `usr\bin\bash.exe` / `mingw64\bin\bash.exe` 时只列前者。本机实测:前者 `MSYSTEM=MINGW64`、`PATH` 前置 `/mingw64/bin:/usr/bin`、`head`/`grep`/`uname` 都在且中文文件名当参数正常;后者 `MSYSTEM` 为空、`PATH` 只有继承来的 Windows PATH,**coreutils 全部 command not found**(交给模型就是命令大面积失败)。没有 `bin\bash.exe` 兄弟的来源(如 MSYS2 只提供 `usr\bin\bash.exe`)照常列出;**手填路径不受此限**。
|
|
1008
|
+
- **探测顺序**(同一路径只留优先级最高的一次,候选会在卡片里列出并标注来源):设置里填的路径 → **PATH 上正在用的那份 git(由 `git.exe` 反推安装根)** → Git 官方落点(`Program Files` / `x86` / `%ProgramW6432%` / **`%LOCALAPPDATA%\Programs`**——安装器以普通用户运行时的默认落点)→ **Scoop / Chocolatey 便携包** → **GitHub Desktop 内嵌 / 旧 GitHub 的 `PortableGit_*` / Visual Studio 内嵌**(带版本号,靠列举子目录,新版本优先)→ MSYS2 / Cygwin → **各盘符根下的 `Git` / `PortableGit` / `msys64` / `cygwin64`** → Niubash(`niu.exe`)→ PATH 兜底。这份清单是**联网核对过**的([SO: Where is git.exe located?](https://stackoverflow.com/questions/11928561/where-is-git-exe-located)、[SO: Why was git installed in AppData](https://stackoverflow.com/questions/32297340/why-was-git-installed-in-appdata-instead-of-program-files)、[cli/cli#2617](https://github.com/cli/cli/issues/2617) 等),并逐条写了测试。探测只做有限次存在性检查 + 三处目录列举,**不递归扫盘、不跑进程**。
|
|
1009
|
+
- **硬排除 WSL 的 `bash.exe`**(`System32` 与 `WindowsApps` 两个来源)——它把 `D:\x` 解释成 `/mnt/d/x`,与模型手里的 Windows 工作目录不兼容,而且只有在它**确实存在**时才会出现在「已排除」说明里。
|
|
1010
|
+
- 本机就是这条规则的受益者:Git for Windows 装在 `D:\Git`(不在 Program Files),只按固定目录找是找不到的——「先找 git 再反推」才能命中 `D:\Git\bin\bash.exe`。
|
|
1011
|
+
- **立刻生效**:改档位后宿主半会**遍历所有在跑会话**重新下发,不需要开新会话(详见下节「为什么这样实现」)。
|
|
1012
|
+
- **工具契约与官方 `@deepseek-ai/dsh-tool-bash` 逐字对齐**:描述、参数与输出 JSON Schema、`[stderr]` 头、`(no output)` 兜底、标记顺序(拒绝 → 升级提示 → 超时 → signal 或 exit code 在**最末**)、终端卡片(exit 状态拆成 pill)、后台任务(`{kind:'background', jobId}` + `job_output` / `job_kill`)、超时收敛(默认 120s、上限 600s)、输出截断与落盘位置、沙箱约束与**升级审批**(fail-closed、严格更宽、审批文案)。
|
|
1013
|
+
- **零新增运行时依赖**:宿主半仍是自包含产物(只内联 schemastery/cosmokit),官方的五个运行时符号(`defineTool` / `TOOL_ABORTED` / `HarnessError` / 沙箱标记与升级助手 / `DSH_ENV_PREFIX`)在 `src/terminal/` 下等价实现,避免因「依赖共享宿主包」被市场体检警告。
|
|
1014
|
+
- **失败不伤会话**:探测不到、拿不到 `subprocess`、`restrict` 被拒(例如该会话本来就看不到 `pwsh`)、非 Windows —— 一律只降级 + 在设置页状态行写明原因,不抛错、不 veto 别的插件。
|
|
1015
|
+
- 设置页只读展示三项宿主半自持字段(`terminalStatus` / `terminalEffective` / `terminalCandidates`),并加了「自动发现」按钮与候选点选。
|
|
1016
|
+
- 设置接口 `GET/POST /composer-ux/terminal`:查状态、重扫候选。与另外两条路由一样受官方 `connection.requestRejection` 守卫(本机 webServer 绑 `0.0.0.0`,而这条接口会回传本地路径)。
|
|
1017
|
+
- 设置页抬头(「输入体验」那张卡)右端加了 **「GitHub ↗」**:真实 `<a target="_blank" rel="noreferrer noopener">`,地址取自契约里的 `REPO_URL`(与 `package.json` 的 `repository.url` 一致,客户端不硬编码第二份 —— 有测试盯着两处不许分叉)。
|
|
1018
|
+
- **「重启 DSH」按钮**(`src/restart.ts` + `GET/POST /composer-ux/restart`):位置是「输入体验」卡片**抬头右端、GitHub 链接左边**,确认条展开在抬头正下方。
|
|
1019
|
+
- **机制照搬插件市场 [dsh-market](https://github.com/dsh-market/dsh-market)**(`src/restart.ts` + `src/dsh-cli.ts`,本机 profile 里就有它的源码与注释)。为什么抄它而不是自己发明:它文件顶部挂着一串 issue 号,每条都是"重启按钮按下去没用"的具体死法。
|
|
1020
|
+
- **两步确认**(用户拍板保留):点按钮先读"会怎么重启、当前有几个会话在跑",确认条在抬头正下方,点「确认重启」才真重启 —— 重启会打断正在跑的会话(包括正在生成的那一轮)。
|
|
1021
|
+
- **分离一个 node 助手进程**(`node -e <源码>`,detached + unref + `stdio: 'ignore'`),宿主自己 **500ms 后退出**,好让这个 HTTP 响应先发出去。
|
|
1022
|
+
- **助手等端口真的空出来**:每 250ms `connect` 探一次、最多 30 秒,通了再等 300ms(Windows 的 TIME_WAIT 尾巴)。用 connect 而不是 bind:bind 一下自己就把那个马上要交出去的端口占住了。固定 sleep 会让新宿主 `EADDRINUSE` 当场死掉、而失败还被 `catch {}` 吞了(市场 issue #177)。
|
|
1023
|
+
- **用隐藏控制台的 PowerShell 起新宿主**:Windows 上 `detached` = `DETACHED_PROCESS` = **没有控制台**,新宿主之后起的每个控制台子进程都会新建一个可见窗口(#40);而 `-WindowStyle Hidden` 管不到 spawn 交给 PowerShell 的那个控制台,所以助手那层还得自己带 `windowsHide`(CREATE_NO_WINDOW,#624)。裸 `dsh` 要显式补成 `dsh.cmd`:PowerShell 会优先选被默认策略拒绝的 `dsh.ps1`(#397)。
|
|
1024
|
+
- **起来之后再验证 20 秒**:端口没人监听就把诊断写进日志 —— 本来该记日志的宿主进程已经退出了,重启失败必须留证据。
|
|
1025
|
+
- **界面靠 `boot` 号判断成功**:每 1.5 秒问一次状态,号变了(新进程接管了端口)就 `location.reload()`;60 秒还没变才报超时,并把日志路径显示出来(市场同款判定)。
|
|
1026
|
+
- **两道关卡**:先过官方 `connection.requestRejection`(Host/Origin 围栏 + 浏览器令牌),再过本插件自己的"回环 peer + 无转发头 + `Origin` 与 `Host` 同源"。这是"杀进程"的接口,跨站页面一定带自己的 `Origin`,挡在这里。非 GET/POST 回 405。
|
|
1027
|
+
- **不该从界面里杀掉的宿主会拒绝**:宿主正被调试器附着(`inspector.url()` / `--inspect` 家族,按 token 前缀匹配以免把 `inspect-tool.js` 这种路径误判),或它在 systemd 下当服务跑(`INVOCATION_ID`/`JOURNAL_STREAM` **且**父进程是 PID 1 或 comm 为 `systemd` —— 只看环境变量会把普通终端与 CI runner 误判成"有 supervisor",市场 issue #229/#471 就是这么踩的)。这时按钮禁用并说明原因。
|
|
1028
|
+
- **退出方式与市场刻意不同**:用 `process.emit('SIGTERM')` 而不是 `process.kill(pid,'SIGTERM')`。理由是可验证的:DSH 在 `apps/cli/src/profile-boot.ts:290` 注册了 `SIGTERM → interrupt(0)`(先 `fiber.dispose()` 再退出,自带 5 秒上限 `PROCESS_SHUTDOWN_TIMEOUT_MS`),而 **Windows 上 `process.kill` 等价于 `TerminateProcess`** —— 本机实测(node 起子进程、注册 handler、自己杀自己)SIGTERM / SIGINT / SIGKILL 三种写法的 handler **一次都没跑到**,进程直接没了。对"刚装完插件随手重启"没差别,对正在跑长会话的用户就是硬切。兜底:10 秒后还活着就 `exit(0)`。
|
|
1029
|
+
- **第一次点的时候,正在跑的宿主还是上一版**(新路由要重启后才加载):宿主半没有 `boot` 字段时,界面如实说明"这次走旧机制,重启之后按钮就是新版了"。旧版 handler 在同一路径上,所以第一次点照样能把 DSH 重启起来。
|
|
1030
|
+
- **去掉「可选重启命令」与那张「维护」卡**(用户拍板):机制换成市场那套之后,这个字段只剩"多一个会填错的地方"。老文档里可能留着的 `restartCommand` 由「恢复默认」顺手清掉;schema 里不再有它。
|
|
1031
|
+
- 新增 `GET /composer-ux/restart`(只读:boot 号、重放命令、日志落点、在跑会话数、是否被拦)与 `POST`(真重启,回 **202** + boot 号)。**测试里绝不让 POST 走到成功路径**:那会真的重启测试进程。
|
|
1032
|
+
|
|
1033
|
+
### 为什么这样实现(三条来自官方源码的硬约束)
|
|
1034
|
+
|
|
1035
|
+
1. **`ctx.tools.restrict()` 不能在全局上下文调用**:`packages/core/tools/src/index.ts:1077` 直接抛 `tools.restrict() requires a scoped context (agent.ctx): a context-global restriction would mask every agent`。所以「隐藏 pwsh」只能发生在**该 agent 自己的 scope** 里;为了让改动立刻生效,本插件在 `agent/created` 与 `settings/updated` 两个时机**遍历在跑会话**逐个下发(与官方 fixture `scoped-tool-subagent` 同形)。
|
|
1036
|
+
2. **`restrict()` 会校验名字必须已在全局注册表里**,否则抛 `names unknown global tool "pwsh"` —— 该会话本来就看不到 `pwsh` 时会命中这条,本插件记一行原因跳过(这正是官方 `tool-bash` 的日志里 `restrict(deny:[pwsh]) refused, skipping` 的那种情况)。
|
|
1037
|
+
3. **不需要再写 `system-prompt/assemble` 过滤器**:工具注册表自己就把 schema 喂给提示词(`packages/core/tools/src/index.ts:834` → `systemPrompt.tools(context => this.wireSchemas(context.scope))`),而 `wireSchemas(scope)` 取的是**已过滤**的可见 schema —— 一次 `deny` 会同时从「可调用注册表」和「提示词工具列表」里消失。提示词侧只做一件事:在本会话作用域里摘掉预设的 `tool:pwsh` 段(按名字过滤 `sections`,与官方 `browser-use-runtime` 同一手法)。
|
|
1038
|
+
|
|
1039
|
+
### 开发中发现并修掉的问题
|
|
1040
|
+
|
|
1041
|
+
0. **把 Niubash 从"单条候选"改成"安装根分组"时它整个消失了。** 新加的来源分组只会试
|
|
1042
|
+
`<root>/bin|usr/bin|mingw64/bin/bash.exe`,而 Niubash 的可执行文件叫 `niu.exe` 且就在根目录下 ——
|
|
1043
|
+
分组形状不匹配。是**测试逮住的**("Niubash 仍是候选"当场变红),不是靠肉眼复查。
|
|
1044
|
+
**下面这条是用户实测发现的**(他在卡片上点了 `D:\Git\usr\bin\bash.exe` 那个候选,模型的命令就大面积失败):
|
|
1045
|
+
`D:\Git\bin\bash.exe` 与 `D:\Git\usr\bin\bash.exe` 实测差别 —— 前者 `MSYSTEM=MINGW64`、`PATH` 前置 `/mingw64/bin:/usr/bin`、`head`/`grep`/`uname` 都在、中文文件名当参数正常;后者 `MSYSTEM` 为空、`PATH` 只有继承来的 Windows PATH(只有 `D:\Git\cmd`),**coreutils 全部 command not found**。
|
|
1046
|
+
2. **`sandbox.confine()` 是 `async`,而本机装着的 `dsh-windows-shell-policy@0.0.5` 把它当同步用**(`confined = sandbox.confine(...)` 后直接读 `.argv`)。官方签名是 `confine(argv, policy, signal): Promise<ConfinedArgv>`,所以那个插件一旦处于受限模式就拿到 `Promise`、`argv` 变成 `undefined`,命令根本发不起来。本实现 `await` 它,并专门写了一条测试(「实际 spawn 的是 confine 返回的 argv」)防止回归。
|
|
1047
|
+
2. **官方 `tool-bash` 在 win32 上是 `disabled`**(`packages/bundle/base/cordis.patch.yml`),而且它只消费 `ctx.shell` —— win32 的 `ctx.shell` 栈是 `pwsh-sandbox`(PowerShell)。所以「启用官方 bash 工具」拿不到 Git Bash,必须自带工具(这正是 converk/dsh-tweaks 那个插件的做法,本实现沿用并补齐了上面那条 async)。
|
|
1048
|
+
3. **「中文路径不能当参数」的根因很可能是本机 shell 环境而非 Windows**:实测当前 `bash` 工具里的 `grep` 解析到 `…/Niubash/winuxcmd/usr/bin/grep.exe`(版本串 `grep (WinuxCmd) 1.0.5`),且该环境**没有 `/proc`**——不是 MSYS/Git Bash;用它跑 `grep <中文文件名>` 会直接 `cannot open` 并 `0xC0000409` 退出。换成真 Git Bash 后是否消失,**等实测确认**(本版不宣称已解决)。
|
|
1049
|
+
|
|
1050
|
+
### 测试
|
|
1051
|
+
|
|
1052
|
+
- 新增 `test/terminal-policy.mjs`(162 条):探测顺序与 WSL 排除、渲染与 `parseExitStatus`(含官方 `[exit code: null]` 怪癖)、升级审批 fail-closed、bash 工具的参数/输出 schema 与执行路径(argv / cwd / env / stdio 上限 / confine 必须 await / 拒绝识别 / 后台钩子 / 中止与超时)、宿主半接线(在跑会话当场下发、新会话下发、幂等、切回 PowerShell 时撤销、非 Windows 与缺服务的降级)。
|
|
1053
|
+
- `test/quick-commands.mjs` 第 10 节重写(重启机制):启动命令重建的两种形态(dsh 入口 vs 裸 `dsh`)、`nodeExecutableOf`、Windows 的 PowerShell 包装与 `.cmd` 补全、端口解析与同源关卡逐条、调试器 / systemd 判定、助手源码的每条"为什么"、排期与优雅退出(spawn / 定时 / 退出全部注入)、以及**真的跑一遍助手**(替换进程的标记文件出现 / 起不来时 err 日志里有诊断 / 端口一空出来就起进程 —— 这条才是"等端口"逻辑真正的护栏)。
|
|
1054
|
+
- `test/client-registration.mjs` 新增第 8 节:按钮在抬头且**在 GitHub 链接左边**、确认条在抬头正下方、第一步只 GET 第二步才 POST、靠 `boot` 号 `location.reload()`、被拦时禁用并说明、老宿主如实提示、类名真的进了产物与样式表(中文在产物里是 `\uXXXX`,所以只查 ASCII 标记)。
|
|
1055
|
+
- 关键护栏做过**变异测试**(`test/mutation-guards.mjs`,手动跑):把「遍历在跑会话」去掉、把同源关卡去掉、把官方信任关卡去掉、把抬头按钮拿掉、把助手的 `windowsHide` 关掉、把"等端口"换成固定 sleep、把「可选重启命令」加回来、把栏开关一律返回 true、把迁移判据丢掉一半、把终端/键位的栏门控去掉、把五栏开关改成带默认值、把快捷指令的文件迁移去掉、把「自定义」档的门控去掉、把三档说明的互斥条件去掉、把 strip 那条死路接回来、把标题行下重复的 hint 加回去、把那 7 个条目开关整段删掉、把那 7 行渲染短路掉、把「浏览器」档的说明短路掉、把三档说明全铺开 —— **21 条全部当场变红**。跑变异必须带上 `node build.mjs`:宿主半的用例打的是构建产物,只改 `src/` 不重建会得到假的"没咬住"。
|
|
1056
|
+
- 新增 `test/settings-render.mjs`(**手动跑,20 项**):这套件一直没有 React 渲染器,设置页版式只能靠搜源码字符串护栏 —— 那是最弱的一层(把 `{settings.menuMode === 'custom' && (` 改成 `{true && (` 它照样绿)。这个文件用 `react-dom/server`(**借 profile 里那份**,不新增依赖、不改 `package.json`)把设置页真的渲染成 HTML,断言:六张卡都渲染出来、右键菜单卡标题行只剩卡级开关、三档正文互斥且两两不同、那 7 行条目开关只在「自定义」档。三处坑记在文件头:两个 React 副本(profile 顶层与 react-dom 的 peer 不是同一个 ⇒ 必须按 react-dom 的解析路径取 react,否则 `dispatcher` 为 null)、三枚胶囊的 `title` 里也带着那三句话(按 HTML 搜会误报"说明没互斥",第一版就假红了 3 项)、`FoldCard` 的展开态在组件内部(SSR 点不动 ⇒ 打包时在内存里改成展开,锚点找不到就报错)。
|
|
1057
|
+
- 开关与迁移的测试(共 60+ 条):全新安装六栏全关、总开关默认开、显式值优先于迁移、每栏判据各一条、宿主半自持字段不当判据、`activeSections` 的总闸语义、**用户真实文档的回归**(六栏保持全开)、schema 的"缺省 ≠ false"、快捷指令的文件迁移(非内置 → 写回;内置 → 不写;已关 → 不写回;幂等;没文件 → 不写)、终端栏关掉时撤销已下发的压制、总开关关掉时同样撤销、拦截器/入口按钮/面板把手各自的栏门控、标题行拆成两个兄弟节点、六张卡各有一个开关。
|
|
1058
|
+
|
|
1059
|
+
## [0.4.0] — 2026-09-15
|
|
1060
|
+
|
|
1061
|
+
> 已发布:用户实测通过后授权发布,已上 npm(`latest`)、GitHub 标签与 Release(`v0.4.0`)。
|
|
1062
|
+
> 从 0.3.0 升级注意:本次含**宿主半 schema 改动**(新增 `menuMode`,并把旧布尔 `menuNative` 改成可选),**必须重启 DSH** 才会生效。
|
|
1063
|
+
|
|
1064
|
+
### 新增
|
|
1065
|
+
|
|
1066
|
+
- **跨分类移动条目**:条目列表最下边多了一行「+ 新建 / 移动」。点开分两个动作 ——「+ 新建一条」与「→ 把别的分类的条目移动到这里」;后者列出其它分类的条目(`分类名 · 条目名`),点它即移入当前分类、**追加到末尾**,`id` / 正文 / 插入模式都跟着走。设置页与面板共用同一个组件(`AddPromptRow.tsx`),行为不会分叉。
|
|
1067
|
+
- 移走最后一条时源分类**留空但不删除**(空分类是合法状态);目标分类不存在、就是源分类、或已满时**原样返回**(不写盘)。
|
|
1068
|
+
- **插入模式改为三选一:关 / 每次 / 仅首次**(原来是单个「默认插入」勾选框)。
|
|
1069
|
+
- 「仅首次」只在**这个会话的第一条消息**上附加。判据是官方会话快照里的 `SessionSnapshot.blank`(这个会话还没有任何消息),**不是**插件自己记的状态——发完第一条它自动失效,刷新页面、切走再切回来都不会重复附加。
|
|
1070
|
+
- 用三选一而不是两个勾选框:「每次」与「仅首次」互斥,两个独立勾选框能造出「又每次又仅首次」的矛盾状态。界面只发出一个 mode,由 `withInsertMode` **一次把两个标志写对**。
|
|
1071
|
+
- 文件里「仅首次」写成扩展键 `autoSendFirst: true`(只在为真时出现),`autoSend` 语义不变——与参考实现的互操作性因此保持;代价是对方重新保存时会丢掉这个键(「仅首次」在那边退化成「关」)。手工把两个键都写成 `true` 时按「每次」处理(每次插入本来就包含第一次),下次写盘修正回互斥。
|
|
1072
|
+
- 「+ 新建」出来的条目正文是占位「(待填写)」而不是空串:空正文会被净化丢掉,而面板里的新建是**立即写盘**的,给空串会当场消失、看起来像坏掉了。设置页保存时也会先剔掉正文为空的条目**并在状态行说明**「已跳过 N 条」。
|
|
1073
|
+
- **「右键菜单」从布尔开关改为三选一:官方 / 浏览器 / 自定义**(原来是「使用系统原生菜单」)。
|
|
1074
|
+
- **官方**(新默认档):本插件**完全不介入**——既不 `preventDefault` 也不 `stopImmediatePropagation`,DSH 与其它插件自己的右键处理原样生效(DSH 官方输入框本身没有右键菜单,所以通常看到的就是浏览器的菜单)。
|
|
1075
|
+
- **浏览器**:固定用浏览器自带的菜单,并在本插件这一层**挡住其它插件的菜单**(这是它与「官方」的唯一差别);粘贴免授权、零配置。
|
|
1076
|
+
- **自定义**:插件自己的固定样式菜单(7 项可逐项开关,只在这一档显示那些开关);`preventDefault` 掉浏览器菜单,并和「浏览器」档一样**挡住其它插件的菜单**(0.2.0~0.4.0 的自定义档只 `preventDefault + stopPropagation`,同一层里别的插件的捕获监听仍会收到事件,可能弹出两个菜单;本次补齐,由用户拍板)。
|
|
1077
|
+
- **迁移**:旧布尔 `menuNative` 仍然认——`true → 浏览器`、`false`(明确关过那个开关)`→ 自定义`;两个键都没有(从没设过)→ 新默认档「官方不介入」。**这是一处行为变化**:以前「没设过」等于自定义菜单,现在没设过等于不介入。旧字段只作迁移线索,0.4.0 起不再写它;「恢复默认」会把两个键一起清掉。
|
|
1078
|
+
- 为此把宿主 schema 里的旧布尔改成**可选**(schemastery 的 `.required(false)`,且不给默认值):否则「文档里没有这个键」与「明确设成了 false」在解析之后长得一模一样,迁移就分不出该落哪一档。
|
|
1079
|
+
- 控件与「插入模式」共用同一个胶囊组件(`PillChoice.tsx`),样式与无障碍行为不会各写一遍而分叉。
|
|
1080
|
+
|
|
1081
|
+
### 开发中发现并修掉的问题
|
|
1082
|
+
|
|
1083
|
+
1. **「仅首次」在写回编辑器那一步被二次过滤掉了**(用户实测发现:新会话第一条只发「每次」的,不发「仅首次」的)。
|
|
1084
|
+
- 成因:批次算得没错——`appendBatchForSend(book, blank)` 在空会话里正确交出了「每次 + 仅首次」;但紧接着写回的函数(当时叫 `withAlwaysPrompts`)内部**又按 `always === true` 过滤了一遍**,于是「仅首次」的条目全被丢掉;**若一条「每次」都没有,`picked.length === 0` 直接返回 null,第一条消息里什么都不附**。
|
|
1085
|
+
- ✅ 正解:写回这一步只负责「把给它的这一批拼上去」,模式判断只留一处(`settings-contract.ts` 的 `insertModeOf`)。同时把这处过滤的载体 `alwaysPrompts()`(按原始标志过滤的辅助函数)**整个删掉**——留着它就会有人再拼一遍同样的错。函数更名成 `withPromptsAppended` / `applyPromptsForSend`,名字如实反映「批次已定,这里只写回」。
|
|
1086
|
+
2. **我原来那条「接缝护栏」是字符串匹配,所以这个 bug 全绿通过。** 它只断言 `client.tsx` 里 `promptsForSend` 接的是 `appendBatchForSend(book, currentBlankSession())` —— 也就是只验「批次算得对」,完全没验「写进去对不对」。
|
|
1087
|
+
- ✅ 正解:改成**执行整条路径**的护栏——用 `publishInputBridge` 装一个假编辑器,再调**拦截器真正调用的** `applyPromptsForSend(appendBatchForSend(book, blank))`,断言**最终写回编辑器的整段文本**;另加一条语义护栏:写回路径里不得再出现 `always === true` / `firstOnly === true`。
|
|
1088
|
+
3. **新护栏被我自己写的说明注释弄红了。** 我在 `quick-commands.ts` 里解释这个 bug 时写了 `always === true` 的字面量,护栏匹配到注释里的散文就报红——和上一轮「护栏命中面板里过时的注释」是同一类错误。
|
|
1089
|
+
- ✅ 正解:护栏匹配前先剥注释(`codeOnly()`)。注释里必须能自由记录踩过的坑。
|
|
1090
|
+
4. **设置页「快捷指令」卡片里三处 `**加粗**` 是 Markdown 字面量**,在 React 里就渲染成三个星号(`**发送时怎么附加**`)。这是 0.4.0 写文案时留下的,没人报过但一眼可见。
|
|
1091
|
+
- ✅ 正解:改成 `<strong>`。**这类问题只有看渲染结果才发现**——文案改动要顺手在真界面里看一眼。
|
|
1092
|
+
5. **Firefox 的粘贴说明一直是错的**(用户照做之后实测发现)。我们教用户「把 `about:config` 里的 `permissions.default.clipboard-read` 设为 `1` 就不弹授权窗」——用户设完之后,「粘贴(P)」小窗照旧出现。
|
|
1093
|
+
- 查证结论:那个小窗是 Firefox 的**固定安全机制**——网页读剪贴板时它弹一个只有「Paste」一项的临时菜单(约 1 秒后才可点),与那个首选项无关;能免掉它的只有 `dom.events.testing.asyncClipboard` 这类**测试用**开关,那等于允许任何网站静默读剪贴板([MDN](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API#security_considerations)、[caniuse](https://caniuse.com/mdn-api_clipboard_readtext):只有带 `clipboardRead` 权限的扩展不受此限)。
|
|
1094
|
+
- ✅ 正解:界面、README 与运行时提示统一改成准确说法——「Firefox 一定会弹那个小窗,插件关不掉;不想多这一步就用 Ctrl+V,或把「菜单来源」换成「浏览器 / 官方」档」。并加一条护栏:界面与运行时提示里**不许再出现**那个首选项名(变异测试:写回去立刻变红)。README 另留一句「早先写法是错的」的更正,避免有人从旧文档学到错的。
|
|
1095
|
+
- 教训:**「用户照做之后没用」就是最硬的证据**。这条错误能存活好几个版本,是因为它只写在文案与提示里、没有任何测试盯着它是否成立。
|
|
1096
|
+
|
|
1097
|
+
### 测试
|
|
1098
|
+
|
|
1099
|
+
- 全量 **304 条 0 失败**(`npm test`:35 + 106 + 125 + 38)。
|
|
1100
|
+
- 新增覆盖:插入模式三态与「两个标志都为真 → 按每次」的优先级、切模式时互斥标志被清掉、`autoSendFirst` 的文件往返与旧文件(只有 `autoSend`)的向后兼容、发送批次在 `blank=false` 时**不带**「仅首次」、**端到端写回**(两批都在 / 第二条只剩「每次」/ 只有「仅首次」时第一条仍附加 / 原文为空时不写)、跨分类移动的语义与四种 no-op、「会话是否为空」取自槽位快照的接缝、两处共用同一控件。
|
|
1101
|
+
- 右键三档新增两层测试:**纯函数**层钉住迁移的每种情形(新字段优先、旧 `true → 浏览器`、旧 `false → 自定义`、都没有 → 官方、脏数据落回旧布尔、元数据齐全);**行为**层扩展客户端测试桩(让 `addEventListener` 保留处理函数、补齐 `Element` 与 `getSelection`),真的派发一次 `contextmenu`,断言官方档三件事都不做、浏览器档只 `stopImmediatePropagation` 不 `preventDefault`、自定义档 `preventDefault` + 挡住同层监听。**这个行为测试当场抓出一处既有行为**:0.4.0 及以前的自定义档其实不挡同层其它捕获监听(可能弹出两个菜单),已按用户拍板补齐,README 同步改写。
|
|
1102
|
+
- 变异测试:写回路径的 3 处变异(二次过滤 / `blank` 失效 / 永远不带「仅首次」)与设置页警告的 3 处变异(删掉 / 样式降级 / 改写措辞)全部被拦住并变红。
|
|
1103
|
+
|
|
1104
|
+
## [0.3.0] — 2026-09-15
|
|
1105
|
+
|
|
1106
|
+
> 已发布:实测通过后发布到 GitHub 与 npm(本条目的「尚未发布」声明在 0.4.0 发布时才补正)。
|
|
1107
|
+
|
|
1108
|
+
### 变更(含一处破坏性调整)
|
|
1109
|
+
|
|
1110
|
+
- **快捷指令的存储搬出设置文档**,改存 `$DSH_HOME/quick-prompts.json`。三个原因:设置文档对**数组**是整份替换、且受 schema 长度上限约束(旧 4000 字会**静默截断**长提示词,而长提示词恰恰是常态);快捷指令是「用户内容」而不是「插件配置」,单独一个文件便于备份、搬迁与手工编辑;插件被禁用或卸载也不牵连它。
|
|
1111
|
+
- **自动迁移**:首次读取时把设置文档里 0.2.x 的那份平铺列表搬成「默认」分类。**旧值保留、不清空**——万一回滚到 0.2.x,用户还能看到自己那几条。
|
|
1112
|
+
- 单条上限 4000 字 → **20 万字**(只做防呆,不再截断正常提示词)。送去「优化」的原文仍限 8000 字:那是给模型的输入,不能跟着涨(原先它由 `QUICK_TEXT_MAX * 2` 推导,现在独立成 `OPTIMIZE_TEXT_MAX`,**值不变**)。
|
|
1113
|
+
- **分类两级结构**:`{ version: 2, categories: [{ id, name, prompts: [...] }] }`。面板顶部是分类标签(点它切换、`+` 新增),分类的改名 / 排序 / 删除与条目的增删改移都在设置页。结构参考 [lnyuqian/dsh-quick-prompts](https://github.com/lnyuqian/dsh-quick-prompts),文件字段名与它**逐字对齐**(`categories` / `name` / `title` / `text` / `autoSend` / `order`),于是两边读得懂同一个文件;代价是**不能同时装两个插件**——一个文件两个写者会互相覆盖,而对方是非原子写。
|
|
1114
|
+
- **「默认插入」跨分类生效**:勾选按条目 id 找,不再只看当前分类;发送时附加的那份列表由 `alwaysQuickPrompts(book)` 跨所有分类聚合。
|
|
1115
|
+
- 新增宿主路由 `GET/POST /composer-ux/prompts`(读整本 / 整本写回),沿用原有的 `webServer.register({ kind: 'exact' })` 接缝。
|
|
1116
|
+
|
|
1117
|
+
### 存储层为什么比参考实现更严
|
|
1118
|
+
|
|
1119
|
+
参考实现的存储有两处真实的丢数据路径,本插件都避开了:
|
|
1120
|
+
|
|
1121
|
+
1. 它用 `writeFile(file, JSON.stringify(...))` **直接覆盖**——中途失败会留下截断的 JSON。本插件用「临时文件 → `handle.sync()` → `rename`」原子替换,并在 `<file>.lock` 里串行化写入(`wx` 创建;超过 30 秒的遗留锁**只报错、不抢占**——锁的新旧无法区分「崩溃的所有者」与「只是被暂停的写者」,处置权交给操作者)。
|
|
1122
|
+
2. 它读盘失败时 `catch { return [] }`——UI 显示空列表,用户一保存就把**空列表写回去**,真数据被覆盖。本插件解析失败先**改名隔离**(`quick-prompts.json.bad-<时间戳>`)再如实报错,**绝不返回空本、绝不写入**。
|
|
1123
|
+
|
|
1124
|
+
### 开发中发现并修掉的问题
|
|
1125
|
+
|
|
1126
|
+
1. **迁移判断在锁外,并发首个请求会互相覆盖。** `ensureQuickBook` 原本「读到文件不存在就就地初始化」:多个并发请求同时看到缺失时,后写的那个会把先写的用户数据覆盖成默认本。✅ 正解:在写锁内**复核一次**再初始化。
|
|
1127
|
+
2. **我写的一条护栏是「永不报警的死护栏」。** 检查「读不懂时不许返回空本」的正则精确匹配了代码形状,但我多写了一个收尾括号,于是它永远不命中任何代码。✅ 正解:改成语义写法(`catch` 之后 200 字内不许出现 `kind: 'ok'`)。**这个问题是靠变异测试发现的**——把 bug 放回去时那条护栏没有变红。
|
|
1128
|
+
3. **越界换位凭空造新对象。** `withPromptMoved` 走到 `mapCategory` 才判边界,越界时仍返回内容相同的新对象,会引发一次无意义的写盘。✅ 正解:先算边界,越界直接返回传入的那个对象。
|
|
1129
|
+
|
|
1130
|
+
### 测试
|
|
1131
|
+
|
|
1132
|
+
- 新增 `test/quick-store.mjs`(77 条)覆盖:原子写(不留 `.tmp`)、坏文件隔离且内容原样保留、认不出的结构不写入、空文件当损坏、v1 平铺与裸数组升级、**5000 字提示词不再被截断**、并发四写不互相截断且最终是一个完整本、遗留锁报错且不删不抢占、字段映射往返、客户端编辑函数的不可变性与跨分类聚合。
|
|
1133
|
+
- 全量 **239 条,0 失败**。
|
|
1134
|
+
- 对三处关键护栏做了**变异测试**(把 bug 放回去看会不会红):读不懂当空本 / 文件字段名改回内部名 / `always` 聚合退化成只看第一个分类——均成功变红。
|
|
1135
|
+
|
|
1136
|
+
## [0.2.0] — 2026-09-14
|
|
1137
|
+
|
|
1138
|
+
> 已在用户机器上实测(并据实测修掉 5 处问题,见下「开发中修掉的问题」),随后发布到 GitHub 与 npm。
|
|
1139
|
+
|
|
1140
|
+
### 新增
|
|
1141
|
+
|
|
1142
|
+
- **快捷指令按钮**:输入框工具行里、「展开」按钮的左侧多了一个同款胶囊按钮(槽位 `conversation.input.right`,order 89 < 官方的 90)。点击展开面板,面板里是快捷指令清单 + 「优化提示词」按钮。
|
|
1143
|
+
- **快捷指令清单(内置 9 条)**:点条目把内容插入输入框(原有内容保留、另起一行)。内置条目 = 用户口述的 4 条(一问一答 / 交接文档 / 仅说明原因 / 分析后直接干)+ 提取自 [congyaqwq/dsh-quick-prompts](https://github.com/congyaqwq/dsh-quick-prompts) 的 5 条(提交代码 / 给方案 / 解释代码 / 写测试 / 代码审查)。另外两个同名插件 [lcsdg](https://github.com/lcsdg/dsh-quick-prompts) / [lnyuqian](https://github.com/lnyuqian/dsh-quick-prompts) 经逐仓库核对**不带内置指令**(列表默认为空),故无从提取。清单可在设置页增删改、上下移、恢复内置。
|
|
1144
|
+
- **「默认插入」**:条目右侧的勾选框。勾上后,在你**点发送时**(Enter 或官方发送按钮)勾选的提示词会被自动拼到消息**末尾**一起发出,输入框里不提前显示;多条按列表顺序拼接、条目间空一行。
|
|
1145
|
+
- **优化提示词**:把输入框里的话交给**另一个 AI**(独立一次模型调用,不占对话轮次、不污染会话)整理成一条能直接发给工作 AI 的清晰指令,结果**直接写回输入框**(Ctrl+Z 可还原)。三档强度可选,默认「高级」。
|
|
1146
|
+
- **宿主半 HTTP 接口** `POST /composer-ux/optimize`:出网请求只能由宿主发出,浏览器侧碰不到模型路由,故走这条往返(与 WestFox 的插件同构)。只接受 POST、限长 8000 字符、180 秒墙钟超时。
|
|
1147
|
+
|
|
1148
|
+
### 提取来源与署名
|
|
1149
|
+
|
|
1150
|
+
「优化提示词」的系统提示词**逐字提取**自 [WestFox-AwA/dsh-prompt-optimizer](https://github.com/WestFox-AwA/dsh-prompt-optimizer) 的 `lib/index.js`(BSD-3-Clause,作者「啃轮胎的西狐」),落在新建的 `src/optimizer-prompt.ts`,保留原作者署名与来源说明:
|
|
1151
|
+
|
|
1152
|
+
- `RELAY_ROLE`:优化者是**传话器**而不是对话伙伴 —— 用户 →(整理成命令)→ 工作 AI,产出会被原样发出。
|
|
1153
|
+
- `NO_META_RULES`:禁止一切元话语(「优化后的提示词」「以下是…」)、对话话术与对用户提问。
|
|
1154
|
+
- 三档 `TIER_SPECS`:普通(只修语言,不添需求,温度 0.2)/ 高级(补全没说出口的必要要求,0.3)/ 极端(命令结构 + 分阶段计划 + 预案,0.3)。
|
|
1155
|
+
- `relayMessage`:把用户原话包成「待转达内容」,这是原作者修「优化 AI 以为自己在和用户对话」的关键一招。
|
|
1156
|
+
|
|
1157
|
+
### 实现要点
|
|
1158
|
+
|
|
1159
|
+
- **发送时附加走官方手势,不自造第二套提交**:Enter 那一路沿用既有的合成 Enter 回放;官方发送按钮那一路在捕获阶段认下点击、先把附加内容写回编辑器、再用同一个按钮重放一次点击。这样官方对「发送 / 排队 / 打断」的判定原样生效。
|
|
1160
|
+
- 发送键与停止键共用同一个位置,只能按图形区分:停止渲染 `<rect>`(方块),发送渲染 `<path>`(箭头)。按图形判别与界面文案、语言无关。
|
|
1161
|
+
- 原文为空时**不附加**(没有「你的消息」可附加,交还官方原语义);一条都没勾时同样不碰事件。
|
|
1162
|
+
- 数据存进本插件既有的 `composer-ux` 设置命名空间(跟另外 8 项设置同一处),换机器也在;净化端逐条收窄形状、按 id 去重、限 60 条 / 4000 字符。
|
|
1163
|
+
|
|
1164
|
+
### 开发中修掉的问题(均由用户实测截图/操作发现,均未流出)
|
|
1165
|
+
|
|
1166
|
+
1. **点了发送只插入、不发送,一直点一直插入。** 官方发送键是个圆形按钮,点击的视觉中心落在它内部的 `<svg>`/`<path>` 上,而 `event.target` 那时**是 SVG 元素** —— `SVGElement` 不继承 `HTMLElement.click()`,于是「先 preventDefault/stopPropagation 再 `button.click()` 重放」里的 `click()` 抛 TypeError:原始点击已被吞掉,重放又没发生 ⇒ **草稿被附加了、消息没发出去**;再点一次又叠一遍。
|
|
1167
|
+
✅ 正解:`isSendButton(target): boolean` 改为 **`sendButtonOf(target): HTMLButtonElement | null`**,返回真正的 `<button>`(并加 `instanceof HTMLButtonElement` 守卫)而不是 `event.target`;拿不到按钮就**不拦截**,绝不吞掉用户的点击。
|
|
1168
|
+
✅ 另加**幂等护栏**:草稿已以同一段后缀结尾时 `withAlwaysPrompts` 返回 null,于是第二次点击直接放行官方发送 —— 万一将来发送那一步再出问题,症状会退化成「再点一次就发出去」,而不是无限叠加。
|
|
1169
|
+
2. **入口按钮与旁边的官方「展开」按钮不统一(那圈白线)。** 我用 `--dsw-alias-border-l2`("Secondary stronger border",更亮)当边框,且没给 `opacity`,于是比 `dsh-composer-expand` 的按钮亮一圈;又因为用了**行内样式**,`hover` 根本表达不出来。
|
|
1170
|
+
✅ 正解:改为**注入样式表**(`src/client/quick-style.ts`),参数与同一个槽位的 `dsh-composer-expand` 的 `.cpex-btn` **逐项对齐**:`height:24px` / `border:1px solid rgba(127,127,137,.35)`(中性半透明灰,不用更亮的主题令牌)/ `padding:0 9px` / `opacity:.75` / `color:inherit`,并补上 `:hover` 与 `[aria-expanded="true"]` 两个状态。
|
|
1171
|
+
3. **档位选中态与「优化提示词」按钮白底白字。** 用 `--dsw-alias-button-primary-fill` 当填充,却把文字**写死成 `#fff`**。该令牌定义为 `--dsw-alias-brand-primary`:浅色主题下是墨色(深),**深色主题下它是白** —— 于是暗色主题里白底配白字(用户截图里那个「空白白色按钮」)。
|
|
1172
|
+
✅ 正解:填充与前景**必须成对**,官方 `ui-primitives/Button.module.css` 的写法是 `background: var(--dsw-alias-button-primary-fill)` + `color: var(--dsw-alias-label-primary-foreground)`;后者浅色是 `neutral-bluish-00`、深色是 `neutral-bluish-1000`,自动翻转。禁用态改用官方的 `opacity: .4`。
|
|
1173
|
+
注:`button-primary-fill` 本身没错 —— 本插件有两处把**它当文字色**用在中性底上(`pillActive`),那是 ink 的正确语义,故保留。
|
|
1174
|
+
4. **列表行长预览把「默认插入」勾选框挤出面板。** 条目按钮写了 `width: 100%`,在 flex 行里吃满整行,右侧勾选框被面板的 `overflow: hidden` 裁掉 —— 截图里 `交接文档`、`提交代码` 两行看不到勾选框。
|
|
1175
|
+
✅ 正解:按钮改 `flex: 1 1 auto; minWidth: 0`,让预览可被压缩。
|
|
1176
|
+
5. **「设置面板」整段失效:拖边缘没反应、点尺寸预设也没反应。**(架构性错误,两个独立缺陷叠加)
|
|
1177
|
+
- **层叠上下文陷阱**:手柄原先渲染在 `shell.overlay` 里、用**视口坐标**对准面板画。但 `shell.overlay` 被官方封在 `ui-layout` 的 `.overlayLayer { position:absolute; inset:0; z-index:20 }` —— `position` + `z-index` 使它**自成层叠上下文**,里面的元素 z-index 再大(我写了 9998)也**逃不出去**,永远排在设置弹窗(`.overlay` z-index:1000)下面,再被全屏遮罩(`.mask { position:absolute; inset:0 }`)吃掉鼠标 ⇒ 手柄**根本抓不到**。
|
|
1178
|
+
✅ 正解:**把手柄 `createPortal` 到面板内部**(`panel` 是 `position: relative`,正是合适的包含块)—— 挂进最高那一层的**里面**,就完全不需要比层叠。附带收益:定位由「视口坐标 + 250ms 轮询重算」变成「面板内绝对定位」,面板移动/滚动/改尺寸都自动跟随,矩形轮询整个删掉。
|
|
1179
|
+
- **尺寸预设不即时生效**:套用尺寸被 `appliedRef` 门控成「只在首次发现面板时套用一次」,点预设只写了设置、没人把它应用到**已经打开**的面板上,必须关掉设置重开才看得到。
|
|
1180
|
+
✅ 正解:套用逻辑拆成独立 effect,依赖 `[panel, panelWidth, panelHeight]` —— 面板出现时套用、**尺寸设置一变立刻重套**;设置为空时 `removeProperty` 回落到官方默认 800。
|
|
1181
|
+
- **顺带修好手感**:居中的 flex 子项一改宽度会**两侧同时伸缩**,拖右边左边也跟着动。现在拖拽开始时先把面板转成 `position: fixed` 并锁定当前 left/top,拖哪边就只动哪边。
|
|
1182
|
+
- **可见性**(用户选定方案):四边一圈 1px 细描边(`border-radius: inherit`,自动跟随面板 32px 圆角)+ 右下角一个抓手图标;四边各 8px 抓取带宽,悬停高亮。
|
|
1183
|
+
|
|
1184
|
+
**新增回归护栏**(都在 `test/quick-commands.mjs`,且都**做过「把 bug 放回去」验证确认会红**):
|
|
1185
|
+
|
|
1186
|
+
- 第 3 节:假 DOM 里让按钮内部的图标节点**故意没有 `click()`**(与真 SVG 一致),断言 `sendButtonOf(svg)` 返回的是**真按钮**而不是那个图标 —— 这正是问题 1 的根因。
|
|
1187
|
+
- 第 2 节:断言幂等(已附加过就不再叠)。
|
|
1188
|
+
- 第 7 节:把样式表**当数据**检查 —— 凡用 `button-primary-fill` 当背景的样式,前景必须是配对令牌;样式表里不得再出现写死的 `#fff`;条目按钮必须可压缩;入口按钮的旧行内样式必须已删除。
|
|
1189
|
+
- 第 8 节(**层叠上下文护栏**):断言手柄走 `createPortal`、源码里不再出现「靠大 z-index 压弹窗」的写法、五个手柄的定位都是**面板内相对值**(任何数值 > 40 即判失败 —— 旧写法就是视口坐标)、描边 `border-radius: inherit` 且不吃指针、手柄层 `pointer-events: none` 而手柄自身 `auto`(写反会让整块面板点不动)。
|
|
1190
|
+
|
|
1191
|
+
### 测试
|
|
1192
|
+
|
|
1193
|
+
- 新增 `test/quick-commands.mjs`(**97 项**):设置净化、末尾拼接语义(含幂等)、发送键图形判别与「返回真按钮」回归(最小假 DOM,含父链)、三档提示词资产、用假 `webServer` + 假 `llm` 驱动 `lib/index.js` 跑通整条优化往返(200 / 405 / 400 / 模型报错 / 无路由)、宿主半真实注册的那个 settings schema 的默认值解析,以及第 7 节的**样式配对 / 行内样式退场护栏**与第 8 节的**层叠上下文护栏**。
|
|
1194
|
+
- 新增 `test/client-registration.mjs`(**29 项**):直接执行 `lib/client.js`,用最小 window/document/React 桩跑一遍 `apply(ctx)`,断言 5 个槽位条目、order=89、拦截器三类监听、以及注入面字段与组件取用的名字一致 —— 这类接线错误构建期看不出来。
|
|
1195
|
+
- `test/host-header-mirror.mjs` 的假 ctx 改为遵循 Cordis 的 inject 语义(只在该服务确实挂载时进入回调),35 项保持全绿。
|
|
1196
|
+
- 合计 **161 passed, 0 failed**;两个产物经市场「装前体检」同款正则扫描,风险特征 **0 命中**。
|
|
1197
|
+
|
|
1198
|
+
## [0.1.5] — 2026-09-13
|
|
1199
|
+
|
|
1200
|
+
### 新增
|
|
1201
|
+
- **按 baseURL 识别 OpenCode 路由**:自动匹配(「作用路由」留空)原先只看路由名是否以 `opencode` 开头,因此自建别名路由(例如 `go: { baseURL: https://opencode.ai/zen/go/v1 }`)拿不到请求头。现在改为「名字前缀 **或** baseURL 主机是 `opencode.ai`(含子域)」两者取或——内置 `opencode-go` 仍走名字那条(它的 baseURL 由 pi-ai 目录内置,配置里读不到),自建别名走 URL 那条。
|
|
1202
|
+
- 仿冒主机不匹配:`https://opencode.ai.evil.example/v1` 若只做子串判断会误伤,因此改为解析 URL 后比较主机名(解析失败才退回子串)。
|
|
1203
|
+
- 设置页「作用路由」的说明与占位符同步更新;无命中时「状态」行会写出实际的匹配规则。
|
|
1204
|
+
|
|
1205
|
+
### 测试
|
|
1206
|
+
- `test/host-header-mirror.mjs` 新增 3 组(9 项):按 URL 命中别名路由 / 子域命中 / 非 OpenCode 路由不动 / 仿冒主机不写入 / 显式名单优先 / URL 非法时子串回退。**35 passed, 0 failed**。
|
|
1207
|
+
|
|
1208
|
+
## [0.1.4] — 2026-09-13
|
|
1209
|
+
|
|
1210
|
+
### 文档
|
|
1211
|
+
- **安装一节精简为两条命令**(安装 / 更新),GitHub 源码安装、锁定 commit、本地目录开发等做法收进折叠块——README 页面不再一上来就是四条命令。
|
|
1212
|
+
- 移除 README 里的本机绝对路径,改为占位符,别人可以直接照抄。
|
|
1213
|
+
- 功能代码无改动(`lib/` 与 0.1.3 一致),26 项宿主行为测试全绿。
|
|
1214
|
+
|
|
1215
|
+
## [0.1.3] — 2026-09-13
|
|
1216
|
+
|
|
1217
|
+
### 变更
|
|
1218
|
+
- **发布到 npm**:`dsh-composer-ux` 已上架 <https://www.npmjs.com/package/dsh-composer-ux>,安装命令简化为 `dsh plugin --profile <profile> add dsh-composer-ux`(预构建产物,安装期不执行任何代码);README 的安装一节改为 npm 优先,GitHub 安装作为等价备选保留。
|
|
1219
|
+
- 包内 `repository` 字段指回 https://github.com/fangwen9527/dsh-composer-ux ,便于插件市场把 npm 包与仓库关联(下载量展示据此生效)。
|
|
1220
|
+
- 功能代码无改动(`lib/` 与 0.1.2 一致),26 项宿主行为测试保持全绿。
|
|
1221
|
+
|
|
1222
|
+
## [0.1.2] — 2026-09-12
|
|
1223
|
+
|
|
1224
|
+
### 修复
|
|
1225
|
+
- **体检正则零命中**:0.1.1 只清了 `new Function`,但市场体检器的规则是 5 条(动态生成函数的两个关键字、`atob`、连续 40+ 数字参数的 `fromCharCode`、200 位以上 base64 字面量)。内联的 cosmokit 二进制工具在非 Node 环境回退到 `atob` 解码,仍会被判为「含混淆/动态执行代码」。宿主包只跑在 Node 上(`Buffer` 必然存在),该回退分支是死代码,现已在打包后替换为显式报错。
|
|
1226
|
+
- `build.mjs` 自身同样不再出现这些风险字面量,避免构建脚本被同一条规则误报。
|
|
1227
|
+
- 现在 `lib/index.js`、`lib/client.js`、`build.mjs` 对该规则的命中数均为 **0**;26 项宿主行为测试全绿。
|
|
1228
|
+
|
|
1229
|
+
## [0.1.1] — 2026-09-12
|
|
1230
|
+
|
|
1231
|
+
### 修复
|
|
1232
|
+
- **`dsh.bundle.patch` 指向修正**:0.1.0 把发行层文件改名为 `cordis.patch.yml` 并删除了旧文件,但清单未同步,安装后会没有任何组合层可用。现已指向 `cordis.patch.yml`(安装流程已用临时 profile 实测通过)。
|
|
1233
|
+
- **消除宿主包里的动态执行标记**:内联的 schemastery 带有「字符串回调 → `new Function` 还原」这条分支,虽然本插件从不使用字符串回调,但它会让插件市场的「装前体检」把宿主包判为「含混淆/动态执行代码」并提示用户不要安装。构建脚本现在会在打包后把该分支替换为等价的空实现,`lib/` 内不再出现 `new Function` / `eval(`;非字符串回调路径与插件行为完全不变(26 项宿主行为测试全绿)。
|
|
1234
|
+
|
|
1235
|
+
## [0.1.0] — 2026-09-12
|
|
1236
|
+
|
|
1237
|
+
首个公开发行版(可作为 DSH 组合包安装)。
|
|
1238
|
+
|
|
1239
|
+
### 输入键位
|
|
1240
|
+
- 可配置「发送键」「换行键」:常用预设(Enter / Ctrl+Enter / Alt+Enter / Shift+Enter)+ 录制任意组合键;支持清空为「无」、一键恢复默认。
|
|
1241
|
+
- 发送/换行互斥校验;IME 组合期间(`isComposing` / `keyCode 229`)一律放行,不误发。
|
|
1242
|
+
- 通过回放官方按键管线实现,完整保留官方行为:忙时排队/steer、连发防重、空草稿不发送、`/` `@` 菜单优先。
|
|
1243
|
+
|
|
1244
|
+
### 输入框右键菜单
|
|
1245
|
+
- 7 个条目(撤销/重做/剪切/复制/粘贴/删除/全选)可单独开关;未选中文本时剪切/复制/删除置灰;分隔线自动合并。
|
|
1246
|
+
- 可切换为系统原生菜单(浏览器自带,粘贴免授权)。
|
|
1247
|
+
- 自定义菜单样式对齐系统编辑器:深色圆角、三分组、快捷键右对齐;点击外部 / Esc / 滚动 / 窗口变化自动关闭。
|
|
1248
|
+
|
|
1249
|
+
### 设置面板
|
|
1250
|
+
- 设置页新增「输入体验」条目:版式对齐社区插件的「Web 插件」页,四个可折叠栏目,默认全折叠。
|
|
1251
|
+
- 导航可滚动开关;面板边缘拖拽调整大小,尺寸持久化并带预设。
|
|
1252
|
+
|
|
1253
|
+
### 全局开关
|
|
1254
|
+
- 设置页顶部「启用输入增强」总开关:关闭后键位、右键菜单、面板滚动/缩放全部停用,输入框回到 DSH 原生行为;状态持久保存。
|
|
1255
|
+
|
|
1256
|
+
### OpenCode 请求头
|
|
1257
|
+
- 自动为 OpenCode 路由的模型请求附加 `x-opencode-session`(DSH 唯一受支持的出网请求头入口 `llm-pi-ai.providers.<route>.headers`)。
|
|
1258
|
+
- 只写已存在的路由,使用 `settings.mutate` 路径寻址,不动同 profile 的其它字段;关闭开关/改头名会精确回收自己写入的那一个键。
|
|
1259
|
+
- 值可改、可重新生成(默认 UUID);写入前按 llm-pi-ai 相同规则校验头名与头值。
|
|
1260
|
+
|
|
1261
|
+
### 打包与分发
|
|
1262
|
+
- 按官方《打包与安装插件》规范提供组合包清单:`package.json:dsh.bundle.patch → ./cordis.patch.yml`,插件行按包名引用。
|
|
1263
|
+
- 构建产物 `lib/index.js`(Host)与 `lib/client.js`(浏览器)随仓库提交,GitHub 安装无需构建授权。
|
|
1264
|
+
- 附带宿主半行为测试(26 项)与线级请求头探针。
|