dsh-newbe-response-window 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -11
- package/cordis.patch.yml +6 -0
- package/lib/client.js +213 -57
- package/lib/index.js +30 -13
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
DeepSeek Harness (DSH) Web 插件:把一轮里的 think(推理)和工具调用放进**有限高度的可滚动窗口**(默认 10 行),以**阶段性文字回复为界分段**,每个分段一个 **slide**;文字回复保持原生完整显示、不做限高。中间过程**始终可见**(只是不撑爆页面)——Grok Build 风格。
|
|
4
4
|
|
|
5
|
-
> 已验证 DSH 版本:**0.1.
|
|
5
|
+
> 已验证 DSH 版本:**0.1.7-alpha.1**(2026-09)。
|
|
6
6
|
> 与「全部折叠成 summary」类插件(如 `dsh-tool-summary`)不同:本插件**不隐藏**任何内容。think 与工具调用一条条列在 slide 里,每段都能展开翻到底;文字回复完全原生展示。
|
|
7
7
|
> 只包 bash + think:阶段性/最终文字 response **不**加限高、**不**加「展开全部/收起」按钮。
|
|
8
|
+
> **0.1.7 起的一轮折叠**:DSH 0.1.7 原生把「已完成的一轮」的中间过程折叠成一行「已完成工作 N 个工具调用」(简洁/详细/完全展开三种「工作过程展示」模式都折叠)。进行中的一轮 slide 照常常显;已完成的一轮点开这一行后,看到的就是本插件的 slide。
|
|
8
9
|
|
|
9
10
|
## 效果
|
|
10
11
|
|
|
@@ -16,6 +17,10 @@ DeepSeek Harness (DSH) Web 插件:把一轮里的 think(推理)和工具
|
|
|
16
17
|
- 主体:`max-height: N 行`(默认 10)的**内部滚动区**
|
|
17
18
|
- **think 与工具调用严格按真实输出顺序交错排列**(不做「think 全在上 / bash 全在下」的强行分区)
|
|
18
19
|
- 每段 think 一行(Think + 单行摘要 + 展开箭头),默认折叠,点击展开完整推理(内部限高滚动);展开文字与工具输出同字号(同一 mono 字体)
|
|
20
|
+
- **think 流式显示三种模式**(`liveStreamThink`,默认 `'out'`):
|
|
21
|
+
- `'out'`(默认):正在流式的 think 在 **slide 外**(原生位置)实时显示,流式结束后**收进 slide 折叠**成单行摘要
|
|
22
|
+
- `'in'`:流式 think 在 **slide 内**自动展开、正文实时滚动,结束后自动收起为单行摘要
|
|
23
|
+
- `'off'`:think 全程折叠为单行摘要,不做流式展开
|
|
19
24
|
- 每条工具调用一行(状态点 + 工具名 + 单行摘要),点击展开参数/输出(输出再限高一档,内部滚动)
|
|
20
25
|
- 执行中自动跟随底部
|
|
21
26
|
- **原生 Think 行从消息流中隐藏**(该段已并入 slide,避免重复显示),与 slide 内展示共用同一份内容
|
|
@@ -26,8 +31,16 @@ DeepSeek Harness (DSH) Web 插件:把一轮里的 think(推理)和工具
|
|
|
26
31
|
|
|
27
32
|
## 安装
|
|
28
33
|
|
|
34
|
+
从 npm 安装(跟 `latest`,即最近一次稳定发布):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
dsh plugin --profile web add dsh-newbe-response-window
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
或从合集仓库 GitHub 子目录安装(`alpha` 通道;换 `#rc` 装 rc 通道,换 `#<commit-sha>` 锁死某次发布):
|
|
41
|
+
|
|
29
42
|
```bash
|
|
30
|
-
dsh plugin --profile web add github:qiqiangvae/dsh-newbe-plugins#path:packages/dsh-newbe-response-window
|
|
43
|
+
dsh plugin --profile web add 'github:qiqiangvae/dsh-newbe-plugins#alpha&path:packages/dsh-newbe-response-window'
|
|
31
44
|
```
|
|
32
45
|
|
|
33
46
|
或本地 link 方式(开发调试):
|
|
@@ -51,24 +64,28 @@ dsh plugin --profile web add "link:$(pwd)/packages/dsh-newbe-response-window"
|
|
|
51
64
|
| 键 | 默认 | 说明 |
|
|
52
65
|
| --- | --- | --- |
|
|
53
66
|
| `lines` | `10` | 窗口高度(行)。`0` = 不限高(等价于关闭窗口) |
|
|
54
|
-
| `collapsed` | `false` |
|
|
67
|
+
| `collapsed` | `false` | slide 本身是否默认收起成一行 bar。默认 `false`:展开、内容可见(已完成轮次的外层折叠由 DSH 原生「已完成工作」那行控制) |
|
|
55
68
|
| `showReadOnly` | `true` | 是否在 slide 里列出 read/grep/web_search 等只读调用(默认全列出,不藏) |
|
|
56
69
|
| `minCollapseRows` | `3` | 仅 `collapsed: true` 时生效:少于该数量的轮次不收起 |
|
|
70
|
+
| `liveStreamThink` | `'out'` | 流式 think 的显示模式:`'out'` 盒外流式结束后进盒折叠 / `'in'` 盒内自动展开 / `'off'` 全程折叠 |
|
|
57
71
|
|
|
58
72
|
### 设置页(Settings → General)
|
|
59
73
|
|
|
60
|
-
插件在 Web UI 的 **Settings → General**
|
|
74
|
+
插件在 Web UI 的 **Settings → General** 里注册了两项:
|
|
61
75
|
|
|
62
|
-
-
|
|
63
|
-
-
|
|
76
|
+
- **「响应窗口大小(行数)」**:`−` / 数值输入 / `+` 调整 `lines`(0–200,`0` = 不限高,默认 10)
|
|
77
|
+
- **「流式思考显示」**:三选一(关闭 / 盒内 / 出盒),对应 `liveStreamThink`(默认出盒)
|
|
78
|
+
- 均**即时生效**:改动后已渲染的 slide 立刻变化(经宿主 settings namespace `dsh-newbe-response-window` 持久化)
|
|
64
79
|
|
|
65
80
|
## 实现说明(为什么安全)
|
|
66
81
|
|
|
67
82
|
- 工具调用 slide 通过 `conversation.chat.node`(`tool-call` key,`priority: -100`)的 **slot shadow** 在 React 层实现:每轮第一个 tool-call 节点渲染整个 slide,同轮其余 tool-call 节点渲染空,任何渲染异常由 slot 运行时的 entry boundary **自动 abdicate** 回内置渲染。
|
|
68
83
|
- **绝不移走 React 拥有的 `[data-chat-anchor-key]` 行节点**。实测:把行移进自定义容器后,一旦 DSH 后续移除该行(会话切换/编辑/压缩),React 会调用 `parent.removeChild(row)` 抛 `NotFoundError`,整个会话树被卸载——因此本插件只用「slot shadow + 类/CSS」两种方式,对 React 行结构零改动。
|
|
69
|
-
- 原生 Think 行隐藏与 slide 内的 think 同步:只对「该段内有 slide」的原生 `data-variant="think"` 行加 `display:none`(DOM 类/CSS,无重挂、无删除),其余(无工具调用的纯 Think 段)保持原生显示。
|
|
84
|
+
- 原生 Think 行隐藏与 slide 内的 think 同步:只对「该段内有 slide」的原生 `data-variant="think"` 行加 `display:none`(DOM 类/CSS,无重挂、无删除),其余(无工具调用的纯 Think 段)保持原生显示。0.1.7 把消息流行套进了 process-group 容器(`[data-chat-flow] > group root > content > 行`),行不再是 flow 的直接子节点,所以这段判定改成按文档顺序读全部 `[data-chat-flow-kind]` 行。(改动前 `flow.children` 只看到组外壳,永远匹配不到 Think,导致 slide 与原生 Think 双份显示。)
|
|
70
85
|
- 插件只读会话快照,不写快照、不调宿主 API。0.1.2-alpha 起快照从 slot 标准 prop **`useChat`** 读取(`{ locations.getTurn(turn), nodes.get(key) }` 形状),不再走 `useSession` 的 `chat` 字段;「幽灵行」判定通过 DOM 行上的 `data-chat-flow-key` 与该快照对号,不再嗅探 markdown CSS 类名(新版 CSS Module 类名已不含 `_markdown`)。
|
|
71
86
|
- 浏览器半依赖 `@deepseek-ai/dsh-client-store`(`createSnapshotStore`,shell 内置 seed module),替代旧版的 `@deepseek-ai/dsh-client-runtime`(该包在新 alpha 已移除)。旧版 `dsh.plugin.json` 清单在新 alpha 不再被读取,已删除。
|
|
87
|
+
- **设置持久化走 `configForms`**:0.1.7 移除了 `settingsScope` 服务,改为每个 profile 条目一个 controller(`ctx.configForms.get(条目 id)`,id 即 settings namespace)。宿主半的 `Config` 必须把用户可改字段标 `.volatile()`(settings 文档只投影 volatile 字段),并 `settings.configure({ auto: false }, ctx.fiber)` 退出自动生成的配置页。旧写法(`settingsScope` + `settings.register()`)会让 `inject` 永远等不到服务、整个插件静默不挂载——这正是 0.1.7 升级后 slide 全消失的原因。
|
|
88
|
+
- 宿主半因此要求 `@deepseek-ai/schemastery` **≥ 3.18.3**(`.volatile()` 的引入版本;随 DSH 0.1.7-alpha.1 发布)。
|
|
72
89
|
|
|
73
90
|
## 开发 / 测试
|
|
74
91
|
|
|
@@ -78,15 +95,18 @@ dsh plugin --profile web add "link:$(pwd)/packages/dsh-newbe-response-window"
|
|
|
78
95
|
npm run check # node --check lib/index.js && node --check lib/client.js
|
|
79
96
|
```
|
|
80
97
|
|
|
81
|
-
E2E(需要已启动的 `dsh web
|
|
98
|
+
E2E(需要已启动的 `dsh web`;用 uv 起 python playwright):
|
|
82
99
|
|
|
83
100
|
```bash
|
|
84
101
|
dsh --profile web --no-open --port 3639 &
|
|
85
|
-
|
|
102
|
+
uv run --with playwright playwright install chromium # 首次
|
|
103
|
+
uv run --with playwright python3 test/e2e.py --url http://127.0.0.1:3639 --session "修复 dsh 升级后的插件不兼容"
|
|
86
104
|
```
|
|
87
105
|
|
|
88
|
-
|
|
89
|
-
|
|
106
|
+
断言含:客户端半确实挂载(slide ≥ 1,服务缺失时这里会先失败)、窗口限高且内部真能滚动、think 单行且点击展开、有 slide 的段落里原生 Think 不重复显示、Settings → General 两行挂载并读到持久值、会话切换不崩。
|
|
107
|
+
|
|
108
|
+
- 宿主半:`lib/index.js`(声明 volatile 设置字段并退出自动配置页,依赖 `@deepseek-ai/schemastery` ≥ 3.18.3)
|
|
109
|
+
- 浏览器半:`lib/client.js`(`window.__ModuleLoader__.load`,require `react` 与 shell seed 的 `@deepseek-ai/dsh-client-store`,设置走 `configForms`)
|
|
90
110
|
|
|
91
111
|
## License
|
|
92
112
|
|
package/cordis.patch.yml
CHANGED
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
# minCollapseRows only auto-collapse a finished slide when it has at least
|
|
20
20
|
# this many tool calls (default 3; only used with
|
|
21
21
|
# collapsed=true).
|
|
22
|
+
# liveStreamThink how a streaming think block is shown (default 'out'):
|
|
23
|
+
# 'off' — always folded into a one-line summary
|
|
24
|
+
# 'in' — auto-expand inside the slide, live scroll
|
|
25
|
+
# 'out' — stream outside the slide (native position),
|
|
26
|
+
# then fold into the slide once settled
|
|
22
27
|
- insert:
|
|
23
28
|
- id: dsh-newbe-response-window
|
|
24
29
|
name: 'dsh-newbe-response-window'
|
|
@@ -27,3 +32,4 @@
|
|
|
27
32
|
collapsed: false
|
|
28
33
|
showReadOnly: true
|
|
29
34
|
minCollapseRows: 3
|
|
35
|
+
liveStreamThink: 'out'
|
package/lib/client.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
// dsh-newbe-response-window — browser half.
|
|
2
2
|
//
|
|
3
|
-
// Adapted for DSH 0.1.
|
|
4
|
-
// generation). Breaking changes vs. the
|
|
3
|
+
// Adapted for DSH 0.1.7-alpha.1 (the current client-module / slot-runtime
|
|
4
|
+
// generation). Breaking changes vs. the alpha this plugin last shipped for:
|
|
5
5
|
//
|
|
6
|
-
// -
|
|
6
|
+
// - The `settingsScope` client service is gone. Durable preferences now go
|
|
7
|
+
// through the `configForms` service, keyed by profile entry id, and the host
|
|
8
|
+
// half has to mark its user-editable fields `volatile()` and call
|
|
9
|
+
// `settings.configure({ auto: false }, ctx.fiber)`. Declaring the old
|
|
10
|
+
// service in `inject` parks the plugin forever ("waiting for service:
|
|
11
|
+
// settingsScope") — that, and only that, is what upgrading DSH broke.
|
|
12
|
+
// - `@deepseek-ai/dsh-client-runtime` is gone. The browser shell seeds
|
|
7
13
|
// `@deepseek-ai/dsh-client-store` (createSnapshotStore) as a static module,
|
|
8
14
|
// so the bundle requires that instead — no dsh.client.inject needed.
|
|
9
15
|
// - `conversation.chat.node` moved from dsh-client-ui-conversation to
|
|
@@ -61,17 +67,28 @@ window.__ModuleLoader__.load({
|
|
|
61
67
|
var createSnapshotStore = storeLib.createSnapshotStore
|
|
62
68
|
|
|
63
69
|
// ---- config -----------------------------------------------------------
|
|
70
|
+
// liveStreamThink mode: 'off' (always folded) | 'in' (auto-expand the row
|
|
71
|
+
// inside the slide while streaming) | 'out' (stream outside the slide in
|
|
72
|
+
// the native position, fold into the slide when settled).
|
|
64
73
|
var DEFAULTS = {
|
|
65
74
|
lines: 10,
|
|
66
75
|
collapsed: false,
|
|
67
76
|
showReadOnly: true,
|
|
68
77
|
minCollapseRows: 3,
|
|
78
|
+
liveStreamThink: 'out',
|
|
69
79
|
}
|
|
70
80
|
function clampInt(value, min, max, fallback) {
|
|
71
81
|
var n = Number(value)
|
|
72
82
|
if (!Number.isFinite(n)) return fallback
|
|
73
83
|
return Math.min(max, Math.max(min, Math.round(n)))
|
|
74
84
|
}
|
|
85
|
+
function normalizeLiveStreamThink(value, fallback) {
|
|
86
|
+
if (value === 'off' || value === 'in' || value === 'out') return value
|
|
87
|
+
// Legacy boolean row config: true meant in-box live streaming.
|
|
88
|
+
if (value === true) return 'in'
|
|
89
|
+
if (value === false) return 'off'
|
|
90
|
+
return fallback
|
|
91
|
+
}
|
|
75
92
|
function readConfig(cfg) {
|
|
76
93
|
cfg = cfg || {}
|
|
77
94
|
return {
|
|
@@ -79,19 +96,22 @@ window.__ModuleLoader__.load({
|
|
|
79
96
|
collapsed: cfg.collapsed === true,
|
|
80
97
|
showReadOnly: cfg.showReadOnly !== false,
|
|
81
98
|
minCollapseRows: clampInt(cfg.minCollapseRows, 1, 50, DEFAULTS.minCollapseRows),
|
|
99
|
+
liveStreamThink: normalizeLiveStreamThink(cfg.liveStreamThink, DEFAULTS.liveStreamThink),
|
|
82
100
|
}
|
|
83
101
|
}
|
|
84
102
|
|
|
85
103
|
// ---- live settings state ----------------------------------------------
|
|
86
|
-
// `lines`
|
|
104
|
+
// `lines` and `liveStreamThink` are user-adjustable from Settings; the
|
|
105
|
+
// rest come from row config.
|
|
87
106
|
var linesStore = null
|
|
107
|
+
var liveStreamThinkStore = null
|
|
88
108
|
var settingsScope = null
|
|
89
109
|
var NS = 'dsh-newbe-response-window'
|
|
90
110
|
function clampLines(value) { return clampInt(value, 0, 200, DEFAULTS.lines) }
|
|
91
111
|
|
|
92
112
|
/** Minimal observable-value hook over a SnapshotStore (plain React). */
|
|
93
|
-
function useStoreValue(store) {
|
|
94
|
-
var pair = React.useState(function () { return store === null ?
|
|
113
|
+
function useStoreValue(store, fallback) {
|
|
114
|
+
var pair = React.useState(function () { return store === null ? fallback : store.getSnapshot() })
|
|
95
115
|
var value = pair[0]
|
|
96
116
|
var setValue = pair[1]
|
|
97
117
|
React.useEffect(function () {
|
|
@@ -105,7 +125,7 @@ window.__ModuleLoader__.load({
|
|
|
105
125
|
|
|
106
126
|
/** Live window size in lines (follows the Settings store). */
|
|
107
127
|
function useLiveLines() {
|
|
108
|
-
return useStoreValue(linesStore)
|
|
128
|
+
return useStoreValue(linesStore, DEFAULTS.lines)
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
// ---- shared style injection ------------------------------------------
|
|
@@ -195,7 +215,7 @@ window.__ModuleLoader__.load({
|
|
|
195
215
|
'.drw-think-name { color: var(--dsw-alias-label-secondary, #bbb); font-size: 11px; font-weight: 600; letter-spacing: 0.04em; flex: none; }',
|
|
196
216
|
'.drw-think-summary { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-tertiary, #999); font-size: 12.5px; flex: 1 1 auto; }',
|
|
197
217
|
'.drw-think-toggle { color: var(--dsw-alias-label-tertiary, #888); font-size: 10px; flex: none; }',
|
|
198
|
-
'.drw-think-body { margin: 0; padding: 6px 8px; color: var(--dsw-alias-label-primary, #eee); font: var(--dsw-font-markdown-code-block-small, 12px/1.5 ui-monospace, monospace); white-space: pre-wrap; word-break: break-word; max-height:
|
|
218
|
+
'.drw-think-body { margin: 0; padding: 6px 8px; color: var(--dsw-alias-label-primary, #eee); font: var(--dsw-font-markdown-code-block-small, 12px/1.5 ui-monospace, monospace); white-space: pre-wrap; word-break: break-word; max-height: 96px; overflow-y: auto; }',
|
|
199
219
|
/* Hide the native Think row once it's inside a slide (DOM + CSS only) */
|
|
200
220
|
'[data-variant="think"][data-drw-hidethink="1"] { display: none !important; }',
|
|
201
221
|
/* Settings row: response window size */
|
|
@@ -213,6 +233,10 @@ window.__ModuleLoader__.load({
|
|
|
213
233
|
'}',
|
|
214
234
|
'.drw-set-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,0.16)); }',
|
|
215
235
|
'.drw-set-btn:disabled { opacity: 0.4; cursor: default; }',
|
|
236
|
+
'.drw-set-btn.drw-set-on {',
|
|
237
|
+
' border-color: var(--dsw-alias-state-business-primary, var(--dsw-alias-brand, #3dbbf5));',
|
|
238
|
+
' color: var(--dsw-alias-state-business-primary, var(--dsw-alias-brand, #3dbbf5));',
|
|
239
|
+
'}',
|
|
216
240
|
'.drw-set-input {',
|
|
217
241
|
' width: 56px; height: 28px; text-align: center;',
|
|
218
242
|
' border: 1px solid var(--dsw-alias-border-l2, var(--dsw-alias-border-strong, rgba(128,128,128,0.35)));',
|
|
@@ -343,16 +367,36 @@ window.__ModuleLoader__.load({
|
|
|
343
367
|
// ---- one think (reasoning) row inside the slide -------------------------
|
|
344
368
|
// A compact single-line row, collapsible to the full reasoning text — the
|
|
345
369
|
// same look and feel as a tool-call row (think and tool calls are both
|
|
346
|
-
// "implementation" and should read the same inside the window).
|
|
370
|
+
// "implementation" and should read the same inside the window). While the
|
|
371
|
+
// block is still streaming the row auto-expands and the body pins to the
|
|
372
|
+
// latest line; when it settles the row folds back to its one-line summary.
|
|
373
|
+
// The head stays a static first-line summary the whole time, so the only
|
|
374
|
+
// moving part during a stream is the expanding body — one visual focus.
|
|
347
375
|
function ThinkRow(props) {
|
|
348
376
|
var text = props.text
|
|
349
377
|
var index = props.index
|
|
378
|
+
var streaming = props.streaming === true
|
|
350
379
|
var openState = React.useState(false)
|
|
351
380
|
var open = openState[0]
|
|
352
381
|
var setOpen = openState[1]
|
|
382
|
+
var bodyRef = React.useRef(null)
|
|
353
383
|
var firstLine = text.split('\n')[0].trim() || text.trim()
|
|
354
384
|
var summary = firstLine.length > 120 ? firstLine.slice(0, 117) + '…' : firstLine
|
|
355
|
-
|
|
385
|
+
// Live streaming: auto-expand while streaming, fold back once settled.
|
|
386
|
+
React.useEffect(function () {
|
|
387
|
+
if (streaming) setOpen(true)
|
|
388
|
+
else setOpen(false)
|
|
389
|
+
}, [streaming])
|
|
390
|
+
// While streaming, keep the body pinned to the latest line.
|
|
391
|
+
React.useEffect(function () {
|
|
392
|
+
if (!open || !bodyRef.current) return
|
|
393
|
+
if (streaming) bodyRef.current.scrollTop = bodyRef.current.scrollHeight
|
|
394
|
+
})
|
|
395
|
+
return React.createElement('div', {
|
|
396
|
+
className: 'drw-think',
|
|
397
|
+
'data-open': open ? '1' : undefined,
|
|
398
|
+
'data-streaming': streaming ? '1' : undefined,
|
|
399
|
+
},
|
|
356
400
|
React.createElement('div', {
|
|
357
401
|
className: 'drw-think-head', role: 'button', tabIndex: 0,
|
|
358
402
|
'aria-expanded': open || undefined,
|
|
@@ -364,7 +408,7 @@ window.__ModuleLoader__.load({
|
|
|
364
408
|
React.createElement('span', { className: 'drw-think-toggle', 'aria-hidden': true }, open ? '\u25BE' : '\u25B8'),
|
|
365
409
|
),
|
|
366
410
|
open
|
|
367
|
-
? React.createElement('pre', { className: 'drw-think-body' }, text)
|
|
411
|
+
? React.createElement('pre', { ref: bodyRef, className: 'drw-think-body' }, text)
|
|
368
412
|
: null,
|
|
369
413
|
)
|
|
370
414
|
}
|
|
@@ -382,6 +426,7 @@ window.__ModuleLoader__.load({
|
|
|
382
426
|
var openFile = props.openFile
|
|
383
427
|
var inspectCall = props.inspectCall
|
|
384
428
|
var cfg = props.config
|
|
429
|
+
var streamingThink = props.streamingThink || null
|
|
385
430
|
var lines = useLiveLines() // live window size from Settings
|
|
386
431
|
var toolItems = items.filter(function (it) { return it.kind === 'tool' })
|
|
387
432
|
var reasoningCount = items.reduce(function (acc, it) { return it.kind === 'think' ? acc + 1 : acc }, 0)
|
|
@@ -393,10 +438,27 @@ window.__ModuleLoader__.load({
|
|
|
393
438
|
var setOpen = openState[1]
|
|
394
439
|
var bodyRef = React.useRef(null)
|
|
395
440
|
var running = stats.running > 0
|
|
396
|
-
//
|
|
441
|
+
// Streaming detection depends on the mode:
|
|
442
|
+
// 'in' — a think block inside the box is still streaming (the row
|
|
443
|
+
// auto-expands and the slide is forced open so the live
|
|
444
|
+
// reasoning stays readable);
|
|
445
|
+
// 'out' — the live tail is streaming OUTSIDE the slide (native row)
|
|
446
|
+
// and folds in when settled; the slide is forced open so the
|
|
447
|
+
// fold-in is visible.
|
|
448
|
+
// In both cases streaming overrides `collapsed`, then the user's
|
|
449
|
+
// collapsed setting is restored (Q7).
|
|
450
|
+
var mode = cfg.liveStreamThink
|
|
451
|
+
var streaming = mode === 'in'
|
|
452
|
+
? items.some(function (it) { return it.kind === 'think' && it.streaming === true })
|
|
453
|
+
: mode === 'out'
|
|
454
|
+
? streamingThink !== null
|
|
455
|
+
: false
|
|
456
|
+
var effectiveOpen = open || streaming
|
|
457
|
+
// Auto-follow to the bottom while calls are still running or a think is
|
|
458
|
+
// still streaming.
|
|
397
459
|
React.useEffect(function () {
|
|
398
|
-
if (!
|
|
399
|
-
if (running) bodyRef.current.scrollTop = bodyRef.current.scrollHeight
|
|
460
|
+
if (!effectiveOpen || !bodyRef.current) return
|
|
461
|
+
if (running || streaming) bodyRef.current.scrollTop = bodyRef.current.scrollHeight
|
|
400
462
|
})
|
|
401
463
|
var list = items
|
|
402
464
|
if (!cfg.showReadOnly) {
|
|
@@ -405,10 +467,10 @@ window.__ModuleLoader__.load({
|
|
|
405
467
|
})
|
|
406
468
|
}
|
|
407
469
|
var thinkSeen = 0
|
|
408
|
-
return React.createElement('div', { className: 'drw-slide', 'data-running': running ? '1' : undefined },
|
|
470
|
+
return React.createElement('div', { className: 'drw-slide', 'data-running': running || streaming ? '1' : undefined },
|
|
409
471
|
React.createElement('div', {
|
|
410
472
|
className: 'drw-head', role: 'button', tabIndex: 0,
|
|
411
|
-
'aria-expanded':
|
|
473
|
+
'aria-expanded': effectiveOpen || undefined,
|
|
412
474
|
onClick: function () { setOpen(!open) },
|
|
413
475
|
onKeyDown: function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setOpen(!open) } },
|
|
414
476
|
},
|
|
@@ -416,6 +478,9 @@ window.__ModuleLoader__.load({
|
|
|
416
478
|
stats.total + ' 个工具调用'
|
|
417
479
|
+ (reasoningCount > 0 ? ' · ' + reasoningCount + ' Think' : '')
|
|
418
480
|
),
|
|
481
|
+
streaming
|
|
482
|
+
? React.createElement('span', { className: 'drw-head-badge', 'data-state': 'running' }, '思考中')
|
|
483
|
+
: null,
|
|
419
484
|
running
|
|
420
485
|
? React.createElement('span', { className: 'drw-head-badge', 'data-state': 'running' }, '进行中')
|
|
421
486
|
: null,
|
|
@@ -425,18 +490,23 @@ window.__ModuleLoader__.load({
|
|
|
425
490
|
!cfg.showReadOnly && stats.readOnly > 0
|
|
426
491
|
? React.createElement('span', { className: 'drw-head-badge' }, '隐藏只读 ' + stats.readOnly)
|
|
427
492
|
: null,
|
|
428
|
-
React.createElement('span', { className: 'drw-head-toggle' },
|
|
493
|
+
React.createElement('span', { className: 'drw-head-toggle' }, effectiveOpen ? '收起 ▴' : '展开 ▾'),
|
|
429
494
|
),
|
|
430
495
|
React.createElement('div', {
|
|
431
496
|
ref: bodyRef,
|
|
432
|
-
className: 'drw-body' + (
|
|
433
|
-
style:
|
|
497
|
+
className: 'drw-body' + (effectiveOpen ? '' : ' drw-collapsed'),
|
|
498
|
+
style: effectiveOpen && lines > 0 ? { maxHeight: (lines * 1.55) + 'em' } : undefined,
|
|
434
499
|
},
|
|
435
500
|
list.map(function (it) {
|
|
436
501
|
if (it.kind === 'think') {
|
|
437
502
|
var tid = thinkSeen
|
|
438
503
|
thinkSeen += 1
|
|
439
|
-
return React.createElement(ThinkRow, {
|
|
504
|
+
return React.createElement(ThinkRow, {
|
|
505
|
+
key: 'think-' + tid,
|
|
506
|
+
text: it.text,
|
|
507
|
+
index: tid,
|
|
508
|
+
streaming: it.streaming === true,
|
|
509
|
+
})
|
|
440
510
|
}
|
|
441
511
|
return React.createElement(SimpleToolRow, {
|
|
442
512
|
key: it.node.key,
|
|
@@ -469,12 +539,13 @@ window.__ModuleLoader__.load({
|
|
|
469
539
|
var current = null
|
|
470
540
|
function ensure() {
|
|
471
541
|
if (current === null) {
|
|
472
|
-
current = { items: [], tools: [], startKey: null }
|
|
542
|
+
current = { items: [], tools: [], startKey: null, streamingThink: null }
|
|
473
543
|
segments.push(current)
|
|
474
544
|
}
|
|
475
545
|
return current
|
|
476
546
|
}
|
|
477
547
|
function closeSegment() { current = null }
|
|
548
|
+
var mode = currentConfig.liveStreamThink
|
|
478
549
|
for (var i = 0; i < order.length; i++) {
|
|
479
550
|
var n = chat.nodes.get(order[i])
|
|
480
551
|
if (n === undefined) continue
|
|
@@ -482,13 +553,29 @@ window.__ModuleLoader__.load({
|
|
|
482
553
|
if (n.kind === 'assistant-step') {
|
|
483
554
|
var blocks = n.data && Array.isArray(n.data.blocks) ? n.data.blocks : []
|
|
484
555
|
var staged = hasVisibleTextBlock(blocks)
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
556
|
+
// While a step is running, its LAST reasoning block is the live
|
|
557
|
+
// streaming tail — the exact rule the native ReasoningRow uses
|
|
558
|
+
// (`running: streaming && i === last`).
|
|
559
|
+
// mode 'out': the tail streams OUTSIDE the slide (native row) and
|
|
560
|
+
// folds in once settled — recorded as streamingThink.
|
|
561
|
+
// mode 'in' : the tail stays INSIDE the box, tagged `streaming` so
|
|
562
|
+
// the row auto-expands and shows live progress.
|
|
563
|
+
// mode 'off': no streaming behavior, always folded.
|
|
564
|
+
var stepRunning = !!n.data && n.data.status === 'running'
|
|
565
|
+
var lastIdx = blocks.length - 1
|
|
488
566
|
for (var b = 0; b < blocks.length; b++) {
|
|
489
567
|
var block = blocks[b]
|
|
490
568
|
if (block && block.kind === 'reasoning' && typeof block.text === 'string' && block.text.trim() !== '') {
|
|
491
|
-
|
|
569
|
+
if (mode === 'out' && stepRunning && b === lastIdx) {
|
|
570
|
+
ensure().streamingThink = { text: block.text, nodeKey: n.key }
|
|
571
|
+
} else {
|
|
572
|
+
ensure().items.push({
|
|
573
|
+
kind: 'think',
|
|
574
|
+
text: block.text,
|
|
575
|
+
nodeKey: n.key,
|
|
576
|
+
streaming: mode === 'in' && stepRunning && b === lastIdx,
|
|
577
|
+
})
|
|
578
|
+
}
|
|
492
579
|
}
|
|
493
580
|
}
|
|
494
581
|
if (staged) closeSegment()
|
|
@@ -554,6 +641,7 @@ window.__ModuleLoader__.load({
|
|
|
554
641
|
cwd: cwd,
|
|
555
642
|
openFile: openFile,
|
|
556
643
|
inspectCall: inspectCall,
|
|
644
|
+
streamingThink: seg.streamingThink || null,
|
|
557
645
|
config: currentConfig,
|
|
558
646
|
})
|
|
559
647
|
} catch (e) {
|
|
@@ -624,10 +712,15 @@ window.__ModuleLoader__.load({
|
|
|
624
712
|
// reasoning does not appear both inside the slide and inline in the flow.
|
|
625
713
|
// Only DOM class toggling — never reparents React-owned rows.
|
|
626
714
|
function hideNativeThink(root, textIndex) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
715
|
+
// 0.1.7-alpha.1 nests flow rows inside process-group containers
|
|
716
|
+
// (`[data-chat-flow] > group root > content > rows`) instead of keeping
|
|
717
|
+
// every row a direct child of one flow element, so walking
|
|
718
|
+
// `flow.children` only ever saw the group wrappers and never matched a
|
|
719
|
+
// Think against the slide that sits beside it in the same group. Read the
|
|
720
|
+
// flow rows in document order instead — that is still reading order —
|
|
721
|
+
// while keeping the segment-boundary walk unchanged.
|
|
722
|
+
var rows = root.querySelectorAll('[data-chat-flow-kind]')
|
|
723
|
+
if (rows.length === 0) return
|
|
631
724
|
// Segment boundary in the DOM: a user/steering row, a turn-level row
|
|
632
725
|
// (turn-process / turn-tail), or an assistant-step row that carries
|
|
633
726
|
// visible staged text (a response shown to the user mid-turn).
|
|
@@ -658,7 +751,15 @@ window.__ModuleLoader__.load({
|
|
|
658
751
|
if (isSegmentBoundary(next)) break
|
|
659
752
|
if (next.querySelector && next.querySelector('.drw-slide')) { hasSlide = true; break }
|
|
660
753
|
}
|
|
661
|
-
if (hasSlide)
|
|
754
|
+
if (hasSlide) {
|
|
755
|
+
// In 'out' mode the streaming think tail lives OUTSIDE the box
|
|
756
|
+
// (native row) until it settles: keep it visible while
|
|
757
|
+
// `data-streaming` is present, then fold it in (hide) the moment the
|
|
758
|
+
// stream is gone. In 'in'/'off' modes the native row is always
|
|
759
|
+
// hidden — the box renders the same content.
|
|
760
|
+
var streamingOut = currentConfig.liveStreamThink === 'out' && !!think.closest('[data-streaming]')
|
|
761
|
+
if (!streamingOut) think.dataset.drwHidethink = '1'
|
|
762
|
+
}
|
|
662
763
|
else if (think.dataset.drwHidethink) delete think.dataset.drwHidethink
|
|
663
764
|
}
|
|
664
765
|
}
|
|
@@ -711,34 +812,64 @@ window.__ModuleLoader__.load({
|
|
|
711
812
|
}
|
|
712
813
|
|
|
713
814
|
// ---- Settings row: response window size ---------------------------------
|
|
815
|
+
var STREAM_MODES = [
|
|
816
|
+
{ value: 'off', label: '关闭' },
|
|
817
|
+
{ value: 'in', label: '盒内' },
|
|
818
|
+
{ value: 'out', label: '出盒' },
|
|
819
|
+
]
|
|
714
820
|
function ResponseWindowSettingsRow(props) {
|
|
715
821
|
var useLines = props.useLines
|
|
716
822
|
var setLines = props.setLines
|
|
823
|
+
var useLiveStreamThink = props.useLiveStreamThink
|
|
824
|
+
var setLiveStreamThink = props.setLiveStreamThink
|
|
717
825
|
var lines = (typeof useLines === 'function' ? useLines(function (s) { return s }) : null) ?? currentConfig.lines
|
|
718
826
|
var value = clampLines(lines)
|
|
719
827
|
var step = function (delta) {
|
|
720
828
|
setLines(value + delta)
|
|
721
829
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
830
|
+
var liveStreamThink = (typeof useLiveStreamThink === 'function' ? useLiveStreamThink(function (s) { return s }) : null) ?? currentConfig.liveStreamThink
|
|
831
|
+
var pickMode = function (mode) {
|
|
832
|
+
if (typeof setLiveStreamThink === 'function') setLiveStreamThink(mode)
|
|
833
|
+
}
|
|
834
|
+
return React.createElement(React.Fragment, null,
|
|
835
|
+
React.createElement('div', { className: 'drw-set-row' },
|
|
836
|
+
React.createElement('div', { className: 'drw-set-text' },
|
|
837
|
+
React.createElement('div', { className: 'drw-set-title' }, '响应窗口大小(行数)'),
|
|
838
|
+
React.createElement('div', { className: 'drw-set-desc' }, '每个响应 slide 的限高滚动窗口。默认 10 行,0 = 不限高,改动即时生效。'),
|
|
839
|
+
),
|
|
840
|
+
React.createElement('div', { className: 'drw-set-control' },
|
|
841
|
+
React.createElement('button', {
|
|
842
|
+
type: 'button', className: 'drw-set-btn', 'aria-label': '减小窗口',
|
|
843
|
+
disabled: value <= 0,
|
|
844
|
+
onClick: function () { step(-1) },
|
|
845
|
+
}, '−'),
|
|
846
|
+
React.createElement('input', {
|
|
847
|
+
type: 'number', className: 'drw-set-input', min: 0, max: 200, step: 1, value: String(value),
|
|
848
|
+
onChange: function (e) { setLines(e.target.value) },
|
|
849
|
+
}),
|
|
850
|
+
React.createElement('button', {
|
|
851
|
+
type: 'button', className: 'drw-set-btn', 'aria-label': '增大窗口',
|
|
852
|
+
disabled: value >= 200,
|
|
853
|
+
onClick: function () { step(1) },
|
|
854
|
+
}, '+'),
|
|
855
|
+
),
|
|
726
856
|
),
|
|
727
|
-
React.createElement('div', { className: 'drw-set-
|
|
728
|
-
React.createElement('
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
857
|
+
React.createElement('div', { className: 'drw-set-row' },
|
|
858
|
+
React.createElement('div', { className: 'drw-set-text' },
|
|
859
|
+
React.createElement('div', { className: 'drw-set-title' }, '流式思考显示'),
|
|
860
|
+
React.createElement('div', { className: 'drw-set-desc' }, '出盒:思考在 slide 外实时显示,结束后收进 slide 折叠;盒内:在 slide 内自动展开实时滚动;关闭:think 全程保持折叠。'),
|
|
861
|
+
),
|
|
862
|
+
React.createElement('div', { className: 'drw-set-control' },
|
|
863
|
+
STREAM_MODES.map(function (m) {
|
|
864
|
+
return React.createElement('button', {
|
|
865
|
+
key: m.value,
|
|
866
|
+
type: 'button',
|
|
867
|
+
className: 'drw-set-btn' + (liveStreamThink === m.value ? ' drw-set-on' : ''),
|
|
868
|
+
'aria-pressed': liveStreamThink === m.value,
|
|
869
|
+
onClick: function () { pickMode(m.value) },
|
|
870
|
+
}, m.label)
|
|
871
|
+
}),
|
|
872
|
+
),
|
|
742
873
|
),
|
|
743
874
|
)
|
|
744
875
|
}
|
|
@@ -746,19 +877,26 @@ window.__ModuleLoader__.load({
|
|
|
746
877
|
// ---- plugin entry -------------------------------------------------------
|
|
747
878
|
var currentConfig = readConfig(null)
|
|
748
879
|
|
|
749
|
-
|
|
880
|
+
// 0.1.7-alpha.1 replaced the `settingsScope` service with `configForms`:
|
|
881
|
+
// one controller per profile entry id, exposing getSnapshot/subscribe/set
|
|
882
|
+
// over the host settings document. Same shape we already used, so the bind
|
|
883
|
+
// is a rename — but the service is a hard inject dependency, and a missing
|
|
884
|
+
// one silently parks the whole plugin (no slides at all).
|
|
885
|
+
var INJECT = ['slots', 'configForms']
|
|
750
886
|
function apply(ctx, config) {
|
|
751
887
|
currentConfig = readConfig(config)
|
|
752
888
|
injectStyles()
|
|
753
889
|
|
|
754
|
-
// Durable settings binding: the host half
|
|
755
|
-
//
|
|
890
|
+
// Durable settings binding: the host half declares the volatile fields of
|
|
891
|
+
// the `dsh-newbe-response-window` entry; this controller mirrors them and
|
|
892
|
+
// persists user overrides.
|
|
756
893
|
linesStore = createSnapshotStore(currentConfig.lines)
|
|
894
|
+
liveStreamThinkStore = createSnapshotStore(currentConfig.liveStreamThink)
|
|
757
895
|
var scopeService = null
|
|
758
|
-
try { scopeService = ctx.get('
|
|
759
|
-
if (scopeService && typeof scopeService.
|
|
896
|
+
try { scopeService = ctx.get('configForms') } catch (e) { scopeService = null }
|
|
897
|
+
if (scopeService && typeof scopeService.get === 'function') {
|
|
760
898
|
try {
|
|
761
|
-
settingsScope = scopeService.
|
|
899
|
+
settingsScope = scopeService.get(NS)
|
|
762
900
|
} catch (e) {
|
|
763
901
|
settingsScope = null
|
|
764
902
|
}
|
|
@@ -772,6 +910,15 @@ window.__ModuleLoader__.load({
|
|
|
772
910
|
var n = clampLines(value.lines)
|
|
773
911
|
if (linesStore !== null && linesStore.getSnapshot() !== n) linesStore.set(n)
|
|
774
912
|
}
|
|
913
|
+
// liveStreamThink is stored as 'off' | 'in' | 'out'; tolerate
|
|
914
|
+
// legacy boolean rows (true → 'in', false → 'off').
|
|
915
|
+
if (value && value.liveStreamThink !== undefined) {
|
|
916
|
+
var v = normalizeLiveStreamThink(value.liveStreamThink, currentConfig.liveStreamThink)
|
|
917
|
+
if (liveStreamThinkStore !== null && liveStreamThinkStore.getSnapshot() !== v) {
|
|
918
|
+
liveStreamThinkStore.set(v)
|
|
919
|
+
currentConfig.liveStreamThink = v
|
|
920
|
+
}
|
|
921
|
+
}
|
|
775
922
|
} catch (e) {}
|
|
776
923
|
}
|
|
777
924
|
settingsScope.subscribe(adopt)
|
|
@@ -796,7 +943,8 @@ window.__ModuleLoader__.load({
|
|
|
796
943
|
}, TextWindowDock)
|
|
797
944
|
})
|
|
798
945
|
|
|
799
|
-
// Settings → General
|
|
946
|
+
// Settings → General rows (window size + live-stream think, both live
|
|
947
|
+
// and persisted).
|
|
800
948
|
ctx.slots.inject('settings.general.item', function () {
|
|
801
949
|
return ctx.slots.register({
|
|
802
950
|
name: 'settings.general.item',
|
|
@@ -804,7 +952,7 @@ window.__ModuleLoader__.load({
|
|
|
804
952
|
order: 60,
|
|
805
953
|
inject: function () {
|
|
806
954
|
return {
|
|
807
|
-
hooks: { lines: linesStore },
|
|
955
|
+
hooks: { lines: linesStore, liveStreamThink: liveStreamThinkStore },
|
|
808
956
|
setLines: function (value) {
|
|
809
957
|
var n = clampLines(value)
|
|
810
958
|
if (linesStore !== null) linesStore.set(n)
|
|
@@ -812,6 +960,14 @@ window.__ModuleLoader__.load({
|
|
|
812
960
|
settingsScope.set('lines', n).catch(function () {})
|
|
813
961
|
}
|
|
814
962
|
},
|
|
963
|
+
setLiveStreamThink: function (value) {
|
|
964
|
+
var v = normalizeLiveStreamThink(value, currentConfig.liveStreamThink)
|
|
965
|
+
if (liveStreamThinkStore !== null) liveStreamThinkStore.set(v)
|
|
966
|
+
currentConfig.liveStreamThink = v
|
|
967
|
+
if (settingsScope !== null) {
|
|
968
|
+
settingsScope.set('liveStreamThink', v).catch(function () {})
|
|
969
|
+
}
|
|
970
|
+
},
|
|
815
971
|
}
|
|
816
972
|
},
|
|
817
973
|
}, ResponseWindowSettingsRow)
|
package/lib/index.js
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
// dsh-newbe-response-window — host half.
|
|
2
2
|
//
|
|
3
|
-
// Verified against DSH 0.1.
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// "./client").
|
|
3
|
+
// Verified against DSH 0.1.7-alpha.1. Owns the durable settings namespace so the
|
|
4
|
+
// browser Settings UI can persist the window size etc. in the user settings
|
|
5
|
+
// document. The heavy lifting lives in the browser half (exports "./client").
|
|
7
6
|
//
|
|
8
|
-
//
|
|
7
|
+
// The host settings contract is the 0.1.7 one: the settings document is keyed
|
|
8
|
+
// by the profile entry id, a namespace only shows up in it when its Config
|
|
9
|
+
// schema projects at least one `volatile()` field, and a plugin opts out of the
|
|
10
|
+
// auto-generated page with `settings.configure({ auto: false }, ctx.fiber)`.
|
|
11
|
+
//
|
|
12
|
+
// Namespace + schema (browser `configForms.get(SETTINGS_NS)` mirrors it):
|
|
9
13
|
// lines window height in lines (default 10; 0 = uncapped)
|
|
10
14
|
// collapsed start slides collapsed instead of always-expanded
|
|
11
15
|
// showReadOnly include read-only tools in the slide list
|
|
12
16
|
// minCollapseRows min tool-call count for auto-collapse (collapsed mode)
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
+
// liveStreamThink how a streaming think block is shown:
|
|
18
|
+
// 'off' — always folded into a one-line summary
|
|
19
|
+
// 'in' — auto-expand inside the slide, live scroll
|
|
20
|
+
// 'out' — stream outside the slide (native row), then
|
|
21
|
+
// fold into the slide once settled (default 'out')
|
|
22
|
+
|
|
23
|
+
// The namespace must equal the profile entry id (the `id:` of the row this
|
|
24
|
+
// bundle patch inserts), because that is what the host settings document and
|
|
25
|
+
// `configForms.get()` are keyed by.
|
|
17
26
|
|
|
18
27
|
import z from '@deepseek-ai/schemastery'
|
|
19
28
|
|
|
@@ -23,20 +32,28 @@ export const name = 'dsh-newbe-response-window'
|
|
|
23
32
|
/** Durable settings namespace for this plugin (lowercase kebab-case). */
|
|
24
33
|
export const SETTINGS_NS = 'dsh-newbe-response-window'
|
|
25
34
|
|
|
26
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Settings schema. Only the two user-editable preferences are `volatile()`:
|
|
37
|
+
* the browser settings document is projected from volatile fields alone, and
|
|
38
|
+
* the rest stay patch-time configuration that the client reads from row config.
|
|
39
|
+
*/
|
|
27
40
|
export const Config = z.object({
|
|
28
|
-
lines: z.natural().min(0).max(200).default(10),
|
|
41
|
+
lines: z.natural().min(0).max(200).default(10).volatile(),
|
|
29
42
|
collapsed: z.boolean().default(false),
|
|
30
43
|
showReadOnly: z.boolean().default(true),
|
|
31
44
|
minCollapseRows: z.natural().min(1).max(50).default(3),
|
|
45
|
+
liveStreamThink: z.string().default('out').volatile(),
|
|
32
46
|
})
|
|
33
47
|
|
|
34
48
|
/**
|
|
35
|
-
*
|
|
49
|
+
* Opt this plugin's entry out of the auto-generated settings page. Its
|
|
50
|
+
* namespace is the profile entry id (`dsh-newbe-response-window`), which is
|
|
51
|
+
* exactly {@link SETTINGS_NS}, so the browser half can bind the durable scope
|
|
52
|
+
* through `configForms.get(SETTINGS_NS)`.
|
|
36
53
|
* @param ctx - host plugin context.
|
|
37
54
|
*/
|
|
38
55
|
export function apply(ctx) {
|
|
39
56
|
ctx.inject(['settings'], (settingsCtx) => {
|
|
40
|
-
settingsCtx.settings.
|
|
57
|
+
settingsCtx.effect(() => settingsCtx.settings.configure({ auto: false }, ctx.fiber))
|
|
41
58
|
})
|
|
42
59
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-newbe-response-window",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DeepSeek Harness (DSH) web plugin: per-turn response window. Each turn's tool calls collapse into ONE always-expanded bounded slide (configurable N-line window, inner scroll, nothing hidden), and long assistant markdown gets a bounded scroll window too — Grok-build style, keeps every intermediate response visible inside a finite window.",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "DeepSeek Harness (DSH) web plugin: per-turn response window. Each turn's tool calls collapse into ONE always-expanded bounded slide (configurable N-line window, inner scroll, nothing hidden), and long assistant markdown gets a bounded scroll window too — Grok-build style, keeps every intermediate response visible inside a finite window. Streaming think blocks can live-stream outside the slide and fold in when settled (or auto-expand inside), switchable via Settings.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"check": "node --check lib/index.js && node --check lib/client.js"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@deepseek-ai/schemastery": "^3.
|
|
31
|
+
"@deepseek-ai/schemastery": "^3.18.3"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"react": "^18.0.0 || ^19.0.0"
|