dsh-sidecard-ask 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1175 -1106
- package/README.md +1073 -1030
- package/client.js +7470 -7348
- package/index.js +2811 -2749
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,1030 +1,1073 @@
|
|
|
1
|
-
# dsh-sidecard-ask · 侧边卡片追问
|
|
2
|
-
|
|
3
|
-
在 DSH Web GUI 里**选中一段文本就能就地追问**:选中处浮出「追问选中内容」按钮 → 弹出提问框 →
|
|
4
|
-
答案落在**两个可切换的承载位置**上:
|
|
5
|
-
|
|
6
|
-
- **独立侧边卡片(默认)**:由子代理在**自己的会话**里作答(不继承父上下文、不打扰主对话),答案在卡片里逐字流式渲染,可关闭 / 复制 / 继续追问;
|
|
7
|
-
- **主对话**:追问内容带着引用块进入当前会话,答案随对话原生流式呈现。
|
|
8
|
-
|
|
9
|
-
聊天记录、任务详情、右侧栏内容……只要是能选中的文本都能用;**没装侧边卡片插件也能跑**(自动回退到内置浮层卡片)。
|
|
10
|
-
|
|
11
|
-
> **1.8.4 增强一览**(详见 CHANGELOG):选区旁的动作条不再「六枚等价胶囊排成一排」——改成**一块面板 + 一个主操作**:
|
|
12
|
-
> 品牌实底按钮「追问选中内容」、一条细分隔线、五个无边框文字胶囊(hover 才浮出底色),深色主题把主按钮
|
|
13
|
-
> 的字改成深墨色(白字在深色品牌上只有 2.79:1);宽度也改成按「视口宽 − 面板左边 − 8」算,
|
|
14
|
-
> 放不下就换行,不再被窗口右边缘切掉。见 §4.3。
|
|
15
|
-
|
|
16
|
-
> **1.8.0 增强一览**(P2,详见 CHANGELOG):**回答风格档位**(`answerStyle` 精简/标准/详细)
|
|
17
|
-
> 与 ≤500 字的自定义指令;改写类答案可**写回主对话输入框**(先做了真实页面探针,走官方
|
|
18
|
-
> `inputActions.insertText`,DOM 路径退化为 `beforeinput` 追加);卡片与历史条目可**查看原文**
|
|
19
|
-
> (码点签名反查 + CSS Custom Highlight 高亮,找不到会明说);可选的**模型建议追问**芯片
|
|
20
|
-
> (`<followups>` 块,宽容解析,不额外发请求);**导出 .md**(单卡 / 全部)与**多版本答案**
|
|
21
|
-
> (localStorage 升到 v3,v1/v2 非破坏式迁移)。
|
|
22
|
-
|
|
23
|
-
> **1.5.0 增强一览**(详见 CHANGELOG):内置浮层不再压住主对话——宽度改由纯函数 `floatPlacement`
|
|
24
|
-
> 按「对话区右侧可用带宽」计算(旧实现是固定 400px,在用户截图上压入正文约 113px);浮层默认是**一行高的胶囊**,
|
|
25
|
-
> 可随时「收起为胶囊」,运行时的形态切换不写回配置;卡片标题栏右侧的 `×` 自 2.0.0 起是**删除这条追问对话**
|
|
26
|
-
> (连同追问历史一起删),不再是隐藏浮层;新增 `floatMode`(默认 `capsule`)与
|
|
27
|
-
> `floatMaxWidth`(默认 340)两项设置;卡片阴影减半。
|
|
28
|
-
|
|
29
|
-
> **1.4.0 增强一览**(详见 CHANGELOG):卡片**思考过程**(reasoning)可折叠展示并随答案流式渲染;
|
|
30
|
-
> **已完成卡片写入 localStorage,刷新页面不丢**;答案渲染扩展链接/斜体/引用/表格;流式输出自动滚动到底;
|
|
31
|
-
> 设置页补齐 `sideTimeoutMs` / `sideProvider` 控件;原生右侧栏 tab
|
|
32
|
-
>
|
|
33
|
-
>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
>
|
|
37
|
-
>
|
|
38
|
-
>
|
|
39
|
-
>
|
|
40
|
-
> `
|
|
41
|
-
>
|
|
42
|
-
>
|
|
43
|
-
>
|
|
44
|
-
>
|
|
45
|
-
> `dsh-selection-
|
|
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
|
-
├── locale/
|
|
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
|
-
avail =
|
|
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
|
-
1.
|
|
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
|
-
3
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
1.
|
|
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
|
-
1.
|
|
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
|
-
|
|
1
|
+
# dsh-sidecard-ask · 侧边卡片追问
|
|
2
|
+
|
|
3
|
+
在 DSH Web GUI 里**选中一段文本就能就地追问**:选中处浮出「追问选中内容」按钮 → 弹出提问框 →
|
|
4
|
+
答案落在**两个可切换的承载位置**上:
|
|
5
|
+
|
|
6
|
+
- **独立侧边卡片(默认)**:由子代理在**自己的会话**里作答(不继承父上下文、不打扰主对话),答案在卡片里逐字流式渲染,可关闭 / 复制 / 继续追问;
|
|
7
|
+
- **主对话**:追问内容带着引用块进入当前会话,答案随对话原生流式呈现。
|
|
8
|
+
|
|
9
|
+
聊天记录、任务详情、右侧栏内容……只要是能选中的文本都能用;**没装侧边卡片插件也能跑**(自动回退到内置浮层卡片)。
|
|
10
|
+
|
|
11
|
+
> **1.8.4 增强一览**(详见 CHANGELOG):选区旁的动作条不再「六枚等价胶囊排成一排」——改成**一块面板 + 一个主操作**:
|
|
12
|
+
> 品牌实底按钮「追问选中内容」、一条细分隔线、五个无边框文字胶囊(hover 才浮出底色),深色主题把主按钮
|
|
13
|
+
> 的字改成深墨色(白字在深色品牌上只有 2.79:1);宽度也改成按「视口宽 − 面板左边 − 8」算,
|
|
14
|
+
> 放不下就换行,不再被窗口右边缘切掉。见 §4.3。
|
|
15
|
+
|
|
16
|
+
> **1.8.0 增强一览**(P2,详见 CHANGELOG):**回答风格档位**(`answerStyle` 精简/标准/详细)
|
|
17
|
+
> 与 ≤500 字的自定义指令;改写类答案可**写回主对话输入框**(先做了真实页面探针,走官方
|
|
18
|
+
> `inputActions.insertText`,DOM 路径退化为 `beforeinput` 追加);卡片与历史条目可**查看原文**
|
|
19
|
+
> (码点签名反查 + CSS Custom Highlight 高亮,找不到会明说);可选的**模型建议追问**芯片
|
|
20
|
+
> (`<followups>` 块,宽容解析,不额外发请求);**导出 .md**(单卡 / 全部)与**多版本答案**
|
|
21
|
+
> (localStorage 升到 v3,v1/v2 非破坏式迁移)。
|
|
22
|
+
|
|
23
|
+
> **1.5.0 增强一览**(详见 CHANGELOG):内置浮层不再压住主对话——宽度改由纯函数 `floatPlacement`
|
|
24
|
+
> 按「对话区右侧可用带宽」计算(旧实现是固定 400px,在用户截图上压入正文约 113px);浮层默认是**一行高的胶囊**,
|
|
25
|
+
> 可随时「收起为胶囊」,运行时的形态切换不写回配置;卡片标题栏右侧的 `×` 自 2.0.0 起是**删除这条追问对话**
|
|
26
|
+
> (连同追问历史一起删),不再是隐藏浮层;新增 `floatMode`(默认 `capsule`)与
|
|
27
|
+
> `floatMaxWidth`(默认 340)两项设置;卡片阴影减半。
|
|
28
|
+
|
|
29
|
+
> **1.4.0 增强一览**(详见 CHANGELOG):卡片**思考过程**(reasoning)可折叠展示并随答案流式渲染;
|
|
30
|
+
> **已完成卡片写入 localStorage,刷新页面不丢**;答案渲染扩展链接/斜体/引用/表格;流式输出自动滚动到底;
|
|
31
|
+
> 设置页补齐 `sideTimeoutMs` / `sideProvider` 控件;原生右侧栏 tab 可关闭(不再残留空壳;该能力在
|
|
32
|
+
> 0.2.1-rc.3 上一度因控制器无 `closeTab` 而失效,2.1.2 改用 `closeIn` 修复);
|
|
33
|
+
> 界面语言解析支持 `navigator.languages` 优先级回退,英文界面显示英文错误文案;
|
|
34
|
+
> 长答案的流式增量按 50ms 窗口合并提交(避免整卡重渲染的二次方开销)。
|
|
35
|
+
|
|
36
|
+
> ## 名字改过两次,原因都写在这里(避免再撞名)
|
|
37
|
+
>
|
|
38
|
+
> 1. **`dsh-selection-ask` → ✗**:npm 上已被 `chestnut23` 占用(仓库 `lzbaclz/dsh-selection-ask`)。
|
|
39
|
+
> 2. **`dsh-selection-followup` → ✗**(1.0.0 / 1.0.1 用的名字):npm 上当时是空的,但 **DSH 插件生态里已被 `zzx-dear` 使用**——
|
|
40
|
+
> GitHub 仓库 [`zzx-dear/dsh-selection-followup`](https://github.com/zzx-dear/dsh-selection-followup) 与官方收录索引
|
|
41
|
+
> `data/plugins/zzx-dear__dsh-selection-followup.yml`(category: ui,早于本插件约 20 天)。
|
|
42
|
+
> 我最初只查了 npm 名字是否可用,**漏查了 GitHub 仓库名与收录索引**,这是本项目的失误,已在 1.1.0 修正。
|
|
43
|
+
> 3. **`dsh-sidecard-ask` → ✓**(1.1.0 起):npm 无同名包、GitHub 无同名仓库;
|
|
44
|
+
> 同时它与已有的 8 个"选中文字→引用进输入框/翻译/批注"类插件(`dsh-selection-ask`、`dsh-selection-explain`、
|
|
45
|
+
> `dsh-selection-toolbar`、`dsh-quote-selection`、`dsh-ui-quote-selection`、`dsh-quote-annotate`、
|
|
46
|
+
> `dsh-selection-memory`、`dsh-plugin-followup`)在**名字与定位上都区分开**:本插件的答案是**独立子代理在侧边卡片里流式产出**,
|
|
47
|
+
> 而不是把选中内容塞进输入框。
|
|
48
|
+
>
|
|
49
|
+
> 旧 npm 包名 `dsh-selection-followup` 已 `npm deprecate` 指向新名字;升级只需卸旧装新(见 §三)。
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 一、三条关键路径(先看这个)
|
|
56
|
+
|
|
57
|
+
| 路径 | 操作 | 期望结果 |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| **A. 就地追问(侧边卡片)** | 在聊天区选中一句话 → 点浮出的按钮(或点旁边的「解释/翻译/总结/润色/纠错」芯片一键发起)→ 输入问题 → Enter | 右下角浮出卡片,答案逐字流出;卡片底部有「复制 / 继续追问 / 改问 / 关闭」,代码块右上角可单独复制 |
|
|
60
|
+
| **B. 主对话追问** | 选中 → 提问框里把「作答位置」切到**主对话** → Enter | 追问以引用块形式出现在主对话,答案由当前会话正常流式输出 |
|
|
61
|
+
| **C. 快捷键 + 降级** | 选中 → 按 `Alt+Q`(默认) | 直接弹出提问框;若侧边作答引擎不可用,卡片给出错误提示与「改到主对话」按钮 |
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 二、目录结构与逐文件用途
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
dsh-sidecard-ask/
|
|
69
|
+
├── package.json 清单:包名/版本/导出/dsh.bundle.patch/dsh.client(web 平台、immediately、inject)
|
|
70
|
+
├── cordis.patch.yml bundle 补丁:向 profile 插入 id 为 sidecard-ask 的宿主行,并给出全部默认配置
|
|
71
|
+
├── index.js Host 半(宿主侧):插件自有 API(JSON + SSE)、配置三层合并与持久化、
|
|
72
|
+
│ 独立作答引擎(ctx.subagents 子代理 + agent/assistant-stream 桥接)、请求信任围栏
|
|
73
|
+
├── client.js Client 半(浏览器侧):dsh.client 浏览器产物,单文件、无构建步骤
|
|
74
|
+
│ §1 常量与默认值 §2 i18n §3 线协议客户端 §4 模块级 store §5 选区引擎
|
|
75
|
+
│ §6 两种承载方式 §7 侧边承载面适配 §8 视图工具 §9 组件 §10 插件入口
|
|
76
|
+
├── locale/zh.json 插件元数据的中文显示文案(Plugins 页卡片标题/描述)
|
|
77
|
+
├── locale/en.json 同上,英文
|
|
78
|
+
├── icon.svg Plugins 页卡片图标(<256 KiB,相对路径)
|
|
79
|
+
├── README.md 本文件
|
|
80
|
+
├── CHANGELOG.md 版本变更记录
|
|
81
|
+
├── LICENSE MIT
|
|
82
|
+
├── design-preview/ 方案讨论资料,**不参与发布**:00–02 是手绘 SVG 示意图(非截图),
|
|
83
|
+
│ 03-prototype-ac.html 是可直接双击打开的可点按原型(自带近似色板,非真实观感),
|
|
84
|
+
│ 07-carrier-seg-{light,dark}.png 是 1.8.2 的离屏渲染对比图(无头 Edge + 真实 CSS
|
|
85
|
+
│ 与真实令牌值,仍非运行中窗口截图)
|
|
86
|
+
├── tools/
|
|
87
|
+
│ └── compat-probe.mjs DSH 版本能力探测(§7 矩阵的来源)
|
|
88
|
+
└── test/
|
|
89
|
+
├── harness.mjs 测试替身:假宿主 ctx / 假 req·res / 假子代理 provider / 合成浏览器 + 迷你 React
|
|
90
|
+
├── verify.mjs 静态完整性:清单、补丁、两半导出面、i18n 键覆盖、零依赖承诺
|
|
91
|
+
├── contract-test.mjs 两端契约:常量一致性、响应信封形状、SSE 逐帧往返、纯函数等价、槽位注册与渲染
|
|
92
|
+
├── float-placement.mjs 浮层几何:宽度预算不越过对话区右边界、胶囊默认形态、源码钩子(1.5.0 新增)
|
|
93
|
+
└── smoke-test.mjs 宿主端到端:流式、截断、取消、持久化与重启、失败分支、并发上限、传输围栏
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**不引入任何外部依赖**:宿主半只 import `node:` 内建模块;客户端半只 `require('react')`(由浏览器模块表提供)。
|
|
97
|
+
`test/` 目录不进 `files`,不会发布。
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 三、安装与启用
|
|
102
|
+
|
|
103
|
+
### 方式 1:从 npm 安装(推荐)
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
# 在 profile 目录(默认 %USERPROFILE%\.dsh\profiles\web)执行
|
|
107
|
+
pnpm add dsh-sidecard-ask
|
|
108
|
+
# 然后把包名写进 profile package.json 的 dsh.profile.bundles 数组
|
|
109
|
+
# "dsh.profile": { "bundles": [ ..., "dsh-sidecard-ask" ] }
|
|
110
|
+
# 重启 DSH(重启后插件行、客户端产物、设置页一起生效)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 方式 2:让 DSH 自己装(本工作区源码)
|
|
114
|
+
|
|
115
|
+
在会话里让 Agent 执行 `plugin_manager` 的 `install_bundle`,target 指向本目录的**绝对路径**:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
plugin_manager(action="install_bundle", target="D:\\Users\\34332\\AI\\dsh-sidecard-ask")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`install_bundle` 会自行完成 profile 的写入与 bundle 选择,**不要**手改 profile 的 `package.json` / `cordis.patch.yml`。
|
|
122
|
+
|
|
123
|
+
### 方式 3:本地联调(junction)
|
|
124
|
+
|
|
125
|
+
```powershell
|
|
126
|
+
cmd /c mklink /J "%USERPROFILE%\.dsh\profiles\web\node_modules\dsh-sidecard-ask" "D:\Users\34332\AI\dsh-sidecard-ask"
|
|
127
|
+
# 再手动把 "dsh-sidecard-ask" 加进 profile package.json 的 dsh.profile.bundles
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 启用与验证
|
|
131
|
+
|
|
132
|
+
1. 重启 DSH(新包需要重启才会加载一行全新的 JavaScript 模块)。
|
|
133
|
+
2. 打开 Web GUI → **设置 → 插件**,应能看到卡片「侧边卡片追问」。
|
|
134
|
+
3. 打开**设置 → 侧边卡片追问**页,点「运行自检」:应显示宿主接口可用、侧边作答引擎可用、provider 列表(本机为 `spawn`)、活动会话代理数。
|
|
135
|
+
4. 若自检显示"宿主接口不可达",说明 `/sidecard-ask/api` 路由没起来——检查 profile 里该 bundle 是否在 `dsh.profile.bundles` 中、以及 DSH 是否重启过。
|
|
136
|
+
|
|
137
|
+
### 从旧名字升级(1.0.x → 1.1.0)
|
|
138
|
+
|
|
139
|
+
```powershell
|
|
140
|
+
# 1) 卸掉旧 bundle(profile 里旧名字是 dsh-selection-followup)
|
|
141
|
+
plugin_manager(action="remove_bundle", target="dsh-selection-followup")
|
|
142
|
+
# 2) 装新名字(本工作区源码)
|
|
143
|
+
plugin_manager(action="install_bundle", target="D:\\Users\\34332\\AI\\dsh-sidecard-ask")
|
|
144
|
+
# 3) 重启 DSH;旧配置目录 <DSH_HOME>\selection-followup 可以删掉(新名字用 <DSH_HOME>\sidecard-ask)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
> 改了 `client.js` 后如果 `pnpm run dev:web` 没有在跑,浏览器需要刷新页面;改了 `index.js` 需要重启 DSH。
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 四、配置项说明表
|
|
152
|
+
|
|
153
|
+
配置有**三层**,优先级从低到高:
|
|
154
|
+
|
|
155
|
+
1. **内置默认值**(`index.js` 的 `DEFAULT_CONFIG`)
|
|
156
|
+
2. **bundle 补丁层**:`cordis.patch.yml` 的 `config:`(改这里需要重启)
|
|
157
|
+
3. **用户层**:设置页保存后写入 `<DSH_HOME>/sidecard-ask/config.<profile>.json`,其中 `<profile>` 取自
|
|
158
|
+
`DSH_PROFILE`(web 端与桌面端各一份,互不覆盖);`DSH_PROFILE` 未设置时用旧名 `config.json`
|
|
159
|
+
(`DSH_HOME` 未设置或为空白时回退到 `~/.dsh`;**不会**写进程当前目录)
|
|
160
|
+
|
|
161
|
+
设置页点「重置为 patch 配置」会清空用户层。
|
|
162
|
+
|
|
163
|
+
| 配置项 | 类型 / 取值 | 默认 | 作用 | 生效方式 |
|
|
164
|
+
|---|---|---|---|---|
|
|
165
|
+
| `trigger` | `selection` \| `shortcut` \| `both` | `selection` | **触发方式**:选中即浮出按钮 / 只用快捷键 / 两者都要 | 改后立即(客户端读 /state) |
|
|
166
|
+
| `defaultCarrier` | `main` \| `side` | `side` | **默认作答位置**:提问框里仍可临时切换 | 立即 |
|
|
167
|
+
| `sideSurface` | `auto` \| `native-rightbar` \| `better-sidebar` \| `flow` | `auto` | **窗口模式(侧边承载面)**:自动挑选(先 dsh-better-sidebar,再内置浮层卡片)/ 强制 DSH 原生右侧栏(不参与自动)/ 强制 dsh-better-sidebar / 强制内置浮层卡片 | 立即 |
|
|
168
|
+
| `maxChars` | 200–60000 | `4000` | **最大字符数**:超出部分头尾保留、中间截断并标注 | 立即 |
|
|
169
|
+
| `floatMode` | `capsule` \| `full` | `capsule` | **内置浮层的默认形态**:胶囊(一行,最小遮挡)/ 完整卡片栈。运行时可即时收起为胶囊,但不写回本项——刷新后仍回到这里设的默认。旧配置里存过 `off` 的会在启动时校验失败、回退为 `capsule`,并在 `problems` 里给出提示(`index.js` 的 `sanitizeLayer`),无需手工迁移 | 立即 |
|
|
170
|
+
| `floatMaxWidth` | 240–560 | `340` | **内置浮层宽度上限**(px):实际宽度 = `min(本项, 对话区右侧可用带宽)`,可用带宽不足 240px 时才会压入对话区 | 立即 |
|
|
171
|
+
| `dockRail` | 布尔 | `true` | **右侧栏打开时占满它**:答案卡铺满右栏标签条下方的内容区,读起来像栏内的一页,而不是 340px 的小浮层。收起为胶囊、点卡片底部「关闭」、右栏宽度 < 280px 或高度 < 240px 时自动回到常规浮层;实现靠测量右栏 DOM 矩形,**没有**走 DSH 的右栏 API(见 §4.2 与 §6.1) | 立即 |
|
|
172
|
+
| `shortcut` | 形如 `Alt+Q`、`Ctrl+Shift+K` | `Alt+Q` | **快捷键**:唤起提问框(`trigger` 允许时) | 立即 |
|
|
173
|
+
| `captureZones` | `auto` \| `chat` \| `task` \| `chat+task` | `auto` | 只在哪些区域触发 | 立即 |
|
|
174
|
+
| `showInUnclassified` | 布尔 | `true` | 无法归类的区域是否也触发 | 立即 |
|
|
175
|
+
| `minChars` | 0–200 | `2` | 少于该字符数的选区不触发 | 立即 |
|
|
176
|
+
| `maxConcurrentAsks` | 1–12 | `3` | 侧边卡片并发作答上限,超出返回 `busy`(可重试) | 立即 |
|
|
177
|
+
| `sideTools` | `readonly` \| `inherit` | `readonly` | 侧边作答者的工具权限:只读 / 继承当前会话。**注意**:0.2.1 的 `SubagentStartRequest` 没有 `toolFilter`,`readonly` 实际由「探针拒写」兜住(发一条会写盘的指令,被拒即判定只读链路有效);要真正收窄工具集请用下一条 | 下一次提问 |
|
|
178
|
+
| `sideAgentPreset` | 字符串,≤120 字符 | `''` | **只读 preset**:子代理以该 agent preset 启动,工具集由 preset 决定——这是 0.2.1 上唯一可靠的收窄通道(`agentPreset` 能力位存在时生效,否则退回探针)。留空 = 不启用 | 下一次提问 |
|
|
179
|
+
| `sideTimeoutMs` | 5000–3600000 | `180000` | 单次侧边作答超时 | 下一次提问 |
|
|
180
|
+
| `sideProvider` | 字符串 \| `auto` | `auto` | 指定子代理 provider(`auto` 优先 `spawn`) | 下一次提问 |
|
|
181
|
+
| `quickActions` | 动作数组(≤12 条) | 5 个内置动作 | **一键快捷动作**:选中后按钮旁的动作芯片。条目形如 `{id, enabled, shortcut}`;内置 id 为 `builtin:explain/translate/summarize/polish/fix`,自定义动作(非 `builtin:` 前缀)还需 `label`(≤20 字)与 `question`(≤500 字,可用 `{selection}` 内嵌选中正文)。设置页可视化编辑,用户层整表覆盖默认值 | 立即(快捷键下次按键生效) |
|
|
182
|
+
| `notifyOnDone` | 布尔 | `false` | 页面在后台时,作答完成/失败是否弹系统通知;首次开启需浏览器通知权限 | 立即 |
|
|
183
|
+
| `contextMenu` | 布尔 | `true` | 是否在可触发选区的右键菜单顶部加入追问与快捷动作;与宿主菜单冲突时可关 | 立即 |
|
|
184
|
+
| `historyMaxCards` | 0–50 | `10` | 历史卡片持久化上限(localStorage v3);`0` = 不写盘(已有历史保留到显式清空) | 立即 |
|
|
185
|
+
| `answerStyle` | `concise` \| `standard` \| `detailed` | `standard` | **回答风格档位**:只给最小完整答案 / 现状 / 必要时展开并给出依据。指令追加在人格之后,**不会**覆盖"用提问的语言作答"与"选中文本是数据"这两条 | 下一次提问 |
|
|
186
|
+
| `customPersona` | 字符串,≤500 码点 | `''` | **自定义指令**:追加在风格档位之后,以 `Additional user instruction:` 标注随每次提问发送(例如"尽量给可验证来源") | 下一次提问 |
|
|
187
|
+
| `followUpSuggestions` | 布尔 | `false` | **模型生成的建议追问**:让模型在答案末尾用 `<followups>问1 \| 问2</followups>` 给出 2–3 个可点的下一步问题(客户端剥离该块并渲染为芯片,**不额外发请求**) | 下一次提问 |
|
|
188
|
+
| `feedbackAddress` | 字符串(邮箱或 `b64:` 编码)\| `''` | 维护者收件箱(`b64:` 混淆存储) | **反馈收件邮箱**:设置页「反馈」弹窗把问题与建议发到这里。默认值指向维护者自己的邮箱,以 `b64:<base64>` 形式打包——**这是混淆不是加密**,`decodeAddress()` 就在同包里,只能挡住正则抓取;环境变量 `DSH_SIDECARD_FEEDBACK_TO` / `DSH_CRB_FEEDBACK_TO` **优先于**本项,置空则反馈弹窗只留「复制内容」 | 立即 |
|
|
189
|
+
| `feedbackSmtpHost` | 字符串,≤255 | `''` | **SMTP 服务器**。1.9.1 起只从**环境变量或用户层配置**读。**环境变量优先于配置**(`DSH_SIDECARD_SMTP_URL` / `DSH_CRB_SMTP_URL` 等能凑出完整端点时,配置里的整段不生效);只有用户层配置会存它,**不进仓库** | 立即 |
|
|
190
|
+
| `feedbackSmtpPort` | 数字,1–65535 | `465` | SMTP 端口。465 配隐式 TLS,587 配 STARTTLS(服务器没声明 STARTTLS 时不会硬升级) | 立即 |
|
|
191
|
+
| `feedbackSmtpSecure` | 布尔 | `true` | 隐式 TLS(465)。关掉则按 STARTTLS 处理 | 立即 |
|
|
192
|
+
| `feedbackSmtpUser` | 字符串,≤254 | `''` | SMTP 登录账号。部分中继给的是裸账号名而非邮箱,故不强制邮箱格式 | 立即 |
|
|
193
|
+
| `feedbackSmtpPass` | 字符串,≤512 | `''` | **密码 / 授权码**(QQ、163 必须用授权码)。**只写不读**:`/state` 会删掉它、页面永远拿到空串;留空保存=保留原值(要删掉已存的密码用下一行的 `feedbackSmtpPassClear`) | 立即 |
|
|
194
|
+
| `feedbackSmtpPassClear` | 布尔 | `false` | **显式清除已存的 SMTP 密码**(典型场景:改回用环境变量)。只在 `/config` 请求里生效,本身不落盘;与 `feedbackSmtpPass` 同时出现时以清除为准 | 立即 |
|
|
195
|
+
| `feedbackSmtpFrom` | 字符串(邮箱)\| `''` | `''` | 发信账号显式指定的 `From:`,留空等于 `feedbackSmtpUser`。必须是 SMTP 服务商允许的地址 | 立即 |
|
|
196
|
+
|
|
197
|
+
非法值会被拒绝(设置页报错、接口返回 `invalid-config`),并把被忽略的项列在 `/state` 的 `problems` 里——不会静默吞掉。
|
|
198
|
+
|
|
199
|
+
### 4.1 内置浮层的位置规则(`floatPlacement`)
|
|
200
|
+
|
|
201
|
+
用户实测报告过一个问题:浮层是 `position:fixed; right:16px; width:min(400px,92vw)` 的实心卡片。
|
|
202
|
+
实测那一次(1400px 宽的窗口)对话区右边界在 x≈971,而浮层左边界在 x≈858,**压住正文约 113px**,
|
|
203
|
+
最后几行答案被盖住。两点如实说明:**旧实现并不是任何窗口宽度下都越界**——1400px 窗口理论上
|
|
204
|
+
`1400-16-400=984`,还在 971 右侧;越界与否取决于当时的帧宽与对话区带宽,而固定 400px 的选择
|
|
205
|
+
让「窗口一窄就必然压住正文」。这条读数(971 / 858 / 113px)是截图上的像素测量,不是日志里的一手数据。
|
|
206
|
+
现在的位置由 `client.js` 里的纯函数 `floatPlacement` 决定:
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
// floatPlacement({ frameWidth, laneRight, railLeft = null, maxWidth = floatMaxWidth })
|
|
210
|
+
avail = 帧宽 - 8 - 对话区右边界 // 对话区右侧那条「预留带」
|
|
211
|
+
if (右栏存在) avail = min(avail, 帧宽 - 右栏左边界 - 10)
|
|
212
|
+
avail = max(240, floor(avail)) // 240px 以下胶囊也不可用,故为硬下限
|
|
213
|
+
width = min(floatMaxWidth, avail)
|
|
214
|
+
left = 帧宽 - 8 - width // 右锚定
|
|
215
|
+
bleeds = 对话区右边界 - left > 1 // 只有带宽 < 240px 时才可能为 true
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
- 浮层**只使用对话区右侧的预留带**(右栏也在这条带里),窗口越窄它越窄,而不是越靠左。
|
|
219
|
+
- `capped: true` 表示「上限没用满,是被可用带宽压住的」——这是正常的自适应,不是错误。
|
|
220
|
+
- 挂载时、窗口 resize 时、右栏开合时(`ResizeObserver`)都会重算;流式刷新只重渲染,不动几何。
|
|
221
|
+
- 浮层根节点带 `data-layer-width`(实际宽度)与 `data-dsa-float-mode`:报告问题时可直接读这两个值。
|
|
222
|
+
|
|
223
|
+
### 4.2 右侧栏打开时「占满它」(`dockRail`,1.8.3)
|
|
224
|
+
|
|
225
|
+
340px 的浮层在右栏打开时会同时压住两块内容:主对话的最后几行、以及右栏自己的正文。1.8.3 起默认
|
|
226
|
+
(`dockRail: true`)把整张卡铺到右栏里,读起来像栏内的一页:
|
|
227
|
+
|
|
228
|
+
- 位置 = 右栏**标签条之下**的内容区矩形,宽度取右栏宽度,高度取到底部;右栏自己的标签条仍然露出、可点。
|
|
229
|
+
- 卡片在这个形态下没有圆角/描边/阴影(`.dsa-card` 的装饰被 `[data-dsa-float-mode="dock"]` 覆盖),只有一条左边线与右栏内容区分界。
|
|
230
|
+
- 标题栏(追问 + 徽标 + ⇥ / ×)与底部操作行固定上下两端,中间 `AnswerBody` 独立滚动——长答案不再把操作行推出视野。
|
|
231
|
+
- 收起为胶囊(⇥)、删除这条追问对话(×)、或把 `dockRail` 关掉,都立即回到常规浮层;右栏宽度 < 280px 或高度 < 240px 时也回退(窄栏里铺满会把正文挤成每行 8–9 字)。注意 × 是**删除**:卡片从浮层与追问历史里一并移除;只想摘离浮层、保留历史请用底部的「关闭」。
|
|
232
|
+
- 折叠的右栏不会被误用:`railOnScreen()` 会拒绝停在帧外(`left >= frameWidth - 1`)或尺寸不足的面板,量不到就回退,绝不把卡片画到帧外。
|
|
233
|
+
- 矩形从哪来:**优先量本插件自己渲染在右栏里的 `CardHost` 节点**(`[data-dsa-rail-host]`)——它所在的位置按定义就是「一页正文」,不依赖任何未被文档承诺的标记;找不到时再按 `data-sidebar-right-*`、dockkit 世代、`.rightbar`、`sidebar.right` 槽逐代回退。
|
|
234
|
+
- 什么时候重算:窗口改宽、右栏开合(只盯属性的 `MutationObserver`)、右栏自身改宽(`ResizeObserver`),以及挂载后的四次延时重测(罩住右栏的滑入动画)。流式刷新不会触发几何重算。
|
|
235
|
+
|
|
236
|
+
为什么是「测量」而不是「右栏内的一页」:DSH 的右栏服务确实存在(`sidebarRightTabs` / `sidebarRight`,
|
|
237
|
+
见 §6.1),本插件也有完整注册代码,但在当前构建里 `openTab` 路径实测仍回落到浮层,且没定位到抛错点。
|
|
238
|
+
在它修好之前,`dockRail` 是视觉上等价、失败即回退的兜底层——它盖住右栏内容区的其他面板(那些面板仍在
|
|
239
|
+
下面,取消勾选或收起胶囊即恢复),也会暂时压住右栏的拖拽改宽手柄。
|
|
240
|
+
|
|
241
|
+
诊断:`data-dsa-float-mode="dock"` 表示处于该形态;`.dsa-stack` 上的 `data-dsa-dock-body="<px>"` 是量到的
|
|
242
|
+
宽度(与卡片宽度一致)。浮层根节点还有 `data-dsa-dock`,它的取值直接说明**为什么**没有铺满:
|
|
243
|
+
|
|
244
|
+
| 值 | 含义 |
|
|
245
|
+
|---|---|
|
|
246
|
+
| `dock` | 正在铺满 |
|
|
247
|
+
| `no-rail` | 没找到已展开的右栏(右栏关着,或这个构建的标记与预期不同) |
|
|
248
|
+
| `rail-too-small` | 找到了,但宽 < 280px / 高 < 240px,或它停在帧外(折叠态被拒绝) |
|
|
249
|
+
| `off` | 设置里的 `dockRail` 被关掉了 |
|
|
250
|
+
| `mode` | 当前不是完整卡片形态(胶囊 / 折叠态),本来就不该铺满 |
|
|
251
|
+
|
|
252
|
+
### 4.3 选区旁的动作条(外观)
|
|
253
|
+
|
|
254
|
+
选中文本后浮出的那条东西以前是**六枚等价的胶囊排成一排**(`追问选中内容` 与 `解释/翻译/总结/润色/纠错`),
|
|
255
|
+
彼此同色同粗、各带边框和阴影:既没有主次,也没有分组,窗口窄一点或选区靠右时整条还会顶出右边缘被裁掉。
|
|
256
|
+
现在它是**一块面板 + 一个主操作**:
|
|
257
|
+
|
|
258
|
+
- 面板 `.dsa-quickbar`:`padding:3px`、`border-radius:12px`、中灰描边、`0 6px 20px` 投影,整条读作一个控件;
|
|
259
|
+
- 主按钮(`.dsa-trigger-embedded`):品牌实底 + 白字 + 600 字重(深色主题改为深墨字,原因同 §九 A 的分段芯片:
|
|
260
|
+
uiskin 深色品牌 `#5b9cf5` 上白字只有 2.79:1);
|
|
261
|
+
- 细分隔线 `.dsa-quickbar-sep`(1px × 15px,`border-l1`)把「做那一件事」和「或从这些现成问题里挑一个」分开;
|
|
262
|
+
- 五个快捷动作降级为**无边框文字胶囊**(`.dsa-quickbar .dsa-chip`):透明底 + `label-secondary` 字色,
|
|
263
|
+
hover 才浮出 `bg-layer-2` 底与 `label-primary` 字色——层级低于主按钮,但仍然一眼可点;
|
|
264
|
+
- **宽度不再靠估算**:组件给面板加 `max-width: min(620, 视口宽 - 面板左边 - 8)`,放不下就换行,
|
|
265
|
+
不会被窗口边缘切掉;分隔线随主按钮留在第一行,换行的胶囊继续从左边排。
|
|
266
|
+
|
|
267
|
+
外观之外没有行为变化:键盘漫游、`Alt+1…5`、右键菜单、点击后直接发问都照旧。这条也不写配置项——
|
|
268
|
+
它是版式修正,不需要开关。
|
|
269
|
+
|
|
270
|
+
### 4.4 设置页的「反馈」入口(邮件)
|
|
271
|
+
|
|
272
|
+
设置页**最顶部**是一条品牌色描边的**反馈横幅**(`dsa-feedback-bar`):一行文字 + 右侧主按钮,**点开才是表单**,
|
|
273
|
+
表单放在一个居中对话框(`role="dialog"`,`Esc` / 点背景 / 右上角 `×` 都能关)。1.9.1 之前是「顶部常驻内联表单」,
|
|
274
|
+
它把真正的设置项全挤到屏幕外,被否掉后重做成现在这种「一条横幅 + 一个弹窗」。表单字段:类型(问题 / 建议 / 疑问 / 其他)、
|
|
275
|
+
标题、详细描述、可选的**你的邮箱**(填了就写进邮件的 `Reply-To:`,维护者按「回复」能直接回给你;留空即匿名)。
|
|
276
|
+
**设置页上没有任何 SMTP 输入框**:发信凭据属于部署方/维护者,走环境变量或用户层配置(见下),填报人不用管。
|
|
277
|
+
两条出口,从首选到兜底:
|
|
278
|
+
|
|
279
|
+
| 出口 | 何时可用 | 说明 |
|
|
280
|
+
|---|---|---|
|
|
281
|
+
| **发送**(推荐) | 收件邮箱已配置 **且** SMTP 凭证可用(环境变量或用户层配置) | 插件直接把邮件投给 SMTP 服务器;成功后清空草稿 |
|
|
282
|
+
| **复制内容** | 总是 | 把同样的纯文本(含版本与环境)放进剪贴板,用户自己粘到任意渠道 |
|
|
283
|
+
|
|
284
|
+
> 1.9.x 曾有的第三条出口「用邮件客户端发送」(`POST /feedback-mailto` 返回拼好的 `mailto:` URL)已在
|
|
285
|
+
> 2.0.0 整体移除:它让插件多出一条谁都可以调的出口,却只是把同一封邮件交给系统邮件应用,与「复制内容」的
|
|
286
|
+
> 实际效果重叠。现在没有发信服务时,弹窗直接说明缺什么,并指向「复制内容」。
|
|
287
|
+
|
|
288
|
+
**收件邮箱默认可用,也可被环境变量接管。** 打包默认值是维护者邮箱(`b64:` 混淆存储,见上表);
|
|
289
|
+
环境变量 `DSH_SIDECARD_FEEDBACK_TO` 或 `DSH_CRB_FEEDBACK_TO`(与 dsh-conversation-recycle-bin 共用)
|
|
290
|
+
**优先**于它。用户层配置落在 `$DSH_HOME/sidecard-ask/config.<profile>.json`
|
|
291
|
+
(`<profile>` = `DSH_PROFILE`;Windows 默认 `C:\Users\<你>\.dsh\sidecard-ask\config.web.json`
|
|
292
|
+
/ `config.desktop.json`,两个壳各一份)——**不在插件目录里,因此不会被提交、不会进 npm 包**。
|
|
293
|
+
|
|
294
|
+
**SMTP 凭证有两种给法**,都落在部署方/维护者这一侧(1.9.1 起设置页不再提供填写框):
|
|
295
|
+
|
|
296
|
+
1. **用户层配置**(`$DSH_HOME/sidecard-ask/config.<profile>.json`,Windows 默认
|
|
297
|
+
`C:\Users\<你>\.dsh\sidecard-ask\config.web.json`,或 profile 的 `cordis.patch.yml` 里给该行写 `config`):
|
|
298
|
+
`feedbackSmtpHost` / `feedbackSmtpPort` / `feedbackSmtpSecure` / `feedbackSmtpUser` / `feedbackSmtpPass` /
|
|
299
|
+
`feedbackSmtpFrom`。**密码只写不读**:`/state` 删除该键、页面永远拿到空串,留空保存表示保留原值。
|
|
300
|
+
QQ 邮箱请填 `smtp.qq.com` / 465 / `secure: true` / 你的 QQ 邮箱 / **授权码**
|
|
301
|
+
(不是 QQ 密码,要在 QQ 邮箱后台「设置 → 账户 → POP3/SMTP 服务」里生成)。
|
|
302
|
+
2. **环境变量**(推荐,**优先级高于用户层配置**),有两种写法:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
# 写法一:一个 URL(smtps = 隐式 TLS,端口默认 465;smtp = STARTTLS,端口默认 587)
|
|
306
|
+
DSH_SIDECARD_SMTP_URL="smtps://user%40example.com:app-password@smtp.example.com"
|
|
307
|
+
# 也可以直接复用 dsh-conversation-recycle-bin 的那一个:
|
|
308
|
+
DSH_CRB_SMTP_URL="smtps://user%40example.com:app-password@smtp.example.com"
|
|
309
|
+
|
|
310
|
+
# 写法二:分开写(密码里带 @ 或 : 时不用手动转义)
|
|
311
|
+
DSH_SIDECARD_SMTP_HOST="smtp.example.com"
|
|
312
|
+
DSH_SIDECARD_SMTP_PORT="465"
|
|
313
|
+
DSH_SIDECARD_SMTP_SECURE="1" # 1/true/yes = 隐式 TLS
|
|
314
|
+
DSH_SIDECARD_SMTP_USER="user@example.com"
|
|
315
|
+
DSH_SIDECARD_SMTP_PASS="app-password"
|
|
316
|
+
DSH_SIDECARD_SMTP_FROM="user@example.com" # 可选,默认等于 USER
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
补充约定(都会体现在 `/state` 的 `capabilities.feedback` 里,反馈弹窗据此显示「已就绪 / 未配置」):
|
|
320
|
+
|
|
321
|
+
- **环境变量优先于用户层配置**,而且只认「完整的那一套」:环境里能凑出主机+账号+密码就用环境(`smtp.source = 'env'`),
|
|
322
|
+
否则才看用户层 `config.json` 填的;两边都不完整就报「还没填全」。这条顺序是有原因的——本机曾出现「环境变量是好的、
|
|
323
|
+
配置里留着半套(填了主机没填密码)」,旧实现先看配置,于是一套能用的凭证被半套配置挡住,
|
|
324
|
+
反馈永远发不出去。1.9.1 起设置页不自带 SMTP 表单,这条优先级只作用于「环境变量 vs 用户层配置」。
|
|
325
|
+
|
|
326
|
+
- **发件人必须像邮箱**:`?from=` / `DSH_SIDECARD_SMTP_FROM` / `USER` 三者取到的地址必须通过邮箱格式检查,
|
|
327
|
+
否则整条 SMTP 路径判为未配置——`MAIL FROM` 里塞不进一个合法地址就该早点说清楚。
|
|
328
|
+
- **STARTTLS 只在服务器声明时才升级**:先看 `EHLO` 的多行回复里有没有 `STARTTLS`,没有就直接明文对话;
|
|
329
|
+
否则一个没开 STARTTLS 的内网中继会被自己的升级请求判成「发信失败」。
|
|
330
|
+
- 本地/内网中继可以用 `smtp://user:pass@host:25?starttls=0` 关掉升级;**不要**对公网服务器这么写。
|
|
331
|
+
- 邮件正文是 `text/plain; charset=UTF-8` + base64,主题用 RFC 2047 编码(中文标题不会变乱码);
|
|
332
|
+
正文里附插件版本、DSH 版本、Node 版本、平台与时间,**不含主机名、用户名或任何路径**。
|
|
333
|
+
- 限速在 Host 侧:30 秒内最多一封、1 小时内最多 5 封;失败返回 `feedback-too-fast` /
|
|
334
|
+
`feedback-too-many` 并带 `retry-after` 头。只有**真正投递成功**才计入配额,中继故障不烧配额。
|
|
335
|
+
- 日志只记 kind 与**打码后的**收件人(`o***@example.com`),**从不记录正文**。
|
|
336
|
+
- **收件人从页面里彻底消失**(两轮返工的终点:先删掉「显示收件人」开关,再按用户要求删掉掩码行与
|
|
337
|
+
「更换收件人」按钮):
|
|
338
|
+
- `/state` 的 `capabilities.feedback` 只给 `hasRecipient` / `toMasked` 这类摘要,**没有** `to`;
|
|
339
|
+
- `/state` 的 `config` 里也**删掉了** `feedbackAddress`,页面拿不到它;
|
|
340
|
+
- 设置页不显示收件人的任何形式——没有掩码行、没有更换入口、没有地址输入框。收件人只由用户层配置
|
|
341
|
+
`feedbackAddress` 或环境变量 `DSH_SIDECARD_FEEDBACK_TO` 提供,那是部署方/维护者的事,填写者不用管;
|
|
342
|
+
- 页面也**不会**拿到 `mailto:` URL:2.0.0 删掉了 `/feedback-mailto` 路由与其 URL 构造器,
|
|
343
|
+
未命中的方法一律得到 `404 not-found`。
|
|
344
|
+
- **`From:` 仍是维护者自己的 SMTP 账号,但填报人的邮箱会写进 `Reply-To:`**:QQ/163 会拒收不属于本账号的
|
|
345
|
+
`From:`(553),所以发件身份不变;填报人在「你的邮箱」里填了地址,Host 就把它作为 `Reply-To:` 写进邮件头,
|
|
346
|
+
维护者按「回复」直接回到填报人。该值在 Host 侧先过邮箱格式校验且不含 CR/LF,因此无法注入头字段;
|
|
347
|
+
留空则整行不写(匿名反馈)。
|
|
348
|
+
|
|
349
|
+
- 两种就绪状态在界面上分开说,不混成一个「未配置」:没有收件人 → 说没有可用发信服务;有收件人但没有 SMTP
|
|
350
|
+
凭证 → 说「只缺发信凭据」;齐全 → 什么都不提示。两种情况都指向「复制内容」,不再留一条邮件应用捷径。
|
|
351
|
+
- **关于「加密」的实话**:这里的邮箱只是一次 SMTP 的收件地址,插件必须能解出明文才能发信,任何随插件
|
|
352
|
+
分发的「加密」都等于把钥匙一起发出去,只能算混淆——打包默认值用的 `b64:` 就是这种混淆,只挡正则抓取。
|
|
353
|
+
真正有效的保护是**只存本机配置 + 明文不进浏览器 + 日志打码 + 可被环境变量整体接管**——这四条都已实现。
|
|
354
|
+
仍然避免不了的一处是**已经提交进 git 历史的作者邮箱**(见下文)。
|
|
355
|
+
|
|
356
|
+
失败排查顺序:`/state` 的 `capabilities.feedback.reason`(它会直接说「账号或密码还没填全」这类话)→
|
|
357
|
+
用户层配置是否整行都在(服务器/端口/账号/密码;**端口必须是数字**,字符串会被判非法)→ 环境变量是否
|
|
358
|
+
被 DSH 进程继承(改完要重启 DSH)→ SMTP 服务商是否要求**应用专用密码**(Gmail / QQ 邮箱 / 163 都要求,
|
|
359
|
+
登录密码会被拒)→ 端口与 TLS 写法是否匹配(465 用隐式 TLS;587 让服务器自己声明 STARTTLS)→
|
|
360
|
+
中继是否在白名单里要求 `FROM` 与 `USER` 同域。
|
|
361
|
+
`feedback-send-failed` 的 message 是服务器原文,直接读它比猜快。
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## 五、侧边承载面与"侧边卡片插件"适配
|
|
366
|
+
|
|
367
|
+
侧边卡片的渲染面按**能力探测**依次挑选,任何一层不可用都不影响整体可用:
|
|
368
|
+
|
|
369
|
+
| 顺序 | 承载面 | 依赖 | 失败时 |
|
|
370
|
+
|---|---|---|---|
|
|
371
|
+
| 1 | **dsh-better-sidebar**(侧边卡片插件) | 客户端服务 `ctx.betterSidebar`:`registerTab` + `openTab(seed, scope)`;卡片 id 走 `tab.meta.cardId`,因此**要求其 `features` 含 `tabMeta`** | 记 `surfaces.better.error`(缺 `tabMeta` 时记 `no-tab-meta` 并跳过,而不是开一个空 tab),落到下一层 |
|
|
372
|
+
| 2 | **内置浮层卡片**(兜底) | 只需要 `shell.overlay` 槽位 | ——(这是保底面,永远可用) |
|
|
373
|
+
| — | DSH 原生右侧栏 | `ctx.sidebarRightTabs`(注册 tab 类型)+ `ctx.sidebarRight`(`openTab`),并占用槽位 `sidebar.right.pane.tab` / `…tab.title` | **不在 `auto` 链里**:只有显式 `sideSurface: 'native-rightbar'` 才用它;注册失败会回滚(见下) |
|
|
374
|
+
|
|
375
|
+
- 装了 `dsh-better-sidebar`:卡片以它的 tab 形式出现在它的面板里,关闭卡片会同时 `closeTab`,不残留空 tab。
|
|
376
|
+
这是 `auto` 的**第一选择**。
|
|
377
|
+
- 没装:自动使用内置浮层卡片,功能完全一致——**这条路径是第二选择,也是保底路径**。
|
|
378
|
+
浮层默认是**贴帧右边缘的胶囊**(一行高,只有状态与问题首句),点击才展开成完整卡片,
|
|
379
|
+
标题栏右侧的 ⇥ 可随时收起为胶囊、× 则删除这条追问对话(连同追问历史,不可撤销);它的宽度由 `floatPlacement` 计算,
|
|
380
|
+
只会占用**对话区右侧那条预留带**,不会压住正文(细节见 §4.1)。
|
|
381
|
+
- **原生右侧栏为什么退出 `auto`**:卡片被塞进右栏只是一个 tab,而这个 tab 的生命周期不归插件管——
|
|
382
|
+
卡片关闭后右栏仍留着一条读作「该卡片已关闭」的空标签,和你自己的右栏标签抢位置,
|
|
383
|
+
落点还取决于我们并不控制的宿主实现细节。它会保留成一个**强制选项**(下拉里标注「强制,不参与自动」)。
|
|
384
|
+
- 强制指定了一个不可用的承载面:回退到内置浮层,并在卡片上标注「已回退」。
|
|
385
|
+
|
|
386
|
+
### 5.1 与 dsh-better-sidebar 0.22.1 的适配核对(2026-09-28)
|
|
387
|
+
|
|
388
|
+
做法:把 0.22.0 与 0.22.1 的发布产物都拉下来、解包、逐文件比对(`npm pack` + SHA256)。
|
|
389
|
+
|
|
390
|
+
| 检查项 | 结果 |
|
|
391
|
+
|---|---|
|
|
392
|
+
| 消费端契约 `lib/types/client/service.d.ts` | **除 `SIDEBAR_SERVICE_VERSION` 常量外逐字节相同** → `BetterSidebarService` 的方法/参数/返回类型没变 |
|
|
393
|
+
| `SIDEBAR_FEATURES`(能力清单) | **未变**(`tabMeta` 等仍在;契约承诺"Features are never removed") |
|
|
394
|
+
| `dsh.client.inject` / peer 依赖 | 未变(仍是 locale / ui-slots / ui-conversation / ui-sidebar-right / client-modules) |
|
|
395
|
+
| `src/client/native/index.ts`(0.22.1 唯一大体量改动) | 是它**自己原生胶水的健壮性修复**:`disposeSafely` + 槽位注册失败时回滚已注册的 tab 类型;不涉及我们调用的任何字段 |
|
|
396
|
+
| 本插件实际用到的面 | `registerTab({id,title,description,order,dedupeKey,component})`、`openTab({type,id,title,meta},{sessionId})`、`closeTab(id)`、`features`、`version` —— 全部仍在 |
|
|
397
|
+
|
|
398
|
+
**因此 0.22.1 下适配为「无需改动即兼容」**;本轮据此做了三处加固:
|
|
399
|
+
|
|
400
|
+
1. **能力门控而非版本判断**:适配器只在 `features` 含 `tabMeta` 时可用(0.12 起才有,旧版会开出空 tab);
|
|
401
|
+
不满足时 `auto` 直接跳到原生右侧栏/内置浮层,设置页自检里写明原因(`no-tab-meta`)。
|
|
402
|
+
2. **原生承载面类型注册回滚**:先注册 tab 类型、再注册槽位;槽位注册抛错(reload/teardown 期的失活 context)时
|
|
403
|
+
**释放已占用的类型 id**——这正是 0.22.1 在自己原生胶水里修的同一类问题(否则该 kind 会永久占用并显示宿主"没有实现"的空面)。
|
|
404
|
+
3. **运行时可见**:设置页「运行自检」新增一行,直接显示**侧边卡片插件自己的 `version` 与能力项数**,
|
|
405
|
+
以及本插件对它的判定(可用 / 版本过旧 / 未检测到),不再依赖 package.json 的声明。
|
|
406
|
+
|
|
407
|
+
> 注意:`version` 是加载到浏览器里的那份模块报告的版本。页面刷新后即可用它确认"跑的是不是 0.22.1"。
|
|
408
|
+
|
|
409
|
+
### 5.2 与 DSH 0.2.0-rc.1 的适配核对(2026-09-29)
|
|
410
|
+
|
|
411
|
+
本机实际运行的就是 **0.2.0-rc.1**(`@deepseek-ai/dsh` 的 `next` 通道;`latest` 仍是 0.1.7-rc.2)。核对分两层:
|
|
412
|
+
|
|
413
|
+
**① 包内容层面**(`node tools/compat-probe.mjs 0.1.7-rc.2 0.2.0-rc.1`):
|
|
414
|
+
16 项能力**逐项与 0.1.7-rc.2 完全一致**——`shell.overlay`、`conversation.input.right`、`settings.section`、
|
|
415
|
+
`data-slot` 锚点、原生右侧栏、`sessions.using/retain`、归档门、`agent/assistant-stream`、子代理、`tools.schemas`、
|
|
416
|
+
`webServer` 路由……全部 ✅。
|
|
417
|
+
|
|
418
|
+
**② 在运行中的实例上实测**:
|
|
419
|
+
|
|
420
|
+
| 检查 | 结果 |
|
|
421
|
+
|---|---|
|
|
422
|
+
| 宿主半是否加载 | ✅ `/sidecard-ask/api/state` 返回 `dsh-sidecard-ask v1.2.0`,`sideEngine=true`(provider `spawn,fork`)、`capabilities.parent` 正常 |
|
|
423
|
+
| 客户端半是否注册 | ✅ `shell.overlay` 有 `sidecard-ask`(order 45、active)、`conversation.input.right` 有 `sidecard-ask-session-probe`、`settings.section` 有设置页 |
|
|
424
|
+
| 依赖的槽位与标准 prop | ✅ `conversation.input.right` 仍是 list/session,`standardProps` 仍含 **`sessionId`** 与 **`inputActions`** |
|
|
425
|
+
| 依赖的客户端服务 | ✅ `sessions`(`retain`/`using`/`scope`)、`slots`、`locale`、`layout`、`theme` 全在;`sessions.fork`/`uiWorkspace.forkSession` 只是**新增**了可选 `onCreated`,不影响既有调用 |
|
|
426
|
+
| 端到端作答 | ✅ 真机 `start→delta×31→status→done`(1.36 s,`streamSource=frames`),只读工具白名单照常生效 |
|
|
427
|
+
|
|
428
|
+
**③ 由此发现并修掉的真实缺陷(1.2.1)**:运行中的 `sidebar.right.pane.tab` 清单里只有
|
|
429
|
+
`dsh-better-sidebar:sidecard-ask:card`,**没有我的原生 `sidecard-ask:card`**。原因是两个适配器用了**同一个 tab kind**:
|
|
430
|
+
better-sidebar 会把收到的每个 tab 描述符**镜像注册进同一个原生注册表**(`sidebarRightTabs.register`,band `extension`),
|
|
431
|
+
而该注册表对*同 band 同 kind 的二次注册直接抛错*(源码注释:*"Everything else colliding on a kind throws"*)——
|
|
432
|
+
后注册的那一方就此失败,而且**在界面上不可见**。修法:
|
|
433
|
+
|
|
434
|
+
1. better-sidebar 承载面改用独立类型 **`sidecard-ask:card:workbench`**,两端 kind 不再重叠;
|
|
435
|
+
2. `contract-test` **模拟该注册表的"重复 kind 抛错"规则**,此类冲突今后会直接让测试失败;
|
|
436
|
+
3. 设置页自检行改为**同时报告两个适配器**的状态(此前原生适配器的错误压根看不到)。
|
|
437
|
+
|
|
438
|
+
**④ 修复后的运行实例复核(2026-09-29,1.3.0)**:`sidebar.right.pane.tab` 的占用者现在**同时**包含
|
|
439
|
+
|
|
440
|
+
| key | 来源 |
|
|
441
|
+
|---|---|
|
|
442
|
+
| `sidecard-ask:card` | 本插件的**原生右侧栏**承载面(1.2.1 之前完全缺失) |
|
|
443
|
+
| `dsh-better-sidebar:sidecard-ask:card:workbench` | 经 dsh-better-sidebar 镜像注册的承载面(独立 kind) |
|
|
444
|
+
|
|
445
|
+
两者共存、均 `active: true` —— 即"同 kind 冲突"已被消除。`auto` 走的是 **dsh-better-sidebar → 内置浮层卡片**这条链(1.8.3 起原生右侧栏只在显式 `sideSurface: native-rightbar` 时使用),两个强制选项都仍可用。
|
|
446
|
+
|
|
447
|
+
### 5.3 与 DSH 桌面版(Electron)的适配核对(2026-09-30)
|
|
448
|
+
|
|
449
|
+
桌面版与命令行/web 版**不是同一套运行形态**,核对结论如下。
|
|
450
|
+
|
|
451
|
+
| 维度 | 事实(实测) |
|
|
452
|
+
|---|---|
|
|
453
|
+
| 桌面 App | `DeepSeek Harness.exe` **0.2.0-rc.2**(`resources\app-update.yml` 更新通道 `nightly`,源 `download.deepseek.com/dsh-desk/feeds/win-x64/`) |
|
|
454
|
+
| 运行形态 | Electron-as-Node 跑 `app.asar\dsh\node_modules\@deepseek-ai\dsh-desktop-host\lib\index.js`;**框架包全部打包在 `app.asar` 内**(121 MB 归档,普通 shell/glob/grep 读不到,只能靠运行实例的 inspection 查) |
|
|
455
|
+
| 自带运行时 | `resources\runtime\versions.json`:node **24.18.1** + pnpm **11.7.0**(装插件用的是它自带的 pnpm) |
|
|
456
|
+
| profile | **`DSH_PROFILE=desktop` → `C:\Users\34332\.dsh\profiles\desktop`**,与 web 版的 `profiles\web` **互相独立**:两者各有自己的 `package.json`/`node_modules`/`dsh.profile.bundles`,插件必须分别安装 |
|
|
457
|
+
| 本插件安装状态 | 桌面 profile 的 `package.json` 中为 `"dsh-sidecard-ask": "link:D:/Users/34332/AI/dsh-sidecard-ask"`,且已在 `dsh.profile.bundles` 内 → **无需改动即加载** |
|
|
458
|
+
| 宿主半 | `GET http://127.0.0.1:19387/sidecard-ask/api/state` → `dsh-sidecard-ask v1.3.1`、`sideEngine=true`(provider `spawn,fork`)、`capabilities.parent` 正常、配置落点 `~/.dsh/sidecard-ask/config.json` |
|
|
459
|
+
| 客户端半 | 由本插件自己的自检上报读出:`version=1.3.1`、`slots={overlay: shell.overlay, settings: settings.section, sessionProbe: conversation.input.right}`、`native.available=true`、`better.available=true` 且 `better.version=0.24.1` |
|
|
460
|
+
| 端到端 | 真机 `start→reasoning×106→delta×46→status→done`,1.89 s,`done.text` 是真实答案(子代理 + 流式帧 + 只读工具白名单全部正常) |
|
|
461
|
+
| 客户端服务目录 | 与 web 版**完全一致**(layout / locale / sessions / slots / theme / timer / uiWorkspace / workspaces),无桌面专属差异 |
|
|
462
|
+
|
|
463
|
+
**桌面版用到的 dsh-better-sidebar 是 0.24.1**(比上轮核对的 0.22.1 新):0.22.1 → 0.24.1 的
|
|
464
|
+
`lib/types/client/service.d.ts` 差异是**纯增量**——`target?: 'right' | 'bottom'` 增加 `'side'`(右侧栏第二窗格),
|
|
465
|
+
并新增可选 `preferNewPane?: boolean`;`SIDEBAR_FEATURES` 仍含 `tabMeta`,`dsh.client.inject` 未变。
|
|
466
|
+
本插件不传这两个字段 → **不受影响**(默认落点行为未变)。
|
|
467
|
+
|
|
468
|
+
**桌面版特有的适配修改(1.3.2):窗口拖拽区。** 桌面外壳会把顶层元素标记为窗口拖拽区
|
|
469
|
+
(`-webkit-app-region: drag`),落在拖拽区内的点击会**变成拖动窗口**而不是点中 UI。同生态的
|
|
470
|
+
dsh-better-sidebar 为此专门带了一条防御(`html[data-platform] body>[data-dsh-better-sidebar]{-webkit-app-region:initial}`
|
|
471
|
+
+ `[data-dsh-panel-host]>*{-webkit-app-region:no-drag}`)。本插件的浮层此前**没有这条防御**,现已在样式表里为
|
|
472
|
+
每个自有根节点声明 `-webkit-app-region:no-drag`
|
|
473
|
+
(`.dsa-layer/.dsa-trigger/.dsa-pop/.dsa-stack/.dsa-card/.dsa-toast/.dsa-settings`),并加回归测试锁定该规则。
|
|
474
|
+
该属性在浏览器里无效,不影响 web 版。
|
|
475
|
+
|
|
476
|
+
**桌面形态下确认「不需要改」的两点**:
|
|
477
|
+
|
|
478
|
+
- `styles.insert` / `host.call` 属于**动态运行时**的插件能力(inspection 的 `Builtin` 定义就是
|
|
479
|
+
"Plain-JavaScript symbols available to a **dynamic** Client half")。本插件是**静态**客户端半
|
|
480
|
+
(`__ModuleLoader__.load` + `require('react')`),官方形态就是手工插入 `<style>`——同为静态形态的
|
|
481
|
+
`uiskin-theme` 在源码里注明 "the static twin of the dynamic runner's `styles.insert`";宿主通信走自有的
|
|
482
|
+
`/sidecard-ask/api` 路由;
|
|
483
|
+
- 名为 `shortcuts` 与 `conversation` 的客户端服务**都不在运行实例的服务目录里**(inspection 明确报
|
|
484
|
+
`no catalogued Service named ...`)。所以"并入宿主快捷键注册表"与"使用 composer 草稿服务"两条路都不存在:
|
|
485
|
+
本插件继续自带 keydown 监听(快捷键可配置)与 `PLACEHOLDER: composer-draft` 降级路径。
|
|
486
|
+
|
|
487
|
+
**仍未验证的部分(如实记录)**:桌面窗口内的**视觉与点击行为**(本工作区无 GUI 自动化,无法点按验证);
|
|
488
|
+
`Alt+Q` 是否会撞上桌面 App 的菜单加速键(Electron 的菜单加速键由菜单先处理,页面的 keydown 可能收不到;
|
|
489
|
+
若冲突,在设置页改成 `Ctrl+Alt+Q` 之类即可);Electron 下 `navigator.clipboard` 的写入权限(已有"选中文本"降级兜底)。
|
|
490
|
+
|
|
491
|
+
### 5.5 2026-10-24 复核(v2.1.2 / 桌面 0.2.1-rc.3 × 网页 0.2.1-rc.3)
|
|
492
|
+
|
|
493
|
+
本轮把双端都升到 **0.2.1-rc.3** 后重新取证,**完整报告见 [`CROSS-END-AUDIT-2.1.1.md`](CROSS-END-AUDIT-2.1.1.md)**。
|
|
494
|
+
结论先行:**插件可加载、可用**(宿主半路由注册成功、客户端半被 `ClientModuleRegistry` 组装并由 `/plugins` 路由取到,
|
|
495
|
+
`GET /sidecard-ask/api/ping` 实机 200),默认承载路径(侧边卡片插件 / 浮层 / 输入区)全部正常;
|
|
496
|
+
原生右侧栏这条**可选**路径此前在 rc.3 上是坏的(D0/D2),2.1.2 已修。
|
|
497
|
+
|
|
498
|
+
| 编号 | rc.3 事实 | 影响 | 2.1.2 处置 |
|
|
499
|
+
|---|---|---|---|
|
|
500
|
+
| D0 | tab 记录只有 `{id,kind,contentId,title}`,body 无 `tab` 属性、无 `tab.meta` | 原生栏卡片恒走「已关闭」兜底 + 600 ms 后被挪去浮层 | `hostCardIdOf()` 三级回退,含适配器记录的 cardId |
|
|
501
|
+
| D1 | `SubagentStartRequest`/`Capabilities` 无 `toolFilter`、无 `persona` | `sideTools: readonly` 只靠探针拒写 | 新增 `sideAgentPreset`(`agentPreset` 是唯一真实收窄通道) |
|
|
502
|
+
| D2 | 控制器无 `closeTab(kind, options)` | 关闭卡片后 tab 残留 | 改用 `openTabs.getSnapshot()` + `closeIn(sessionId, tabId)` |
|
|
503
|
+
| D3 | locale 快照是 `{active, locales, revision}` | 切换语言不跟随 | 先读 `active` |
|
|
504
|
+
| D4 | `persona` 能力位不存在 | persona 拼进 prompt(无损失) | 保持,文档标注 |
|
|
505
|
+
| P1 | end 帧只有 `outcome`,无 `stopReason` | SSE 该字段恒空 | 前端取 `result.stopReason` 兜底 |
|
|
506
|
+
| P2 | `webRuntime` 服务不存在 | `trustedHosts` 恒空 ⇒ 非 loopback 403 | 改读 `connection.trustedHosts` |
|
|
507
|
+
| P3 | 无 `engines.dsh` | 安装期无版本门禁 | 声明 `>=0.1.2-rc.1 <0.3.0` |
|
|
508
|
+
| P6 | `.rightbar` / `[data-dsh-panel-host]` 全树 0 命中 | 后备选择器白跑 | 已从锚点阶梯与观察器移除 |
|
|
509
|
+
| P7 | `conversation.input.for()` 不在服务目录 | 草稿降级为第二顺位 | 保持防御式最后一档 |
|
|
510
|
+
|
|
511
|
+
**仍然存在的结构限制(非缺陷)**:内置右栏的 tab body 拿不到 `tab`,`useTabInfo()` 是它给自家 body 的通道,
|
|
512
|
+
插件 bundle 无法 import ⇒ 原生承载的"一张卡一个 tab"做不到,只能是**按 kind 单例**(同时一张卡)。
|
|
513
|
+
`auto` 本就不选原生栏,这条限制只影响显式开 `native-rightbar` 的人。
|
|
514
|
+
|
|
515
|
+
### 5.4 2026-10-22 复核(v2.1.0 / 桌面 0.2.0-rc.2)
|
|
516
|
+
|
|
517
|
+
上一轮之后桌面端与网页端的形态都变过,本轮把能静态取证的部分重新核了一遍,**完整报告见
|
|
518
|
+
[`CROSS-END-AUDIT-2.0.2.md`](CROSS-END-AUDIT-2.0.2.md)**(含实测数字与未验证清单)。要点:
|
|
519
|
+
|
|
520
|
+
| 维度 | 事实 | 对插件意味着什么 |
|
|
521
|
+
|---|---|---|
|
|
522
|
+
| 宿主版本落差 | 桌面 `dsh-desktop 0.2.0-rc.2`(包内 `dsh` 同版本);网页 **`0.2.1-rc.2`** | 适配矩阵要按两端分别列,不能写成"DSH 0.2.x" |
|
|
523
|
+
| 渲染内核 | Electron **43.3.0 = Chromium 150.0.7871.212**(`~/.dsh/electron/version` 亦为 43.3.0);网页端取决于访问者浏览器(本机对照 Edge/Chromium 155) | `renderRichText` 在 150 与 155 上实测同量级(8 k 0.2–0.3 ms,64 k 1.2–2.0 ms) |
|
|
524
|
+
| 自定义 scheme | `dsh-app://app/` 特权:`standard/secure/supportFetchAPI/corsEnabled/stream/codeCache` | `fetch` + 流式读取可用,不需要 EventSource |
|
|
525
|
+
| 请求转发 | `forwardWebRequest`:只放行 `origin=dsh-app://app`,删 `host/origin/cookie/sec-fetch-site` 后注入宿主 cookie,`duplex:"half"` | 2.0.2 起的会话门在桌面端靠这条链路通过;插件侧 `origin` 缺失时被判为可信(`isTrustedRequest`) |
|
|
526
|
+
| 权限 | 主窗口 `setPermissionRequestHandler`:非 `media` 一律放行;登录窗/侧栏 webview 一律拒绝并拦下载 | 剪贴板与通知**有**权限;导出下载走主窗口,不受拦截 |
|
|
527
|
+
| 通知 | 全包 **无 `setAppUserModelId`** | Windows toast 可能不弹;`notifyOnDone` 默认关,打开后仍以卡片状态为兜底 |
|
|
528
|
+
| 菜单加速键 | 全包 `Alt+Q` **0 命中** | 外壳不抢默认快捷键(输入法/其它程序仍可能抢) |
|
|
529
|
+
| 插件产物缓存 | `/plugins/*` 被壳改写为 `cache-control: no-store` | 每次加载重新拉 349 KB(localhost,传输≈0,代价在解析) |
|
|
530
|
+
|
|
531
|
+
> 另:2.0.2 CHANGELOG 里"桌面端在本机冷启动即崩"的结论,2026-10-22 **未能复现**——本次冷启动成功、
|
|
532
|
+
> 宿主在 19387 正常服务。该条已按"与安装包/启动方式相关,需按版本复验"处理,不再当作既定事实。
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## 六、未知 DSH API:占位接口与替换方式
|
|
537
|
+
|
|
538
|
+
开发时以**运行时能力探测**为主,任何"本机没验证到"的接口都在代码里显式留了占位与替换点。它们集中在三处:
|
|
539
|
+
|
|
540
|
+
### 1. `client.js` §7 —— `surfaces.native`(原生右侧栏适配器)
|
|
541
|
+
|
|
542
|
+
```js
|
|
543
|
+
// 现状:结构性子集 + try/catch,任何一步不成立就置 error 并降级
|
|
544
|
+
ctx.inject(['sidebarRightTabs', 'sidebarRight'], (injected) => { ... })
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
- **已实测的调用形态**(0.2.1-rc.3):`registry.register({ id, kind, title, guide: [{ id, order, title, description }] })`、
|
|
548
|
+
`controller.openTab(kind, { params, revealIfOpened })`、槽位 `sidebar.right.pane.tab`(**keyed,注册项只有 `key`**,
|
|
549
|
+
没有 `inject`)、关闭走 `closeIn(sessionId, tabId)`(tab 行来自 `controller.openTabs.getSnapshot()`)。
|
|
550
|
+
- **rc.3 的硬限制**:该槽位的 body 拿不到 `tab`、也拿不到 `navigation.params`(内置 body 靠 `useTabInfo()`,
|
|
551
|
+
插件无法 import)⇒ 卡片 id 只能由适配器在 `open()` 时记下(`surfaces.native.cardId`)。这也是原生承载
|
|
552
|
+
**按 kind 单例**的原因。
|
|
553
|
+
- **未逐版本实测的部分**(占位):`guide[].icon`、`canOpen/patterns`、`closeTarget/activateIn`。
|
|
554
|
+
替换方式:在 `client.js` 搜索 `PLACEHOLDER: native-rightbar`,按目标 DSH 版本的真实签名补齐;
|
|
555
|
+
补齐前该分支只会记录错误并降级,不会破坏插件。
|
|
556
|
+
|
|
557
|
+
### 2. `client.js` §6/§3 —— 主对话发送与线协议
|
|
558
|
+
|
|
559
|
+
- **已实测**:客户端服务 `ctx.sessions` 的 `using(id, { source:'gateway' }, ref => ref.binding.session.prompt(parts, 'queue'))`。
|
|
560
|
+
- **占位 1(草稿降级)**:`ctx.get('conversation').input.for(scope)` → `{ state.getSnapshot().draft, setDraft(text) }`。
|
|
561
|
+
这是**未出现在服务目录里**的接口(属 harness 内部形态),所以只作为第二顺位降级;搜索 `PLACEHOLDER: composer-draft`。
|
|
562
|
+
- **占位 2(最后兜底)**:前两者都不可用时,插件抛出可读错误并提示用户复制文本,搜索 `PLACEHOLDER: clipboard-fallback`。
|
|
563
|
+
- **线协议**:客户端与宿主半之间是插件**自有的** `/sidecard-ask/api`(`GET /state`、`POST /ask|cancel|config|reset|diagnose`),
|
|
564
|
+
不依赖任何 harness 内部 RPC;若未来 harness 提供正式的同进程 RPC,替换点就是 `client.js` §3 的 `postJson/streamAsk`。
|
|
565
|
+
- **`POST /diagnose`(客户端自检上报)**:宿主**看不到浏览器里的客户端半**,所以客户端在启动、槽位落点变化、
|
|
566
|
+
承载面回退时把自己的一份受限摘要(版本、承载面、区域锚点、槽位落点、两个适配器的可用性与原因、侧边卡片插件版本与能力)
|
|
567
|
+
POST 给宿主;随后 `GET /state` 的 `value.client` 就能读到它。宿主侧对上报做**白名单 + 长度截断**(未知键丢弃、
|
|
568
|
+
字符串截断到 200 字符、能力列表最多 40 项),所以页面即使被注入垃圾也不会污染状态面。
|
|
569
|
+
这条通道是在两次"客户端静默失效只能靠人工翻槽位清单才发现"之后补上的。
|
|
570
|
+
|
|
571
|
+
> 约定:所有占位点都写成 `PLACEHOLDER: <名字>` 注释 + 可运行的降级路径,替换时只需改该函数的实现,调用方(卡片、设置页)无需改动。
|
|
572
|
+
|
|
573
|
+
### 3. `client.js` §8c —— 答案写回输入框(1.8.0,已做真机探针)
|
|
574
|
+
|
|
575
|
+
2026-10-17 用**隔离 `DSH_HOME` + 无头 Edge**(插件不参与)对真实 DSH 页面做了探针,
|
|
576
|
+
脚本见 `.compat-report/scripts/composer-probe.mjs`,证据见
|
|
577
|
+
`.compat-report/evidence/p2-probe-2026-10-17.json`,三条硬结论:
|
|
578
|
+
|
|
579
|
+
| 问题 | 结论 | 证据来源 |
|
|
580
|
+
|---|---|---|
|
|
581
|
+
| composer 的真实 DOM | **Lexical contenteditable**:`<div contenteditable="true" role="textbox" data-composer-input="true" data-lexical-editor="true">`,槽位 `conversation.composer` | 页面探针 |
|
|
582
|
+
| native setter + input 事件能否同步 | **N/A 且无效**:不是 textarea,没有 value setter;`document.execCommand('insertText')` 返回 `false`、文本不变。**有效的是**合成 `beforeinput`(`inputType:'insertText'`):文本落地、发送按钮由禁用转可用 | 页面探针 |
|
|
583
|
+
| 有没有官方写入面 | **有**。槽位标准 prop `inputActions` 暴露 `captureInsertion()` / `insertText(text, span)` / `setDraft(text)` / `submit()`。`sessions.using(...).binding.session.prompt` 只负责**提交**,不能写草稿 | **产物层核验**(见下),**不是**页面探针 |
|
|
584
|
+
|
|
585
|
+
> **1.8.1 勘误**:上面第三行原先被写成"探针的第④条结论",但 `composer-probe.mjs` 的正文
|
|
586
|
+
> 从未探测 `inputActions`(只有脚本头的注释承诺过)——这条声明当时**没有可核验的证据**。
|
|
587
|
+
> 经复核,声明本身**成立**,现改由产物层证据背书:`npm pack
|
|
588
|
+
> @deepseek-ai/dsh-client-ui-conversation@0.2.0-rc.2` 与 `@0.2.1-rc.2` 解包后,
|
|
589
|
+
> `lib/types/client/contract/input.d.ts` 含 `captureInsertion(): TokenSpan` 与
|
|
590
|
+
> `insertText(text: string, span: TokenSpan): boolean`(且文档明确 *"returns false when the
|
|
591
|
+
> draft changed"*,与实现"拒绝先于写入、故可安全回落 DOM 路径"的假设一致),
|
|
592
|
+
> `lib/client.js` 含 `var SessionInputShell = class`。逐条命中与行号见
|
|
593
|
+
> `.compat-report/evidence/p2-inputactions-contract-2026-10-17.json`。
|
|
594
|
+
|
|
595
|
+
因此实现是三级阶梯:`inputActions.insertText`(重新 `captureInsertion` 后可重试一次)→
|
|
596
|
+
DOM `beforeinput`(追加到末尾)→ 失败时 toast。**脚本设定的 Range 不被 Lexical 采纳**,
|
|
597
|
+
所以 DOM 路径只能追加、不能定位光标;"替换原文"仅在捕获到**文本框内**选区时提供。
|
|
598
|
+
替换点:`client.js` 搜 `P2-2`,函数 `insertIntoComposer` / `composerWriteSupport`。
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
## 七、DSH 版本适配(近 10 余个版本,**证据来自已发布产物**)
|
|
603
|
+
|
|
604
|
+
不是靠字段笔记,而是把每个版本要用的包从 npm 拉下来、解包、按标记字符串判定:
|
|
605
|
+
|
|
606
|
+
```powershell
|
|
607
|
+
node tools/compat-probe.mjs # 13 个版本 × 12 个包
|
|
608
|
+
node tools/compat-probe.mjs --json tools/.cache/matrix.json
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
脚本会 `npm pack` 相应包到 `tools/.cache/tarballs/`(已在 .gitignore),用内置 tar 读取器在内存里搜索,
|
|
612
|
+
然后打印下面的能力矩阵。**矩阵里的 ❌ 就是插件必须降级的点**,而插件对每一个 ❌ 都有对应分支。
|
|
613
|
+
|
|
614
|
+
### 7.1 能力矩阵(0.1.2-rc.1 → 0.1.7-rc.2)
|
|
615
|
+
|
|
616
|
+
| 能力(依赖的 API) | 0.1.2-rc.1 | 0.1.3-alpha.2 | 0.1.5-alpha.1 … 0.1.5-rc.3 | 0.1.6-alpha.1 | 0.1.6-alpha.2 | 0.1.7-alpha.1 … 0.1.7-rc.2 |
|
|
617
|
+
|---|---|---|---|---|---|---|
|
|
618
|
+
| 客户端产物协议 `window.__ModuleLoader__.load` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
619
|
+
| `shell.overlay`(浮层/触发按钮挂载点) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
620
|
+
| `conversation.input.right`(会话 id 采集) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
621
|
+
| `settings.section`(设置页) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
622
|
+
| `data-slot` 出口锚点(区域归类) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
623
|
+
| 原生右侧栏 `sidebarRightTabs` + `sidebar.right.pane.tab` | **❌** | **❌** | ✅ | ✅ | ✅ | ✅ |
|
|
624
|
+
| 主对话提交 `sessions.using(target, options, operation)` | **❌** | **❌** | **❌** | **❌** | ✅ | ✅ |
|
|
625
|
+
| 主对话提交 `sessions.retain(target, options)` | **❌** | **❌** | **❌** | **❌** | ✅ | ✅ |
|
|
626
|
+
| 归档会话门(拒绝归档血统的每一步) | **❌** | **❌** | **❌** | **❌** | **❌** | ✅ |
|
|
627
|
+
| 进程内流式帧 `agent/assistant-stream` | **❌** | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
628
|
+
| 持久化分块 `assistant/chunk`(会话事件) | **✅** | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
629
|
+
| 子代理 `subagents.start` + `SubagentRun.localAgent` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
630
|
+
| provider 能力面(`toolFilter` / `persona`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
631
|
+
| `tools.schemas()` + `tools.restrict()` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
632
|
+
| 宿主路由 `webServer.register({kind:'prefix'})` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
633
|
+
|
|
634
|
+
### 7.2 每个 ❌ 对应的适配(都在代码里)
|
|
635
|
+
|
|
636
|
+
| 缺口 | 适配实现 | 在真机/测试里的表现 |
|
|
637
|
+
|---|---|---|
|
|
638
|
+
| 0.1.2 / 0.1.3 没有原生右侧栏 | 承载面探测链:原生右侧栏 → `dsh-better-sidebar` → **内置浮层卡片**;不可用的一律不计入 `pickSurface` | `contract-test` 断言 `pickSurface('auto')` 在无插件组合下落到 `flow`、两端可用时选 `better-sidebar`;`float-placement` 钉住自动链里不出现原生右侧栏 |
|
|
639
|
+
| ≤0.1.6-alpha.1 没有 `using`/`retain` | 主对话提交**四级阶梯**(调用时逐级探测,不查版本表):`sessions.using` → `sessions.retain`+`release` → 槽位标准 prop `inputActions`(`setDraft` + `submit`)→ 仅写入输入框并明确提示"请按 Enter" | `contract-test` 逐级断言 `via`:`sessions.using` / `sessions.retain`(并断言 `release()` 被调用)/ `inputActions.submit` / `composer.draft` / 全无时抛 `no-main-carrier` |
|
|
640
|
+
| ≤0.1.6-alpha.2 没有归档门 | 不再因"父会话已归档"直接拒绝:仍优先挑未归档代理,但只有归档候选时**照常尝试**,并在 `start` 事件里带 `parentArchived:true`;真被门拒绝时把空 turn 的 `refusal` 翻译成 `blocked-step` 并说明原因 | `smoke-test`:全归档组合仍能拿到答案;`refusal` 用例断言 `code=blocked-step` 且文案提到归档 |
|
|
641
|
+
| 0.1.2-rc.1 没有进程内帧 | 增加**第二条流式源**:订阅 `session/event` 的持久化 `assistant/chunk`(`{turn,step,chunk}`,形状取自该版本自己的 `chunk-rows.js`),与帧源**互斥**(先说话的那个生效,绝不重复计一次文本) | `smoke-test`:`frameMode:'chunks'` 用例断言 delta 拼出完整答案、`streaming:true`、`streamSource:'chunks'`;同时断言两源并存时文本不重复 |
|
|
642
|
+
| provider 能力面差异 | 只发 provider **声明支持**的启动字段(`capabilities.persona/toolFilter`);`persona` 不支持时**内联进提示词**;`toolFilter` 不支持时在 `start` 事件里报 `toolFilter:'unsupported'`,卡片明说"本 provider 不支持工具白名单" | `smoke-test`:`capabilities:null` 与 `{toolFilter:false,persona:true}` 两种 provider 的字段断言 |
|
|
643
|
+
| 槽位键在老版本可能不同 | 每个注册走**槽位阶梯**(都是 `list` 槽,绝不碰 `single` 槽以免替换宿主 UI):浮层 `shell.overlay → conversation.input.dock → conversation.composer.dock`;设置页 `settings.section → settings.plugins.tab`;会话采集 `conversation.input.right → conversation.input.left → composer.dock → input.dock`。**先到者胜**,更好的槽位后到会顶掉兜底 | `contract-test`:`absentSlots` 模拟未声明的槽位,断言注册落到下一级且诊断里记录了落点 |
|
|
644
|
+
| 没有 `data-slot` 锚点 | 检测到选区但槽位路径为空 → 判定"区域锚点不可用",**停用区域过滤**(否则 `captureZones:chat` 会静默失效)并在设置页自检里标明 | `contract-test`:`shouldOffer(..., {anchors:false})` 断言放宽且返回 `zoneFiltering:'unavailable'` |
|
|
645
|
+
| 路由 kind 不被接受 | `prefix` 注册失败 → 退化为**逐方法精确路由**(`state/ask/cancel/config/reset`),处理器不变 | 前缀路由由 smoke 全流程覆盖;退化分支为纯 fallback(未在真机触发过) |
|
|
646
|
+
|
|
647
|
+
### 7.3 真机验证记录(1.6.0,2026-10-11;1.7.1 复核,2026-10-16)
|
|
648
|
+
|
|
649
|
+
1.6.0 把 §7.1/§7.2 的产物层证据升级为**真机证据**:最近 10 个发布版(0.1.5-rc.3 → 0.2.1-alpha.2,
|
|
650
|
+
按 npm 发布时间)全部在隔离 `DSH_HOME` 中真实安装、启动并读取运行时能力面;
|
|
651
|
+
0.2.0-rc.2(= npm `latest`)与 0.2.1-alpha.2(= npm `next`)另做无头浏览器真机验证。
|
|
652
|
+
|
|
653
|
+
- 0.1.5-rc.3 … 0.2.1-alpha.1:引擎可用,provider=spawn/fork,0 启动告警。
|
|
654
|
+
- **0.2.1-alpha.2:1.5.1 真机不可用 → 1.6.0 已修复**。该版本把子代理 API 从 `subagents.start(provider, request)`
|
|
655
|
+
重构为 `subagents.startActivation({provider, label, request, signal, delivery})`,1.5.1 的能力门只认旧方法
|
|
656
|
+
(provider 其实注册成功,自检却显示"不可用")。1.6.0 按运行时方法面自动选择两代 API,新 API 用
|
|
657
|
+
`delivery:'caller'` 保持"答案不进主对话",流式帧按子会话 id 归因。
|
|
658
|
+
- 更早的 0.1.2-rc.1 … 0.1.4 仍只有产物层证据(§7.1/§7.2 的降级分支仍保留,双 API 调度不改动旧路径)。
|
|
659
|
+
- 生态侧:25 个侧边卡片/侧边栏类插件做了产物扫描 + 4 组共载真机,localStorage 键 / 路由前缀 / tab kind /
|
|
660
|
+
patch id / 配置目录 / Alt+Q 零硬冲突;旧名包 `dsh-selection-followup@1.0.1` 共载时的双重触发由
|
|
661
|
+
1.6.0 的页面内家族单例选举解决。完整矩阵、复现脚本与截图见 `.compat-report/`。
|
|
662
|
+
|
|
663
|
+
**1.7.1 复核(2026-10-16):0.2.1-alpha.3 / 0.2.1-alpha.4——两版均无需任何代码改动**(基线 = 1.6.0 已适配的 alpha.2)。
|
|
664
|
+
|
|
665
|
+
- alpha.3(2026-10-14 发布)与 alpha.4(2026-10-15 发布,`alpha` 通道当前指向)在隔离 `DSH_HOME` 中
|
|
666
|
+
从零安装(官方 CLI 装入 `npm pack` 产物)、启动并做无头 Edge 断言:**28/28 × 两版全过**——
|
|
667
|
+
含 `subagents.startActivation` 实调出真实 childId、三个槽位落点、选区恰 1 个触发按钮、`Alt+Q` 恰 1 个弹层、
|
|
668
|
+
0 启动告警、0 未捕获页面错误。
|
|
669
|
+
- 产物层复核:16 项能力矩阵在 alpha.2/3/4 上逐项一致;13 个相关包的逐文件 SHA256 diff 证实
|
|
670
|
+
alpha.3 → alpha.4 宿主侧零变化(`dsh-subagent` 的 `lib/index.js` 逐字节一致),alpha.4 的增量全在
|
|
671
|
+
客户端侧且为纯增量;alpha.2 → alpha.3 的子代理能力模型重排(`persona`/`toolFilter` 退出协商、
|
|
672
|
+
`agentPreset` 进入)不命中插件消费面——1.6.0 的字段门控原样覆盖。
|
|
673
|
+
- 插件四套自测重跑 **1004 项全过**;完整证据(脚本 / 原始输出 / 截图,`wb-*` 前缀为本轮独立复核产物)
|
|
674
|
+
见 `.compat-report/`,过程细节(含两处探针口径修正的如实记录)见该目录 `COMPATIBILITY-REPORT-alpha3-alpha4.md` §8。
|
|
675
|
+
|
|
676
|
+
**2.1.2 复核(2026-10-24):0.2.1-rc.3(桌面端与网页端同为 rc.3)——发现 4 项契约漂移并全部修复**(详见 §5.5 与 `CROSS-END-AUDIT-2.1.1.md`)。
|
|
677
|
+
|
|
678
|
+
- 实机证据:桌面端进程(`0.2.1-rc.3`,Electron 43.3.0)在 `127.0.0.1:14582` 上
|
|
679
|
+
`GET /sidecard-ask/api/ping` 返回 200(前缀路由注册成功),`GET /plugins/??dsh-sidecard-ask/client.js&rev=…`
|
|
680
|
+
返回 369 873 字节(客户端半被 `ClientModuleRegistry` 组装并从 `/plugins` 路由可取)。
|
|
681
|
+
- 产物级证据:`@deepseek-ai` 下 307 个包全为 `0.2.1-rc.3`;`tools/compat-probe.mjs` 在 rc.2/rc.3 上矩阵完全一致。
|
|
682
|
+
- 契约漂移:D0(右栏 tab 无 `tab.meta`)、D1(无 `toolFilter`/`persona`)、D2(无 `closeTab`)、
|
|
683
|
+
D3(locale 字段名 `active`)——这三项会让**原生右侧栏**与**语言跟随**在 rc.3 上表现异常,2.1.2 已修;
|
|
684
|
+
默认承载路径(侧边卡片插件 / 浮层 / 输入区)不受影响。
|
|
685
|
+
- 四套自测重跑 **1839 项全过**(verify 57 / contract 1427 / float-placement 84 / smoke 271)。
|
|
686
|
+
|
|
687
|
+
### 7.4 更早版本仍未做真机验证的部分(如实标注)
|
|
688
|
+
|
|
689
|
+
- 上表所有 ✅/❌ 都是**包内容层面的证据**(字符串/签名存在于该版本的发布产物),不等于"插件在该版本上跑起来过"。
|
|
690
|
+
真机覆盖为 0.1.5-rc.3 → 0.2.1-alpha.4(1.6.0 与 1.7.1 两轮实测)与 0.1.7-rc.2(§10.1 的历史实测)。
|
|
691
|
+
- 在 0.1.2-rc.1 … 0.1.4 这几个版本上,只验证了"所需 API 是否存在 + 插件有为缺失准备的分支",
|
|
692
|
+
**没有**在那些版本上安装并启动过插件。
|
|
693
|
+
- `inputActions` 作为会话槽位标准 prop:13 个版本的 `dsh-client-ui-conversation` 产物里都有该名字,
|
|
694
|
+
0.1.7-rc.2 的槽位目录也把它列为 `conversation.input.right` 的 standardProps;**更早版本是否真的把它下发给该槽位条目未验证**,
|
|
695
|
+
插件对此是探测式使用(取不到就走草稿降级)。
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
## 八、边界处理清单(对应需求第六点)
|
|
700
|
+
|
|
701
|
+
| 边界 | 处理 | 代码位置 |
|
|
702
|
+
|---|---|---|
|
|
703
|
+
| **空选 / 过短** | 选区为空、折叠、或短于 `minChars` → 不浮出按钮并清掉上一次的触发态 | `readSelection` / `shouldOffer`(client §5) |
|
|
704
|
+
| **编辑框内选择** | 选区锚点/焦点落在 `input`/`textarea`/`contenteditable` 内 → 视为编辑操作,不触发 | `isEditable`(client §5) |
|
|
705
|
+
| **跨区选择** | 锚点与焦点区域不同 → 标为"跨区选择",按**锚点**区域归类并在徽标上显示;仍可追问 | `readSelection` 的 `cross`、`zoneLabel` |
|
|
706
|
+
| **超长文本** | 头 70% + 尾 30% 保留,中间插入「已省略中间 N 个字符」;提问框与卡片都显示截断徽标;宿主侧再做一次硬上限(60000 字符提示词上限) | `truncateSelection`(两端一致,契约测试断言等价) |
|
|
707
|
+
| **接口失败** | 逐层降级:子代理不可用 → 卡片错误 + 「改到主对话」;主对话发送不可用 → 草稿写入;再不可用 → 明确报错不静默 | `toWireError` / `askInMainConversation` |
|
|
708
|
+
| **重复触发** | 同一选区 + 近似位置在 400ms 内只触发一次;同一卡片 id 的重复请求返回 `duplicate`;并发超限返回 `busy`(可重试) | `selectionSignature`、`runs.has(id)`、`maxConcurrentAsks` |
|
|
709
|
+
| **流式中断** | 浏览器断开 / 点「停止作答」/ 超时 → `AbortController` 取消子代理:有部分文本时以 `done{aborted:true}` 收尾并保留已流出的内容(卡片显示「已停止」),一个字都没流出时才用 `error.code='aborted'` | `handleAsk` 的 `res.on('close')` 判定、`sideTimeoutMs` |
|
|
710
|
+
| **父会话已归档** | 归档门会拒绝**整条子代理血统**里的每一步(表现为「没有发起任何模型请求的空 turn」),但它只存在于 0.1.7-alpha.1+。插件优先挑未归档代理;只剩归档候选时**照常尝试**并在 `start` 事件里带 `parentArchived:true`(旧版本本来就能正常作答,一刀切拒绝会误伤 0.1.2–0.1.6) | `resolveParent` / `archivedSessionIds`(host) |
|
|
711
|
+
| **步骤被宿主拒绝** | 子代理接缝把这种空 turn 记为 `refusal`;插件翻译成 `blocked-step`,文案点明常见原因(会话已归档)并给出「改到主对话」 | `ask()` 的终局映射(host) |
|
|
712
|
+
| **卸载** | 插件卸载时取消全部进行中的作答、注销槽位、移除 DOM 监听、断开流式桥 | `ctx.effect` + `disposers` |
|
|
713
|
+
| **跨站请求** | 插件路由带信任围栏:Host 必须是回环或配置的可信域,`sec-fetch-site: cross-site` 或跨域 `Origin` 一律 403 | `isTrustedRequest`(host) |
|
|
714
|
+
| **找不到原文(1.8.0)** | 签名匹配失败 / 页面无文本节点 → toast「原文已变化或不在当前视图」或「当前环境不支持定位」,**不静默** | `locateText` → `locatePassageRange`(client §8b) |
|
|
715
|
+
| **写回失败(1.8.0)** | 无输入框 / 草稿已变 / 写入被拒 → 分别是「找不到可写入的输入框」「输入框内容已变化…」「插入失败,请手动复制」;只在真能写时才渲染按钮 | `insertIntoComposer` / `canInsertAnswer`(client §8c) |
|
|
716
|
+
|
|
717
|
+
### 错误码对照(`error` 事件的 `code`)
|
|
718
|
+
|
|
719
|
+
| code | 含义 | 是否可重试 | 建议动作 |
|
|
720
|
+
|---|---|---|---|
|
|
721
|
+
| `bad-request` | 问题或选中文本为空 | 否 | 重新选中/输入 |
|
|
722
|
+
| `busy` | 并发追问达上限 | 是 | 稍后重试 |
|
|
723
|
+
| `duplicate` | 同一卡片 id 已有在跑的任务 | 否 | 等它结束 |
|
|
724
|
+
| `no-side-engine` | 没有可用的子代理 provider | 否 | 「改到主对话」 |
|
|
725
|
+
| `no-parent` | 没有活动会话代理 | 否 | 「改到主对话」 |
|
|
726
|
+
| `blocked-step` | 这一步被宿主的 `agent/pre-step` 拒绝(未发起请求);0.1.7+ 上最常见的原因是会话已归档 | 是 | 换会话或「改到主对话」 |
|
|
727
|
+
| `aborted` | 被取消或超时,且没有已流出的内容 | 是 | 重试 |
|
|
728
|
+
| `engine-error` / 其它 | 子代理启动、模型调用或传输失败 | 是 | 重试或改到主对话 |
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
## 九、三条关键路径的自测要点
|
|
733
|
+
|
|
734
|
+
### A. 路径一:就地追问 → 侧边卡片流式作答
|
|
735
|
+
|
|
736
|
+
1. 在聊天区选中一句**非输入框内**的文本(例如助手消息里的半句话);
|
|
737
|
+
2. 选区末端应浮出动作条:「💬 追问选中内容」按钮 + 「解释 / 翻译 / 总结 / 润色 / 纠错」芯片,
|
|
738
|
+
按钮带区域徽标(聊天区 / 任务区 / 跨区选择);**点任一芯片即跳过提问框直接在侧边卡片作答**;
|
|
739
|
+
3. 点主按钮后提问框出现(顶部同样有一排芯片),焦点在输入框,引文预览显示选中文本;
|
|
740
|
+
底部「作答位置」是两枚带内联图标的芯片(气泡=主对话,带右轨的框=侧边卡片):**选中侧为品牌实底 + 白字 600 + 内白环**,
|
|
741
|
+
未选侧只有描边;深色主题下选中侧改深墨字(品牌色在深色被提亮到 `#5b9cf5`,白字只有 2.79:1,深墨字 6.57:1);
|
|
742
|
+
控件带 `role="group"` 与 `aria-pressed`,键盘 Tab 可见品牌色焦点环;
|
|
743
|
+
4. 输入问题按 Enter → 提问框关闭,右下角出现卡片,状态先为「作答中…」并**逐字增长**;
|
|
744
|
+
5. 结束后状态变「已完成」;底部出现「改问 / 复制 / 继续追问 / 关闭」;答案里代码块的右上角有「⧉ 复制代码」;
|
|
745
|
+
6. 点「复制」→ 出现「已复制」提示;点「继续追问」→ 输入第二条问题(追加一轮),卡片内容**重置换行**后继续流式;
|
|
746
|
+
点「改问」→ 修改当前问题后**重新生成这一轮**(不追加历史);
|
|
747
|
+
7. 点「关闭」→ 卡片消失;若承载面是 better-sidebar/原生右侧栏,对应 tab 也应关闭。
|
|
748
|
+
7b. 设置页「快捷动作」区:可停用内置动作、给动作配快捷键(如 `Alt+T`)、添加自定义动作
|
|
749
|
+
(名称 + 问题指令,指令里可写 `{selection}`);配好后选中文字直接按快捷键等价于点芯片。
|
|
750
|
+
7c. 答案完成后:问题下方显示「模型 spawn · 耗时 Xs」,答案下方出现
|
|
751
|
+
「更简短 / 更详细 / 举个例子 / 译成英文」芯片;卡片可 Tab 聚焦,`R` 重试、`C` 复制、`Esc` 失焦。
|
|
752
|
+
7d. 在选中文字上**右键**:应出现本插件菜单(追问 + 前三个快捷动作);设置页关闭「选区右键菜单」后,
|
|
753
|
+
右键恢复为宿主原生菜单。
|
|
754
|
+
7e. 设置页勾选「完成后桌面通知」会请求通知权限;授权后切到其它标签页/窗口,作答结束时应弹系统通知。
|
|
755
|
+
7f. 设置页「历史追问卡片」:可搜索、恢复到浮层、单条删除、清空;「历史卡片保存数量」设 0 后新卡片不再写盘;
|
|
756
|
+
注意:**关闭浮层卡片不会删历史**(1.8.0 起历史只在管理中心删除)。
|
|
757
|
+
**改发到主对话(`carrier: 'main'`)的问答不算卡片记录**(2.1.1 起):它只出现在主对话里,不写
|
|
758
|
+
localStorage,也不出现在历史管理器与「导出全部 .md」;旧版本写进去的 main 条目在读取时丢弃。
|
|
759
|
+
7g. **回答风格(1.8.0)**:设置页把「回答风格」切成「精简」并保存 → 下一次追问的答案应明显更短、
|
|
760
|
+
不复述原文;切「详细」→ 应看到展开与依据。自检办法:`GET /state` 的 `config.answerStyle` 应为所选值;
|
|
761
|
+
若 provider 不支持 persona,指令会内联进提示词(卡片上的 provider 行为不变)。
|
|
762
|
+
在「自定义指令」里写一句可辨认的要求(如"每条都用表格")→ 下一次答案应体现;超过 500 字时
|
|
763
|
+
保存会被拒绝(`invalid-config`),不会半保存。
|
|
764
|
+
7h. **插入到输入框(1.8.0)**:在主对话输入框里先随便打几个字(**不要发送**),再选中一段正文 →
|
|
765
|
+
点「润色」芯片 → 答案完成后卡片上出现「插入到输入框」;点它 → 输入框末尾应追加答案,
|
|
766
|
+
并提示「已插入到输入框,请按 Enter 发送」。**已发送的消息不得被改动**。
|
|
767
|
+
判据:插入后输入框里的内容 = 原有草稿 + 答案(末尾追加,不是光标处插入);再点一次会再追加一份。
|
|
768
|
+
若在输入框里先选中一段文字(文本框或 textarea),才可能看到「替换原文」。
|
|
769
|
+
7i. **查看原文(1.8.0)**:在聊天区选中一段文字追问 → 答案完成后点「查看原文」→ 页面应滚动回那段
|
|
770
|
+
原文并高亮约 4 秒;连点两次不应叠加高亮。把会话滚动到别处、或让该消息被虚拟列表回收后再点 →
|
|
771
|
+
应看到提示「原文已变化或不在当前视图」(**这不是 bug**,是如实降级)。
|
|
772
|
+
7j. **建议追问(1.8.0)**:设置页打开「答案末尾给出建议追问」→ 下一次答案末尾应出现「建议追问」
|
|
773
|
+
与 2–3 个芯片;点其中一个 → 它以**新一轮**追问发出,旧问答进入卡片历史。
|
|
774
|
+
关掉开关 → 新答案不再出现该行。模型没有按格式输出时(可人为在提示词上干扰)应只有答案、没有芯片。
|
|
775
|
+
7k. **导出(1.8.0)**:卡片上点「导出 .md」→ 应下载 `sidecard-ask-YYYY-MM-DD-HHMM.md`,
|
|
776
|
+
内容依次是问题标题、`> 原文` 引用块、答案原文(Markdown 原样);设置页历史区点「导出全部 .md」→
|
|
777
|
+
一条历史一节、`---` 分隔。浏览器若拦截下载,应看到「导出失败:浏览器不允许下载」而不是静默。
|
|
778
|
+
7l. **多版本(1.8.0)**:对一张已完成卡片点「改问」并改成另一个问题 → 重新作答后卡片上出现
|
|
779
|
+
「第 1 版」;点它 → 显示**旧问题 + 旧答案**并标注"正在查看历史版本(只读)",点「返回最新」回到新答案。
|
|
780
|
+
刷新页面后历史卡片仍能切版本(v3 持久化)。连续改问 6 次后只保留最近 5 版。
|
|
781
|
+
8. **浮层形态与位置(1.5.0)**:默认应是一个**一行高的胶囊**(状态 + 问题首句),点它才展开成完整卡片;
|
|
782
|
+
展开后标题栏右侧有**收起为胶囊(⇥)**与**删除这条追问对话(×)**——× 会把该卡片从浮层与追问历史里
|
|
783
|
+
一并移除,**无法撤销**(有内容时会先弹插件内确认层;点浮层底部的「关闭」则只把它摘离浮层、历史仍保留在
|
|
784
|
+
设置页的历史管理器里)。2.0.0 起不再有「隐藏整个浮层 / 只留一个计数圆点」这套形态。
|
|
785
|
+
用 F12 选中浮层根节点(`[data-dsa-root]`)读两个值自检:
|
|
786
|
+
- `data-dsa-float-mode` 应为 `capsule` / `full`(铺满右栏时为 `dock`);
|
|
787
|
+
- `data-layer-width` 应等于 `min(floatMaxWidth, 对话区右边界到帧右边缘的可用带宽)`。
|
|
788
|
+
**关键判据:胶囊/卡片的左边缘不得越过对话区(`[data-slot^="conversation"]`)的右边界**。
|
|
789
|
+
把窗口从 1920px 拖到 900px、再开关一次右侧栏,浮层应随之变窄而不是往左压住正文;
|
|
790
|
+
只有窗口窄到可用带宽不足 240px 时才允许压入(此时是设计内的降级,不是 bug)。
|
|
791
|
+
9. **占满右侧栏(1.8.3,`dockRail`)**:先把右侧栏打开(任意面板,例如「团队任务板」),再追问一次 →
|
|
792
|
+
答案卡应铺满**标签条下方**的整块内容区:右边缘贴帧边、下边缘贴窗口底,右栏的标签条仍然露出可点。
|
|
793
|
+
点卡片标题栏的 ⇥(收起为胶囊)→ 卡片立即退出占满、回到常规浮层,右栏内容区恢复;点 × 则是删除这条
|
|
794
|
+
追问对话(同样退出占满,但卡片与历史都不再存在)。
|
|
795
|
+
自检:F12 读浮层根节点,`data-dsa-float-mode` 应为 `dock`,`data-dsa-dock` 也应为 `dock`;
|
|
796
|
+
`.dsa-stack` 上的 `data-dsa-dock-body` 应等于卡片宽度(px)。**没铺满就看 `data-dsa-dock`**:
|
|
797
|
+
`no-rail` = 没找到右栏,`rail-too-small` = 找到了但太窄或在帧外,`off` = 开关关了,`mode` = 不是完整形态。
|
|
798
|
+
把右栏拖到 260px 宽 → 应自动回退成常规浮层(低于 280px 不铺满);再拖回 400px → 应重新铺满。
|
|
799
|
+
设置页取消「右侧栏打开时占满它」并保存 → 同一操作不再铺满。
|
|
800
|
+
|
|
801
|
+
**失败信号**:卡片停在「作答中…」不动 = SSE 帧没到达(看 console 是否有 `/sidecard-ask/api/ask` 报错);`done.streaming=false` = 该版本没有流式帧(属预期降级,卡片会写明)。
|
|
802
|
+
|
|
803
|
+
### B. 路径二:主对话承载
|
|
804
|
+
|
|
805
|
+
1. 选中文本 → 提问框里把「作答位置」切到**主对话** → 输入问题 → Enter;
|
|
806
|
+
2. 主对话应立刻出现一条用户消息,形如:
|
|
807
|
+
|
|
808
|
+
```
|
|
809
|
+
> 选中的原文(逐行引用)
|
|
810
|
+
> …(已截断时会有「已省略约 N 字」)
|
|
811
|
+
|
|
812
|
+
【来源:聊天区】
|
|
813
|
+
你的问题
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
3. 该消息的答案由**当前会话**正常流式输出(这就是"主对话承载"的定义);
|
|
817
|
+
4. 卡片侧显示「已发送到主对话」并说明答案在主对话中;若接口不可用而降级为草稿写入,卡片会明确显示「已填入输入框」,此时需手动按 Enter 发送(**不算失败**,但要能看到这条提示)。
|
|
818
|
+
|
|
819
|
+
### C. 路径三:快捷键 + 引擎不可用的降级
|
|
820
|
+
|
|
821
|
+
1. 选中文本 → 按 `Alt+Q` → 提问框直接出现(无需点按钮);
|
|
822
|
+
2. 在设置页把「触发方式」改成「仅快捷键」→ 再选中文本时**不应**出现按钮,但 `Alt+Q` 仍可用;
|
|
823
|
+
3. 把「侧边卡片承载面」强制设为 `native-rightbar` 或 `better-sidebar`(未安装/未启用时)→ 追问应回退到内置浮层卡片,并提示"已回退";
|
|
824
|
+
4. 用一个没有子代理的 DSH 组合(或临时把 `sideProvider` 设成一个不存在的名字)→ 侧边作答应返回错误卡片:
|
|
825
|
+
文案说明"没有可用的子代理",并给出「改到主对话」按钮;点它应把同一问题转到主对话(不得丢失已选中的文本与问题)。
|
|
826
|
+
|
|
827
|
+
---
|
|
828
|
+
|
|
829
|
+
## 十、自测脚本
|
|
830
|
+
|
|
831
|
+
```powershell
|
|
832
|
+
cd D:\Users\34332\AI\dsh-sidecard-ask
|
|
833
|
+
node test/verify.mjs # 静态:清单/补丁/导出面/i18n/零依赖
|
|
834
|
+
node test/contract-test.mjs # 契约:常量、信封、SSE 逐帧、纯函数、槽位注册与渲染
|
|
835
|
+
node test/float-placement.mjs # 浮层定位矩阵
|
|
836
|
+
node test/smoke-test.mjs # 冒烟:装载、槽位、渲染、错误路径
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
当前规模(本机实测,四项均 exit 0):verify **57** 项 + contract **1377** 项 + float-placement **82** 项 +
|
|
840
|
+
smoke **262** 项 = **1778 项全部通过**(较 1.8.4 的 1359 多 419 项;contract 从 989 涨到 1377,新增大多来自
|
|
841
|
+
反馈功能与 2.0.x 的跨端修复:
|
|
842
|
+
纯函数边界、`parseSmtpUrl` / `readSmtpFromEnv` 两种写法(含复用 `DSH_CRB_SMTP_URL`)、`b64:` 收件人解码、
|
|
843
|
+
「环境变量优先于用户层配置」的优先级回归、`Reply-To:` 组头、限速闸门,以及一条**真 socket** 的 `/feedback`
|
|
844
|
+
端到端——本地 SMTP 假中继逐条断言 `AUTH PLAIN`、信封、`DATA`、编码后的主题与正文、429 与 `retry-after`,
|
|
845
|
+
并断言正文没有进日志;1.9.1 又把内联表单的断言整块换成「横幅一行 + 弹窗表单」的断言)。
|
|
846
|
+
|
|
847
|
+
四个脚本都以 `process.exitCode` 反映结果,失败会列出具体条目;测试会把 `DSH_HOME` 指向临时目录,不会污染真实配置。
|
|
848
|
+
1.8.4 为 1359 项:contract 989 → 998,新增 6 条钉住选区动作条的版式(单一面板、品牌实底主按钮、
|
|
849
|
+
chips 去边框、分隔线规则、深色墨字覆盖、面板自带 `maxWidth` 上限)。
|
|
850
|
+
1.8.3 为 1350 项:contract 972 → 989,新增 8 项钉住设置页的「占满
|
|
851
|
+
右侧栏」复选框、3 个新 i18n 键,以及承载面 `auto` 的新顺序;float-placement 38 → 78,新增 40 项覆盖 `railDock` 的夹取/最小尺寸/
|
|
852
|
+
`clipped` 语义、真机上 dock 没生效那次发现的三个缺陷(残留的 `rail` 变量、右栏开合不触发重算、
|
|
853
|
+
右栏锚点必须优先用插件自己的 `[data-dsa-rail-host]` 节点),以及「自动链里不出现原生右侧栏」这条顺序断言)。更早的 1.8.1 为 1277 项,其中 contract
|
|
854
|
+
由 956 → 972 是提问框「作答位置」分段芯片的 16 项——唯一 `.dsa-seg`、两个按钮、恰好一个
|
|
855
|
+
`aria-pressed`、各带一枚内联图标、品牌实底规则与深色覆盖规则;点击切换本身不可测,原因见 1.8.2 的 CHANGELOG。
|
|
856
|
+
|
|
857
|
+
**真机探针(不属于四套自测)**:`.compat-report/scripts/composer-probe.mjs` 需要
|
|
858
|
+
`playwright-core` 与本机 Edge,对**真实 DSH 页面**回答 composer DOM / 哪条写入路径真能改变
|
|
859
|
+
编辑器状态 / `CSS.highlights` 是否可用三个问题(证据 `.compat-report/evidence/p2-probe-2026-10-17.json`)。
|
|
860
|
+
它**不**探测 `inputActions`——那是通过槽位 prop 交给插件的面,页面侧脚本读不到;该写入面的存在性
|
|
861
|
+
改由**产物层证据**背书(`.compat-report/evidence/p2-inputactions-contract-2026-10-17.json`)。
|
|
862
|
+
探针是 P2-2 的落地依据,**不能**被四套自测替代,也不替代人工观感确认。
|
|
863
|
+
|
|
864
|
+
> 1.5.0 之前这里还放过 `tools/balance-check.mjs` 与 `tools/i18n-check.mjs` 两个「替代检查」。
|
|
865
|
+
> 它们已被**删除**:前者用「正则字面量 vs 除号」的启发式判断,把 8 个源文件全部误报为残损;
|
|
866
|
+
> 后者定位词典块的缩进假设写错,解析出 0 个键并误报上百条缺失。**跑不了测试时宁可标注「未验证」,
|
|
867
|
+
> 也不要引入会撒谎的检查器**——真实把关始终只有上面那四个 `test/*.mjs`。
|
|
868
|
+
|
|
869
|
+
### 10.2 版本能力探测(§7 矩阵的来源)
|
|
870
|
+
|
|
871
|
+
```powershell
|
|
872
|
+
node tools/compat-probe.mjs # 13 个版本 × 12 个包,逐个 npm pack 后按标记判定
|
|
873
|
+
node tools/compat-probe.mjs --json tools/.cache/matrix.json
|
|
874
|
+
node tools/compat-probe.mjs 0.1.7-rc.2 # 也可只探某个版本
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
首次运行会下载约 250 个包到 `tools/.cache/tarballs/`(已 gitignore,之后走缓存);输出的矩阵就是 README §7 的表。
|
|
878
|
+
|
|
879
|
+
### 10.1 对已安装实例做真实联调(本机实测通过)
|
|
880
|
+
|
|
881
|
+
> **2.1.0 起除 `/ping` 外的每个接口都走宿主会话门。** 不带浏览器登录 cookie 的脚本调用会拿到
|
|
882
|
+
> `401 {"error":{"code":"unauthorized"}}`,这表示"没登录",**不表示插件没起来**——先用第 0 步区分。
|
|
883
|
+
> 页面内自检(设置 → 侧边卡片追问 → 运行自检)读的是同一份 `/state`,不需要任何脚本。
|
|
884
|
+
|
|
885
|
+
```powershell
|
|
886
|
+
# 0) 探活:唯一免鉴权的接口,只回插件版本与宿主时钟
|
|
887
|
+
(Invoke-WebRequest http://127.0.0.1:8080/sidecard-ask/api/ping -UseBasicParsing).Content
|
|
888
|
+
# → {"ok":true,"value":{"version":"2.1.1","at":1792...}} 200 = 插件宿主半已加载
|
|
889
|
+
|
|
890
|
+
# 1) 宿主接口与能力(archived 父会话检测也在这里)——需要会话
|
|
891
|
+
# 在本机浏览器已登录的前提下复用会话:先 -WebSession 登录,或把浏览器的 Cookie 头带进来
|
|
892
|
+
(Invoke-WebRequest http://127.0.0.1:8080/sidecard-ask/api/state `
|
|
893
|
+
-Headers @{ Cookie = $cookie } -UseBasicParsing).Content
|
|
894
|
+
|
|
895
|
+
# 2) 端到端流式作答:把 sessionId 换成当前会话 id($env:DSH_SESSION_ID);同样需要会话
|
|
896
|
+
# 期望事件序列:start → reasoning*/delta* → status → done(done.text 是真实答案)
|
|
897
|
+
$body = @{ id='probe'; question='用一句话说明这句话在说什么。';
|
|
898
|
+
selection='槽位出口会带上 data-slot 标记。'; zone='chat';
|
|
899
|
+
sessionId=$env:DSH_SESSION_ID } | ConvertTo-Json -Compress
|
|
900
|
+
$tmp = Join-Path $env:TEMP 'probe.json'
|
|
901
|
+
[System.IO.File]::WriteAllText($tmp, $body, (New-Object System.Text.UTF8Encoding($false)))
|
|
902
|
+
(Invoke-WebRequest http://127.0.0.1:8080/sidecard-ask/api/ask -Method POST `
|
|
903
|
+
-Headers @{ Cookie = $cookie } `
|
|
904
|
+
-ContentType 'application/json; charset=utf-8' -InFile $tmp -TimeoutSec 240 -UseBasicParsing).Content
|
|
905
|
+
|
|
906
|
+
# 3) 客户端半的真实状态(浏览器跑的那份代码/槽位落点/两个适配器)
|
|
907
|
+
# 页面加载后 GET /state,看 value.client —— 这是唯一能从页面外读到客户端状态的通道
|
|
908
|
+
((Invoke-WebRequest http://127.0.0.1:8080/sidecard-ask/api/state `
|
|
909
|
+
-Headers @{ Cookie = $cookie } -UseBasicParsing).Content | ConvertFrom-Json).value.client
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
**客户端状态样例**(1.3.0 起):
|
|
913
|
+
|
|
914
|
+
```json
|
|
915
|
+
{
|
|
916
|
+
"at": 1790555771338,
|
|
917
|
+
"version": "1.3.0",
|
|
918
|
+
"surface": "native-rightbar",
|
|
919
|
+
"zoneAnchors": true,
|
|
920
|
+
"sessionKnown": true,
|
|
921
|
+
"slots": { "overlay": "shell.overlay", "settings": "settings.section", "sessionProbe": "conversation.input.right" },
|
|
922
|
+
"native": { "available": true },
|
|
923
|
+
"better": { "available": true, "version": "0.22.1", "features": ["badge", "…", "tabMeta"] }
|
|
924
|
+
}
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
`native.reason` / `better.reason` 会写明**为什么**某个承载面不可用(例如 `no-tab-meta` 或注册表的报错原文)——
|
|
928
|
+
1.2.1 修掉的那个 tab kind 冲突正是靠这类信息才定位到的。
|
|
929
|
+
|
|
930
|
+
> **两半代际不一致是常态**:宿主半只在重启 DSH 时更新,客户端半在页面加载时更新。
|
|
931
|
+
> 所以"新客户端 + 旧宿主"很常见——此时 `/diagnose` 还不存在,客户端会**识别这一点、只提示一次并停止重试**
|
|
932
|
+
> (`not-found` / SPA 兜底 HTML / 不可达三类失败),不会每次加载都刷警告。
|
|
933
|
+
|
|
934
|
+
**2026-09-28 在 DSH 0.1.7-rc.2 上的实测结果**(`provider=spawn`、`sideTools=readonly`):
|
|
935
|
+
`start x1 → reasoning x181 → delta x90 → status x1 → done x1`,耗时 2.6 s,`done.text` 为真实模型答案。
|
|
936
|
+
这条记录同时说明:子代理启动、只读工具白名单(与真实工具表求交后)、流式帧桥接、SSE 线协议、运行结束后的 `dispose()` 都是**在真实宿主上跑通的**,
|
|
937
|
+
不只是单元测试里的替身。
|
|
938
|
+
|
|
939
|
+
> 改过 `index.js` 之后**必须重启 DSH** 才会加载新的宿主半代码:插件行在 profile 启动时已被 Loader 导入,重新启停该行不会重新读盘
|
|
940
|
+
> (本机实测:`set_plugin` 关开、`remove_bundle` + `install_bundle` 都不会刷新已加载的模块;只有进程重启会)。
|
|
941
|
+
> `client.js` 的改动则需要刷新页面(客户端产物带修订号,刷新即重新拉取)。
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
---
|
|
945
|
+
|
|
946
|
+
## 十一、架构与数据流
|
|
947
|
+
|
|
948
|
+
```
|
|
949
|
+
选中文本 (client §5)
|
|
950
|
+
│ page → data-slot 链 → 区域归类 → 触发按钮
|
|
951
|
+
▼
|
|
952
|
+
提问框 (client §9) ── 作答位置选择 ──┐
|
|
953
|
+
│ │
|
|
954
|
+
│ side │ main
|
|
955
|
+
▼ ▼
|
|
956
|
+
POST /sidecard-ask/api/ask ctx.sessions.using(id).binding.session.prompt(...)
|
|
957
|
+
│ (SSE) │ ↓ 草稿降级 / 复制兜底
|
|
958
|
+
▼ 答案随主对话原生流式
|
|
959
|
+
Host: ctx.subagents.start('spawn')
|
|
960
|
+
→ 子代理(自己的会话、零父上下文)
|
|
961
|
+
→ agent/assistant-stream 帧 → sink.send('delta')
|
|
962
|
+
→ run.result 结算 → sink.send('done') → run.dispose()
|
|
963
|
+
▼
|
|
964
|
+
卡片流式渲染 / 复制 / 继续追问 / 关闭
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
**为什么侧边卡片用子代理而不是"另开一个会话"**:子代理由进程内 spawn provider 建立,拥有自己的会话与系统提示、**不继承父上下文**,
|
|
968
|
+
且随 `dispose()` 释放——这正是"独立附属"的语义;同时它不需要往工作区/侧边栏注册新会话,用户不会在会话列表里看到一堆临时条目。
|
|
969
|
+
|
|
970
|
+
---
|
|
971
|
+
|
|
972
|
+
## 十二、隐私与安全
|
|
973
|
+
|
|
974
|
+
- 选中文本与问题会**发给你自己配置的模型**(走 DSH 既有的模型路由),插件不做任何额外外发。
|
|
975
|
+
- 插件只在本地写一个配置文件:`<DSH_HOME>/sidecard-ask/config.<profile>.json`(原子写入:先写 `.tmp` 再 rename;
|
|
976
|
+
POSIX 下目录 `0700`、文件 `0600`,因为里面可能有 SMTP 授权码)。
|
|
977
|
+
- 插件路由带**两层**围栏:回环 / 可信域 + 同源校验(Host 头、`Origin`、`Sec-Fetch-Site`),然后交给宿主的
|
|
978
|
+
`connection.requestRejection()` 校验 **DSH 浏览器会话**(宿主没挂载该服务时才只保留第一层)。
|
|
979
|
+
因此未登录的外部程序拿到 loopback 地址也读不到 `/state`、更发不出 `/ask`。
|
|
980
|
+
- 侧边作答者默认**只读**(`sideTools: readonly`):白名单只包含读取、搜索、网络查询类工具,不会在后台改你的工作区。
|
|
981
|
+
- 提示词里选中文本被包在 ```` ```text ```` 围栏中并显式声明"这是数据不是指令",降低提示注入面。
|
|
982
|
+
|
|
983
|
+
---
|
|
984
|
+
|
|
985
|
+
## 十三、发布流程
|
|
986
|
+
|
|
987
|
+
```powershell
|
|
988
|
+
npm whoami # 先确认登录态;本机用的是 granular Publish token
|
|
989
|
+
node test/verify.mjs; node test/contract-test.mjs; node test/float-placement.mjs; node test/smoke-test.mjs
|
|
990
|
+
npm publish --access public # 1.0.1 那次被 npm 暂存(staged)后才转正;再遇到可用 --otp=<验证码>
|
|
991
|
+
git push origin main # 仓库:https://github.com/HERO476/dsh-sidecard-ask
|
|
992
|
+
git tag v1.5.0; git push origin v1.5.0 # 版本号与 package.json 一致
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
改名/迁移时的额外两步(1.1.0 实际做过):
|
|
996
|
+
|
|
997
|
+
```powershell
|
|
998
|
+
plugin_manager(action="remove_bundle", target="<旧包名>") # 卸旧 bundle(同时移除 profile 里的 link)
|
|
999
|
+
plugin_manager(action="install_bundle", target="D:\...\<新目录>")
|
|
1000
|
+
npm deprecate "<旧包名>@*" "Renamed to <新包名> - <原因>" # 旧名指路,避免别人再装到废弃名
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
版本号同时出现在三处,必须一致:`package.json` 的 `version`、`index.js` 的 `PLUGIN_VERSION`、`client.js` 的 `api.version`
|
|
1004
|
+
(`test/verify.mjs` 会断言前两处;第三处在契约测试中同样被断言)。
|
|
1005
|
+
|
|
1006
|
+
**发布前必查"地址类信息"**(1.0.0 的教训):包名不能只查 npm —— 还要查 GitHub 仓库名是否已被占用,以及
|
|
1007
|
+
[`awesome-dsh-plugin` 收录索引](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/tree/main/data/plugins)
|
|
1008
|
+
里是否已有同名插件(本插件第一次改名就是因为漏查了后者)。
|
|
1009
|
+
|
|
1010
|
+
---
|
|
1011
|
+
|
|
1012
|
+
## 十四、已知限制(含未在本机验证的部分)
|
|
1013
|
+
|
|
1014
|
+
1. **区域归类是启发式的**:`data-slot` 名称来自 harness 自己;其它插件若在自己的面板里不再经由槽位渲染,
|
|
1015
|
+
该区域会被归为"其它区域"——用 `captureZones: auto` + `showInUnclassified: true`(默认)仍然可用,或改用 `captureZones` 限制。
|
|
1016
|
+
本机实测能识别:`conversation.*`(聊天)、`rightbar` / `sidebar.right.*` / 名字含 task·todo·schedule·team·job·plan 的面板(任务)。
|
|
1017
|
+
2. **浏览器内的视觉与交互**:1.6.0 起用无头 Edge(Playwright,系统浏览器 `channel:'msedge'`)做真机验证,
|
|
1018
|
+
脚本见 `.compat-report/scripts/`,截图见 `.compat-report/shots/`——选区浮按钮、Alt+Q 弹层、共载单实例、
|
|
1019
|
+
引擎自检均已在 0.2.0-rc.2 与 0.2.1-alpha.2 上自动核对;0.2.1-alpha.3 / alpha.4 的复核见 §7.3。桌面 Electron 的视觉观感仍只有 2026-09-30 的人工实测(§5.3)。
|
|
1020
|
+
已做的保障是:只使用 `--dsw-alias-*` 主题令牌、只在槽位内渲染(`shell.overlay` / `settings.section` / `conversation.input.right`)、不写 `document.body`、不 import harness 客户端包。
|
|
1021
|
+
2b. **1.5.0 的浮层改动:四个测试脚本已在本机实跑通过(各 exit 0),但页面里的观感仍未验证**:
|
|
1022
|
+
写这个版本的前半段,本工作区的 shell 工具整体不可用(任何命令都返回
|
|
1023
|
+
`SetNamedSecurityInfoW failed (Win32 5): grantWrite(<工作区>)`),所以当时的改动**没有跑过任何命令**,
|
|
1024
|
+
我也没有资格说它"验证过"。后半段该故障解除后已补跑:`node --check` 对全部源文件通过,
|
|
1025
|
+
`verify` 53 / `contract` 296 / `float-placement` 38 / `smoke` 138 **全部 0 failed**。
|
|
1026
|
+
其中 float-placement 第一轮曾报 7 条红,逐条查完**全部是我把测试写错**(旧实现的越界 fixture 算错、
|
|
1027
|
+
`avail` 期望值用了带右栏的用例、`capped` 语义写反、以及去找一个源码里并不存在的
|
|
1028
|
+
`setFloatMode('capsule')` 调用点——真实实现走 `floatControl('capsule','floatCollapse','⇥')` 的
|
|
1029
|
+
`onFloatMode` 回调),实现本身没有因此改动。
|
|
1030
|
+
仍未验证的是**浏览器内的视觉观感**:浮层实际宽度是否等于预算、是否真的不越过对话区右边界,
|
|
1031
|
+
必须按第九节 **A 路径第 8 条**在真实页面上确认(读 `data-dsa-float-mode` 与 `data-layer-width`),测试替代不了。
|
|
1032
|
+
此外,本版一度新增过 `tools/balance-check.mjs` 与 `tools/i18n-check.mjs` 两个"替代检查",
|
|
1033
|
+
**现已删除**:它们本身有错(前者把 8 个源文件全误报为残损,后者解析出 0 个词典键并误报上百条缺失),
|
|
1034
|
+
属于会撒谎的检查器——跑不了测试时应当标注"未验证",而不是用一个错的检查器去证明没问题。
|
|
1035
|
+
3. **原生右侧栏承载面未在真实宿主上验证**:本机 profile 虽已启用 `@deepseek-ai/dsh-client-ui-sidebar-right`,但没有浏览器控件去确认 tab 是否真的渲染。
|
|
1036
|
+
为此加了一道**渲染证明**:适配器接受了打开请求后 600ms 内若没有观察到我们的卡片组件挂载,就自动把卡片移到内置浮层(流式内容不丢,因为卡片数据在 store 里)。
|
|
1037
|
+
4. **主对话承载的"发送"依赖客户端会话服务**:`ctx.sessions.using(...).prompt(...)` 在会话未被保留/不可用时降级为草稿写入,
|
|
1038
|
+
降级时会在卡片上明确显示,需要手动按 Enter。
|
|
1039
|
+
5. **宿主半代码更新必须重启 DSH**:本机实测 `set_plugin` 关开与 `remove_bundle` + `install_bundle` 都不会让已加载的模块重新读盘。
|
|
1040
|
+
本仓库当前文件比运行中的宿主模块新(1.0.1 的「归档父会话不再一刀切 / 持久化 chunk 流式源 / 能力门控 / 路由降级」),
|
|
1041
|
+
**重启后**这些改动才生效;`/state` 的 `version` 与 `capabilities.parent` 字段可以直接确认是否已加载新版。
|
|
1042
|
+
6. **"安装启动"真机验证从 0.1.5-rc.3 起**:1.6.0 实测 0.1.5-rc.3 → 0.2.1-alpha.2,1.7.1 复核 0.2.1-alpha.3 / alpha.4(见 §7.3);
|
|
1043
|
+
更早版本只有产物层证据——验证到的是"这些版本缺哪些 API + 插件对每个缺失都有分支",分支本身由 1267 项自测覆盖。
|
|
1044
|
+
7. **原文定位(1.8.0)不承诺 100% 命中**:签名匹配依赖"那段文字此刻仍在 DOM 里"。虚拟列表回收、
|
|
1045
|
+
消息折叠、流式重渲染、富文本把文本拆进多个节点都会导致找不到——此时卡片明示
|
|
1046
|
+
「原文已变化或不在当前视图」,**不会**静默无反应。高亮优先 `CSS.highlights`(不改 DOM,
|
|
1047
|
+
本机 Edge 155 实测可用),不支持时退化为 `window.getSelection()` 选中,观感不同属预期。
|
|
1048
|
+
8. **写回输入框(1.8.0)只保证"追加到末尾"**:DSH 的 composer 是 Lexical contenteditable,
|
|
1049
|
+
脚本设定的 Range 不被采纳(探针实测),所以无法精确插到光标处;`document.execCommand('insertText')`
|
|
1050
|
+
在该编辑器上**无效**(返回 false、文本不变)。写入优先走槽位 `inputActions.insertText`,
|
|
1051
|
+
取不到时用 `beforeinput` 追加。**已发送的消息不会被改动**;"替换原文"只在捕获到文本框内选区时出现,
|
|
1052
|
+
contenteditable 不提供替换。真实页面里的按钮点击与手感尚未人工验证(见 CHANGELOG 1.8.0 未验证栏)。
|
|
1053
|
+
9. **导出(1.8.0)不往 `document.body` 插节点**:下载锚点是游离节点,Chromium/Safari 会触发下载,
|
|
1054
|
+
个别浏览器(老 Firefox)可能要求节点在文档里——那种情况下插件只报「导出失败」,不会伪造成功。
|
|
1055
|
+
Electron 外壳的下载落盘目录与权限未在本版复测。
|
|
1056
|
+
10. **建议追问(1.8.0)依赖模型遵循格式**:模型不写 `<followups>`、写半个、或写得过长时,
|
|
1057
|
+
客户端只会"少几个芯片",答案正文照常显示;这是设计内的降级,不是错误。该功能不额外发请求。
|
|
1058
|
+
11. **多版本(1.8.0)只在「重试 / 改问」时产生**:普通「继续追问」把旧问答存进 `history`(作为上下文),
|
|
1059
|
+
不进 `versions`;每卡上限 5 条,超出丢最旧。v3 持久化**默认保留** v2/v1 旧键(回退旧版本仍可读
|
|
1060
|
+
自己的数据);**唯一的例外是「清空全部历史」**——显式清空会同时删除 v3/v2/v1 三代键(1.8.1)。
|
|
1061
|
+
这条例外是必需的:只删当前键会让 `readPersisted()` 的 v3→v2→v1 回落把刚清掉的历史从旧键读回来,
|
|
1062
|
+
刷新一次就复活(1.8.1 复现并修复)。
|
|
1063
|
+
12. **接口鉴权(2.0.2 起,2.1.0 补 `/ping`)**:除 `/ping` 外的每个接口都要宿主会话;从页面外读
|
|
1064
|
+
`/state` 会得到 `401 unauthorized`。`/ping` 是唯一免鉴权的探活口,只回 `{version, at}`——
|
|
1065
|
+
不要往里加字段,它存在的理由是"能给监控用",不是"能省登录"。
|
|
1066
|
+
13. **活动卡片上限 20(2.1.0)**:超过后最旧的**已完成**卡片自动隐藏(`open:false`,数据仍在,
|
|
1067
|
+
历史里可恢复),正在流式的卡片永不隐藏。这不是删除,但会"少一张卡片在浮层里"。
|
|
1068
|
+
14. **冷启动回放预算 200 K 字符(2.1.0)**:最新一张必定展开,其余按累计字符预算展开;
|
|
1069
|
+
超预算的旧卡片以折叠态回来,点开才解析。改 `historyMaxCards` 不改变这个预算。
|
|
1070
|
+
15. **部署前缀不带尾斜杠时(2.1.0)**:`API` 基址会先按 `document.baseURI` 猜一次,若得到 404
|
|
1071
|
+
则回退到根绝对路径重试一次。这是唯一安全的判别方式(401/403 都不能用来改基址),
|
|
1072
|
+
代价是每次页面加载最多多一次 404 往返——正常部署下不会触发。
|
|
1073
|
+
|