dsh-toolfold 0.1.7 → 0.1.8

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 CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  所有显著变更将记录于此。格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [0.1.8] - 2026-08-28
6
+
7
+ ### Fixed
8
+ - 修复运行时收起"先动一下再突然收起":当模型产生大量工具调用、且它们是最新信息时,run 在收起动画期间仍在增长。旧实现(自 0.1.3 起)假定行集合固定:折叠条点击闭包持有的是创建时的旧 run 快照,动画只覆盖原始行,随后按旧行数计时的完成定时器把整个增长后的 run 一次性合并,新流入的行瞬间消失。现点击改为操作当前 run(`bar._ccxRun`)而非创建时快照;`syncBars` 将动画期间新增的行折入同一瀑布(`extendCollapse`:rise 类 + 高度收缩 + 各自 stagger)并重排完成定时器(`armCollapseFinish`),最后一行动画结束才应用合并。(`lib/client.js`, `lib/dynamic-body.js`)
9
+
10
+ ### Changed
11
+ - npm 发布工作流增加 OIDC 权限(`id-token: write`、`contents: read`),发布改用 OIDC 身份认证(移除 `NODE_AUTH_TOKEN` 环境变量),并禁用 `package-manager-cache`。
12
+ - `package.json` 增加 `repository` 字段指向 GitHub 仓库。
13
+ - 重构 README:重组章节(安装、快速开始、默认行为、功能、设置、性能、限制),新增"默认行为 – 首次使用"说明(为什么思考会消失、为什么调用默认分组,以及快速修复指引),补充安装方法与徽章,并同步翻译为 `README.en.md`。
14
+
5
15
  ## [0.1.7] - 2026-08-26
6
16
 
7
17
  ### Fixed
@@ -54,6 +64,7 @@
54
64
  - 首个可用版本:连续 `tool-call` 折叠为最后一条的单行摘要 + “已折叠 N 个工具调用”,支持 `durMs / keepThink / splitThink / stats` 四项设置(DSH `settings` 服务 → `~/.dsh/settings.yaml`,回退到 Host 路由与 `localStorage`)与瀑布动画。
55
65
  - `toolfold` 远端包与一键安装、设置双半通信(Host `GET/POST /api/dsh-toolfold/settings` + Client 桥)。
56
66
 
67
+ [0.1.8]: https://github.com/Minecraftbe/dsh-toolfold/compare/v0.1.7...v0.1.8
57
68
  [0.1.7]: https://github.com/Minecraftbe/dsh-toolfold/compare/v0.1.6...v0.1.7
58
69
  [0.1.6]: https://github.com/Minecraftbe/dsh-toolfold/compare/v0.1.5...v0.1.6
59
70
  [0.1.5]: https://github.com/Minecraftbe/dsh-toolfold/compare/v0.1.4...v0.1.5
package/README.en.md CHANGED
@@ -1,136 +1,174 @@
1
1
  # dsh-toolfold · Tool Call Folding
2
2
 
3
- > A DSH Web GUI plugin delivering a **Codex-like folding experience**: runs of consecutive
4
- > **tool calls** fold into a single compact bar showing a one-line summary of the **last call** —
5
- > click to expand/collapse. It replaces no built-in renderer, and uninstalling restores the UI
6
- > completely.
7
-
8
- ![Folding demo](assets/demo-fold.gif)
9
- <!-- 🖼️ IMAGE SLOT: assets/demo-fold.gif — record 6–10s: a run of tool calls auto-folds into one bar → click to expand (cards waterfall down) → click to collapse (cards rise, heights shrink), 16:9, ≤2MB -->
10
-
11
- ## Highlights
12
-
13
- - **Runs of tool calls fold into one bar**: consecutive tool calls collapse into a single bar
14
- showing the **last call's** one-line summary, labelled "已折叠 N 个工具调用 · 点击展开"
15
- (N folded calls · click to expand);
16
- - **Thinking separates call groups (default on)**: settled think blocks SPLIT the calls around
17
- them into independent bars — calls before and after a completed think never merge into one bar;
18
- turn it off to restore the original fold, where thinking folds WITH the surrounding tool group;
19
- - **Thinking hidden by default, keepable**: settled think blocks are hidden outright by default;
20
- with "保留思考" enabled they never disappear — shown between the folded bars in split mode, or
21
- **re-inserted in their original order** between the calls on expand in merge mode;
22
- - **In-progress thinking stays visible**: streaming think blocks render as their own row until
23
- they complete, then follow the "split thinking" setting;
24
- - **Spring waterfall animations**: cards cascade down on expand (spring overshoot) and rise with a
25
- synchronized height shrink on collapse — content below follows continuously, no end snap;
26
- disabled automatically under `prefers-reduced-motion`;
27
- - **Bottom-pinned expand**: expanding near the bottom pins the bar's viewport position, so the
28
- chat's auto-scroll never shoves it off the top of the screen;
29
- - **Near-zero performance cost**: no page-wide observer, zero work while content streams, and
30
- everything pauses when the tab is hidden — measured ≈0.03% of one core while idle
31
- (see [Performance](#performance)).
32
-
33
- ![Expand / collapse animation](assets/expand-collapse.gif)
34
- <!-- 🖼️ IMAGE SLOT: assets/expand-collapse.gif — close-up of one 3–4 call run expanding (waterfall + label slide) and collapsing (rise + height shrink), square or 4:3, ≤2MB -->
3
+ [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
4
+
5
+ English | [中文](README.md)
6
+
7
+ > A DSH Web GUI plugin that provides a **Codex‑like tool folding experience**: consecutive **tool calls** are collapsed into a single compact bar showing only a one‑line summary of the last call. Click to expand/collapse.
8
+ > It does not replace any built‑in renderer, and uninstallation restores the UI completely.
9
+
10
+ ![Demo folding effect](assets/demo-fold.gif)
11
+
12
+ ---
13
+
14
+ ## Table of Contents
15
+
16
+ - [Installation](#installation)
17
+ - [Quick Start](#quick-start)
18
+ - [Default Behaviour First Use](#default-behaviour--first-use)
19
+ - [Features](#features)
20
+ - [Settings](#settings)
21
+ - [Options](#options)
22
+ - [Storage Location](#storage-location)
23
+ - [Performance](#performance)
24
+ - [Limitations & Compatibility](#limitations--compatibility)
25
+
26
+ ---
35
27
 
36
28
  ## Installation
37
29
 
38
- ### One-line GitHub install
30
+ ### Recommended (via npm)
39
31
 
40
32
  ```sh
41
- dsh plugin --profile web add github:Minecraftbe/dsh-toolfold
33
+ dsh plugin --profile web add dsh-toolfold
42
34
  ```
43
35
 
44
- ### Install from source
36
+ ### Alternative Methods
45
37
 
46
38
  ```sh
39
+ # From GitHub
40
+ dsh plugin --profile web add github:Minecraftbe/dsh-toolfold
41
+
42
+ # From source
47
43
  git clone https://github.com/Minecraftbe/dsh-toolfold.git
48
44
  dsh plugin --profile web add ./dsh-toolfold
49
45
  ```
50
46
 
51
- After installing, **restart dsh and refresh the browser** to activate (bundle layers are composed
52
- at boot). Verify the plugin reached the final config with:
47
+ After installation, **restart dsh and refresh your browser** the plugin will be active. You can verify that it is included in the final bundle with:
53
48
 
54
49
  ```sh
55
50
  dsh --profile web --dump-config
56
51
  ```
57
52
 
58
- > If an earlier version was mounted manually with a `toolfold` row in `cordis.patch.yml`,
59
- > remove that row before switching to this channel to avoid double-mounting.
60
-
61
- ### Uninstall
53
+ ### Uninstallation
62
54
 
63
55
  ```sh
64
56
  dsh plugin --profile web remove dsh-toolfold
65
57
  ```
66
58
 
67
- After a restart the UI is completely restored.
59
+ After restart, the interface returns to its original state.
60
+
61
+ ---
62
+
63
+ ## Quick Start
64
+
65
+ Once installed, consecutive tool calls are automatically folded into a single bar in any DSH conversation.
66
+
67
+ - **Click the fold bar** to expand or collapse the tool‑call cards (keyboard `Enter` / `Space` also work)
68
+ - **The bar shows**: a one‑line summary of the last call plus the label “N tool calls folded · click to expand”
69
+
70
+ ![Expand/collapse animation](assets/expand-collapse.gif)
71
+
72
+ ---
73
+
74
+ ## Default Behaviour – First Use
75
+
76
+ The plugin works immediately after installation, but you might notice two things that seem “unexpected” at first. Both are intentional defaults and can be changed easily.
77
+
78
+ ### ❓ Why did my thinking content disappear?
79
+
80
+ **Reason**: “Keep thinking” is off by default (`keepThink: false`). Completed thinking blocks are hidden to keep the conversation cleaner.
68
81
 
69
- ## Usage & Settings
82
+ **How to restore**: go to `Settings → Plugins → Tool Fold` and turn on **“Keep thinking”**.
70
83
 
71
- Settings: **Settings Plugins 工具折叠** (the card matches the built-in plugin cards and follows
72
- the light/dark theme).
84
+ ### Why are my tool calls split into two separate fold bars?
85
+
86
+ **Reason**: “Split thinking across call groups” is on by default (`splitThink: true`). The plugin treats a completed thinking block as a logical separator, folding calls before and after it independently.
87
+
88
+ **How to merge them**: go to `Settings → Plugins → Tool Fold` and turn off **“Split thinking across call groups”**.
89
+
90
+ > Ongoing (streaming) thinking is always visible regardless of these settings, and will only be processed according to the rules once it finishes.
91
+
92
+ ---
93
+
94
+ ## Features
95
+
96
+ ### Core Folding Behaviour
97
+
98
+ - **Automatic folding of consecutive tool calls** – adjacent tool calls are collapsed into one bar, showing only the last call’s summary line and the number of folded items.
99
+ - **Thinking splits call groups (enabled by default)** – a completed thinking block separates the tool calls before and after it into two independent fold bars; they are never merged across a thinking block.
100
+ - **Thinking hidden by default, can be kept** – completed thinking is hidden to save space; when “Keep thinking” is enabled, thinking appears between fold bars (in split mode) or interpolated back between calls on expansion (in merged mode).
101
+ - **Ongoing thinking always visible** – streaming thinking remains visible until it finishes, then follows the above rules.
102
+
103
+ ### Animation & Experience
104
+
105
+ - **Spring‑loaded waterfall animation** – cards drop one by one on expand (with spring bounce), and on collapse they rise while heights shrink synchronously, so content below moves continuously without jumping.
106
+ - **Sticks to the bottom without pushing out** – when expanding near the bottom, the fold bar’s viewport position is pinned so that automatic scrolling does not push it off screen.
107
+ - **Respects system preferences** – if the system has “Reduce motion” enabled, animations are automatically disabled.
108
+
109
+ ### Performance & Resource Usage
110
+
111
+ - **Near‑zero performance impact** – no page‑wide observers, zero engine work during streaming, and all activity is paused when the tab is hidden. Measured idle overhead is about 0.03% of a single CPU core.
112
+ - **Optional real‑time stats** – enable “Performance stats” in the settings card to see per‑step timings and verify overhead.
113
+
114
+ ---
115
+
116
+ ## Settings
117
+
118
+ Settings are found at: **Settings → Plugins → Tool Fold** (the card looks like other built‑in plugin cards and follows the light/dark theme).
73
119
 
74
120
  ![Settings card](assets/settings.png)
75
- <!-- 🖼️ IMAGE SLOT: assets/settings.png — screenshot of the expanded "工具折叠" card in Settings → Plugins (duration slider + keep-think toggle + stats toggle + storage-location hint), light theme, PNG/WebP ≤1MB -->
76
121
 
77
- | Setting | Description |
122
+ ### Options
123
+
124
+ | Option | Description |
78
125
  | --- | --- |
79
- | **展开动画时长** (expand duration) | Per-card duration of the expand/collapse waterfall, 0–1000ms, default 240ms; 0 = instant |
80
- | **保留思考** (keep thinking) | Settled thinking is hidden by default; on: it never disappears shown between the folded bars in split mode, or re-inserted in its original order between the calls on expand in merge mode |
81
- | **思考分隔调用组** (split call groups) | On (default): settled thinking splits the calls around it into independent bars; Off: thinking folds with the surrounding tool group (legacy behavior) |
82
- | **性能统计** (performance stats) | Shows the plugin's own live cost in the card (cumulative counts and ms/s for observer callbacks / engine refreshes / merge passes / safety rescans / summary clones, plus the streaming batches ignored by the zero-cost short-circuit) |
126
+ | **Expand animation duration** | Duration of the expand/collapse animation per card (0–1000 ms, default 240 ms; 0 = instant) |
127
+ | **Keep thinking** | Hide completed thinking by default; when enabled, thinking is shown between fold bars in split mode, or interpolated back between calls on expansion in merged mode |
128
+ | **Split thinking across call groups** | Enabled by default: a completed thinking block separates tool calls before and after it into independent fold bars. Disabled: thinking is folded together with the surrounding call group (legacy behaviour) |
129
+ | **Performance stats** | Show real‑time plugin timing in the card (cumulative count and ms/s for observation callbacks, engine refresh, merge recalculation, safe rescan, summary cloning, plus the number of streaming batches short‑circuited with zero overhead) |
130
+
131
+ ---
83
132
 
84
- Interactions: click the bar to expand/collapse; `Enter` or `Space` works too.
133
+ ### Storage Location
85
134
 
86
- ### Where settings live
135
+ Plugin settings are persisted automatically, with the following priority:
87
136
 
88
- - **Installed (bundle) mode**: the DSH **settings service** persists them to
89
- `~/.dsh/settings.yaml` (namespace `toolfold`) — the same document the product and other
90
- plugins use, host-side, so they survive browser/device changes. The card footer shows
91
- "设置保存在 DSH 主机配置". The file is editable directly:
137
+ - **Primary (installed/bundle mode)** – stored via the DSH **settings service** in `~/.dsh/settings.yaml` (namespace `toolfold`). They follow the dsh host, so they persist across browsers/devices. You can also edit the file manually:
92
138
 
93
139
  ```yaml
94
140
  toolfold:
95
- durMs: 240 # expand animation duration, 0–2000ms
96
- keepThink: false # keep settled thinking visible (between bars / on expand)
97
- splitThink: true # settled thinking splits call groups into independent bars (default true)
98
- stats: false # performance meter
141
+ durMs: 240 # expand animation duration 0–2000 ms
142
+ keepThink: false # whether to keep completed thinking visible
143
+ splitThink: true # whether to split call groups by thinking
144
+ stats: false # whether to enable performance stats
99
145
  ```
100
146
 
101
- - **When no DSH settings service is detected** (remote browsers and other setups without the DSH
102
- settings bridge): degrades to browser `localStorage` (key `dsh-toolfold.settings.v1`) and the
103
- card says "仅保存在本浏览器"; the legacy key `dsh-codex-collapse.settings.v1` migrates
104
- automatically on first load.
147
+ - **Fallback (when settings service is unavailable)** – falls back to browser `localStorage` (key `dsh-toolfold.settings.v1`), so settings are only kept in that browser. The old key `dsh-codex-collapse.settings.v1` is automatically migrated on first load.
105
148
 
106
- ![Keep-thinking comparison](assets/keep-think.gif)
107
- <!-- 🖼️ IMAGE SLOT: assets/keep-think.gif — same run expanded twice, "keep thinking" off vs on, showing the think block hidden vs re-inserted in order, ≤2MB -->
149
+ The settings card shows the current storage method (“Settings saved in DSH host config” or “Saved only in this browser”).
150
+
151
+ ---
108
152
 
109
153
  ## Performance
110
154
 
111
- - **Measured on the live GUI (8s idle window)**: engine total 1.6–2.8ms (0.3ms/s, 0.03% of one
112
- core); a 5s CPU sample in the same window shows **zero samples from the engine** the page's long
113
- tasks (141686ms) come from page load, product rendering, and other plugins;
114
- - **Zero work while streaming**: text/think token mutations are short-circuited in O(1) and never
115
- trigger a recompute (≈0.1–0.4µs per node);
116
- - **Hidden tab = literally zero**: all observers and timers disconnect on hide and re-attach with a
117
- reconciliation pass on return;
118
- - Turn on "性能统计" to watch every cost item live inside the settings card.
119
-
120
- ## Notes & Limitations
121
-
122
- - Folding works on the rendered DOM and depends on stable product markers; if the product markup
123
- changes, the worst case is that folding stops applying the chat itself is never harmed;
124
- - **In-progress thinking** and **AI text output** separate runs; **settled thinking** also
125
- separates runs by default ("思考分隔调用组" on) with it off, settled thinking folds with the
126
- surrounding tool group instead;
127
- - Expanded state is remembered per session flow + node key; reopening a session with the same keys
128
- restores it;
129
- - Settings are owned by the DSH host config (`~/.dsh/settings.yaml`); browser `localStorage` is
130
- only the fallback when the host bridge is unreachable (then each browser needs its own setup);
131
- - Requires `MutationObserver` and `requestAnimationFrame`; without them it degrades to "fold once on
132
- initial render".
155
+ - **Measured on a real GUI page (8 s idle window)** total engine time ~1.6–2.8 ms (~0.3 ms/s, ~0.03% single‑core); during a 5‑s CPU profile, engine functions appeared zero times.
156
+ - **Zero work during streaming** streaming thinking/text tokens are short‑circuited with O(1) checks and do not trigger any recalculation (~0.1–0.4 µs per node).
157
+ - **Hidden tab = literally zero** all observers and timers are disconnected when the tab is hidden; they are re‑attached and a single reconciliation run is performed when the tab becomes visible again.
158
+ - Enable **“Performance stats”** in the settings card to see detailed timings for every plugin operation.
159
+
160
+ ---
161
+
162
+ ## Limitations & Compatibility
163
+
164
+ - **DOM‑based** folding relies on stable product DOM markers. If markers change, the worst case is that folding stops working – it will not break the chat.
165
+ - **Splitting rules**:
166
+ - Ongoing thinking and plain text output separate tool call groups.
167
+ - Completed thinking also separates groups by default (when “Split thinking across call groups” is on); when off, thinking is folded together with its surrounding call group.
168
+ - **State memory** expand/collapse state is remembered per conversation flow and node key; re‑opening a conversation with the same key restores the state.
169
+ - **Browser requirements** needs `MutationObserver` and `requestAnimationFrame`. If unsupported, it falls back to “fold once on initial render”.
170
+ - **Settings storage** – uses the DSH host config (`~/.dsh/settings.yaml`) when available; only falls back to `localStorage` when the host bridge is unreachable (in which case settings are browser‑local only).
133
171
 
134
172
  ---
135
173
 
136
- This project was entirely completed by dsh + deepseek v4 flash(max).
174
+ > This project is entirely built with dsh + deepseek v4 flash(max).
package/README.md CHANGED
@@ -1,56 +1,54 @@
1
1
  # dsh-toolfold · 工具调用折叠
2
2
 
3
- > DSH Web GUI 插件,提供**类 Codex 的工具折叠体验**:把连续的**工具调用**折叠成一条简洁的
4
- > 折叠条,只展示最后一个调用的一行摘要,点击即可展开/收起。
5
- > 不替换任何内置渲染器,卸载后界面完全恢复原样。
3
+ [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
4
+
5
+ 中文 | [English](README.en.md)
6
+
7
+ > DSH Web GUI 插件,提供**类 Codex 的工具折叠体验**:把连续的**工具调用**折叠成一条简洁的折叠条,只展示最后一个调用的一行摘要,点击即可展开/收起。不替换任何内置渲染器,卸载后界面完全恢复原样。
6
8
 
7
9
  ![折叠效果演示](assets/demo-fold.gif)
8
- <!-- 🖼️ 图片位:assets/demo-fold.gif —— 建议录制 6~10s:多个工具调用自动折成一条 → 点击展开(卡片瀑布落下)→ 点击收起(卡片上行、高度收缩),16:9,≤2MB -->
9
-
10
- ## 特性一览
11
-
12
- - **连续工具调用折叠为一条**:相邻的工具调用整段收起,条内直接显示**最后一个调用**的一行
13
- 摘要,并标注「已折叠 N 个工具调用 · 点击展开」;
14
- - **思考分隔调用组(默认开启)**:已完成的思考会把前后两组工具调用**隔开、各自独立折叠**,
15
- 不会把思考前后的调用合并成一条;关闭后恢复原折叠方式——思考并入所在工具组一起折叠;
16
- - **思考默认隐藏,可保留**:已完成的思考默认隐藏(不占版面);开启「保留思考」后思考不再
17
- 消失——分隔模式下显示在两条折叠条之间,合并模式下展开时按**原顺序插回调用之间**;
18
- - **进行中的思考独立显示**:流式思考保持原样可见,完成后按「思考分隔」设置处理;
19
- - **弹性瀑布动画**:展开时卡片自上而下逐张落下(spring 回弹),收起时逐张上行且行高度同步收缩,
20
- 下方内容连续上移、结尾无跳变;系统开启「减少动态效果」时动画自动关闭;
21
- - **贴底不顶出**:靠近底部展开时,折叠条视口位置被钉住,不会因聊天自动滚动被顶出屏幕;
22
- - **接近零性能占用**:无页面级观察器、流式期间引擎零工作、标签页隐藏时全部暂停——
23
- 实机测量空闲开销约 0.03% 单核(详见[性能](#性能))。
24
10
 
25
- ![展开/收起动画](assets/expand-collapse.gif)
26
- <!-- 🖼️ 图片位:assets/expand-collapse.gif —— 建议:单段 3~4 个调用的展开(瀑布落下 + 标签滑左)与收起(上行 + 高度收缩)特写,正方形或 4:3,≤2MB -->
11
+ ---
27
12
 
28
- ## 安装
13
+ ## 目录
14
+
15
+ - [安装](#安装)
16
+ - [快速上手](#快速上手)
17
+ - [初次使用·默认行为说明](#初次使用默认行为说明)
18
+ - [功能特性](#功能特性)
19
+ - [设置说明](#设置说明)
20
+ - [设置项](#设置项)
21
+ - [存储位置](#存储位置)
22
+ - [性能](#性能)
23
+ - [限制与兼容性](#限制与兼容性)
29
24
 
25
+ ---
30
26
 
27
+ ## 安装
31
28
 
32
- ### GitHub 一行安装
29
+ ### 推荐(通过 npm)
33
30
 
34
31
  ```sh
35
- dsh plugin --profile web add github:Minecraftbe/dsh-toolfold
32
+ dsh plugin --profile web add dsh-toolfold
36
33
  ```
37
34
 
38
- ### 从源码安装
35
+ ### 其他方式
39
36
 
40
37
  ```sh
38
+ # 从 GitHub 安装
39
+ dsh plugin --profile web add github:Minecraftbe/dsh-toolfold
40
+
41
+ # 从源码安装
41
42
  git clone https://github.com/Minecraftbe/dsh-toolfold.git
42
43
  dsh plugin --profile web add ./dsh-toolfold
43
44
  ```
44
45
 
45
- 装完**重启 dsh 并刷新浏览器**即可生效(bundle 层在启动时合成)。可用以下命令确认已进入
46
- 最终配置:
46
+ 安装后**重启 dsh 并刷新浏览器**即可生效。可通过以下命令确认配置已生效:
47
47
 
48
48
  ```sh
49
49
  dsh --profile web --dump-config
50
50
  ```
51
51
 
52
- > 早期版本若曾在 `cordis.patch.yml` 手工加过 `toolfold` 行,先删除该行再切换,避免双挂载。
53
-
54
52
  ### 卸载
55
53
 
56
54
  ```sh
@@ -59,62 +57,117 @@ dsh plugin --profile web remove dsh-toolfold
59
57
 
60
58
  重启后界面完全恢复原样。
61
59
 
62
- ## 使用与设置
60
+ ---
61
+
62
+ ## 快速上手
63
+
64
+ 安装完成后,在 DSH 对话界面中,连续的工具调用会自动折叠为一条折叠条。
65
+
66
+ - **点击折叠条**:展开或收起工具调用卡片(键盘 `Enter` / `Space` 同样有效)
67
+ - **折叠条显示**:最后一个工具调用的一行摘要 + “已折叠 N 个工具调用 · 点击展开”
68
+
69
+ ![展开/收起动画](assets/expand-collapse.gif)
70
+
71
+ ---
72
+
73
+ ## 初次使用·默认行为说明
74
+
75
+ 插件安装后立即生效,你可能首先注意到两个“异常”现象——这都是默认设置所致,可按需调整。
76
+
77
+ ### ❓ 思考内容怎么消失了?
78
+
79
+ **原因**:默认关闭「保留思考」(`keepThink: false`),已完成的思考被折叠隐藏,目的是让界面更清爽。
63
80
 
64
- 设置入口:**设置 → 插件 → 工具折叠**(卡片外观与内置插件卡片一致,跟随深浅主题)。
81
+ **改回**:`设置 → 插件 → 工具折叠` → 开启「保留思考」。
82
+
83
+ ### ❓ 工具调用为什么被切成了两段?
84
+
85
+ **原因**:默认开启「思考分隔调用组」(`splitThink: true`),插件把思考视为逻辑分界线,将前后的工具调用各自独立折叠。
86
+
87
+ **改回**:`设置 → 插件 → 工具折叠` → 关闭「思考分隔调用组」。
88
+
89
+ > 进行中的思考不受任何开关影响,始终保持可见,完成后再按规则处理。
90
+
91
+ ---
92
+
93
+ ## 功能特性
94
+
95
+ ### 核心折叠行为
96
+
97
+ - **连续工具调用自动折叠**:相邻工具调用整段收起,仅显示最后一个调用的摘要行,并标注折叠数量
98
+ - **思考分隔调用组(默认开启)**:已完成的思考会将前后两组工具调用**隔开、各自独立折叠**,不会跨思考合并
99
+ - **思考默认隐藏,可保留**:已完成的思考默认不占用版面;开启「保留思考」后思考会出现在折叠条之间(分隔模式)或展开时插回调用之间(合并模式)
100
+ - **进行中的思考独立显示**:流式思考始终保持可见,完成后按上述规则处理
101
+
102
+ ### 动画与体验
103
+
104
+ - **弹性瀑布动画**:展开时卡片逐张落下(带 spring 回弹),收起时逐张上行且高度同步收缩,下方内容连续上移、无跳变
105
+ - **贴底不顶出**:靠近底部展开时,折叠条视口位置固定,不会因自动滚动被顶出屏幕
106
+ - **尊重系统偏好**:系统开启「减少动态效果」时,动画自动关闭
107
+
108
+ ### 性能与资源
109
+
110
+ - **接近零性能占用**:无页面级观察器,流式期间引擎零工作,标签页隐藏时全部暂停(空闲开销约 0.03% 单核)
111
+ - **实时性能统计**(可选):在设置卡片内显示插件各环节耗时,便于验证开销
112
+
113
+ ---
114
+
115
+ ## 设置说明
116
+
117
+ 设置入口:**设置 → 插件 → 工具折叠**(卡片外观与内置插件一致,跟随深浅主题)。
65
118
 
66
119
  ![设置卡片](assets/settings.png)
67
- <!-- 🖼️ 图片位:assets/settings.png —— 截图建议:设置 → 插件 页内「工具折叠」卡片展开态(时长滑块 + 保留思考开关 + 性能统计开关 + 存储位置提示),浅色主题,PNG/WebP ≤1MB -->
120
+
121
+ ### 设置项
68
122
 
69
123
  | 设置项 | 说明 |
70
124
  | --- | --- |
71
- | **展开动画时长** | 展开/收起瀑布动画的单卡时长,0–1000ms,默认 240ms0 为瞬时切换 |
72
- | **保留思考** | 已完成的思考默认隐藏;开启后思考保留——分隔模式下显示在两条折叠条之间,合并模式下展开时按原顺序插回调用之间 |
73
- | **思考分隔调用组** | 开启(默认):已完成的思考把前后两组工具调用隔开、各自独立折叠;关闭:思考并入所在工具组一起折叠(旧行为) |
74
- | **性能统计** | 在卡片内实时显示插件自身耗时(观察回调/引擎刷新/合并重算/安全重扫/摘要克隆的累计次数与 ms/s,以及被零开销短路忽略的流式批次),用于验证插件开销 |
125
+ | **展开动画时长** | 单张卡片的展开/收起动画时长(0–1000ms,默认 240ms;设为 0 为瞬时切换) |
126
+ | **保留思考** | 默认隐藏已完成的思考;开启后思考保留(分隔模式下显示在两条折叠条之间,合并模式下展开时插回调用之间) |
127
+ | **思考分隔调用组** | 开启(默认):已完成的思考把前后两组工具调用隔开、各自独立折叠;关闭:思考并入所在工具组一起折叠 |
128
+ | **性能统计** | 在卡片内实时显示插件自身耗时(观察回调/引擎刷新/合并重算/安全重扫/摘要克隆的累计次数与耗时,以及被零开销短路忽略的流式批次) |
75
129
 
76
- 交互方式:点击折叠条展开/收起;键盘 `Enter` 或 `Space` 同样有效。
130
+ ---
131
+
132
+ ### 存储位置
77
133
 
78
- ### 设置存到哪里
134
+ 插件设置会自动持久化,优先级如下:
79
135
 
80
- - **已安装(bundle)模式**:设置由 DSH **设置服务**持久化到
81
- `~/.dsh/settings.yaml`(命名空间 `toolfold`),与产品和其他插件的设置在同一个文档里,
82
- 随 dsh 主机走,换浏览器/设备不丢失;卡片底部会显示「设置保存在 DSH 主机配置」。
83
- 也可以直接手工编辑该文件:
136
+ - **优先(已安装模式)**:由 DSH **设置服务**写入 `~/.dsh/settings.yaml`(命名空间 `toolfold`),随主机走,换浏览器/设备不丢失。手工编辑示例:
84
137
 
85
138
  ```yaml
86
139
  toolfold:
87
140
  durMs: 240 # 展开动画时长 0–2000ms
88
- keepThink: false # 是否保留已完成的思考(显示在折叠条之间 / 展开时插回)
89
- splitThink: true # 是否让已完成的思考隔开前后两组工具调用(默认 true)
141
+ keepThink: false # 是否保留已完成的思考
142
+ splitThink: true # 是否让已完成的思考隔开前后两组工具调用
90
143
  stats: false # 是否开启性能统计
91
144
  ```
92
145
 
93
- - **未检测到 DSH 设置服务时**(远程浏览器等未接入 DSH 设置服务的场景):降级为浏览器
94
- `localStorage`(键 `dsh-toolfold.settings.v1`),卡片会显示「仅保存在本浏览器」;
95
- 旧版键 `dsh-codex-collapse.settings.v1` 首次加载自动迁移。
146
+ - **降级(无设置服务时)**:退化为浏览器 `localStorage`(键 `dsh-toolfold.settings.v1`),仅保存在当前浏览器。旧版键 `dsh-codex-collapse.settings.v1` 会在首次加载时自动迁移。
147
+
148
+ 设置卡片底部会显示当前使用的存储位置(“设置保存在 DSH 主机配置”或“仅保存在本浏览器”)。
96
149
 
97
- ![保留思考对比](assets/keep-think.gif)
98
- <!-- 🖼️ 图片位:assets/keep-think.gif —— 建议:同一段调用分别以「保留思考 关 / 开」各演示一次展开,展示思考不显示 / 按原顺序插回,≤2MB -->
150
+ ---
99
151
 
100
152
  ## 性能
101
153
 
102
- - **实测(真实 GUI 页面,空闲窗口 8s)**:引擎总耗时约 1.6–2.8ms(≈0.3ms/s,约 0.03% 单核);
103
- 同窗口 5s CPU 采样中引擎函数零样本,页面长任务(141686ms)来自页面加载与产品/其他插件渲染;
104
- - **流式零工作**:思考/文本 token 流式变更被 O(1) 短路直接忽略,不触发任何重算(约 0.1–0.4µs/节点);
105
- - **标签页隐藏 = 字面零占用**:隐藏时断开全部观察器与定时器,回到可见时重挂并补一次对账;
106
- - 打开「性能统计」即可在设置卡片内实时查看插件自身的每一项耗时。
154
+ - **实测数据(真实 GUI 页面,空闲窗口 8s)**:引擎总耗时约 1.6–2.8ms(≈0.3ms/s,约 0.03% 单核);同窗口 5s CPU 采样中引擎函数零样本。
155
+ - **流式零工作**:思考/文本 token 流式变更被 O(1) 短路忽略,不触发任何重算(约 0.10.4µs/节点)。
156
+ - **标签页隐藏 = 零占用**:隐藏时断开全部观察器与定时器,恢复可见时重挂并补一次对账。
157
+ - 打开「性能统计」即可在设置卡片内查看各项耗时明细。
158
+
159
+ ---
107
160
 
108
- ## 说明与限制
161
+ ## 限制与兼容性
109
162
 
110
- - 折叠基于渲染后的 DOM,依赖产品稳定标记;若产品标记变动,最坏情况是折叠停止生效,不会破坏聊天;
111
- - **进行中的思考**与 **AI 文字输出**会隔开工具调用;**已完成的思考**默认也会隔开(「思考分隔
112
- 调用组」开启时),关闭该选项后已完成的思考并入所在工具组一起折叠;
113
- - 展开状态按会话流 + 节点 key 记忆;会话重开且 key 相同时会恢复展开状态;
114
- - 设置以 DSH 主机配置(`~/.dsh/settings.yaml`)为准;仅当主机桥不可达时才退化为浏览器
115
- `localStorage`(此时换浏览器/设备需重新设置);
116
- - 需要浏览器支持 `MutationObserver` `requestAnimationFrame`;不支持时退化为「初始渲染折叠一次」。
163
+ - **基于 DOM 标记**:折叠依赖产品稳定的渲染标记;若标记变动,最坏情况是折叠停止生效,但不会破坏聊天。
164
+ - **分隔规则**:
165
+ - 进行中的思考与 AI 文字输出会隔开工具调用
166
+ - 已完成的思考默认也隔开(「思考分隔调用组」开启时);关闭后已完成的思考并入所在工具组一起折叠
167
+ - **状态记忆**:展开状态按会话流 + 节点 key 记忆;会话重开且 key 相同时会恢复展开状态。
168
+ - **浏览器要求**:需要支持 `MutationObserver` 与 `requestAnimationFrame`;不支持时退化为“初始渲染折叠一次”。
169
+ - **设置存储**:以 DSH 主机配置(`~/.dsh/settings.yaml`)为准;仅当主机桥不可达时才退化为 `localStorage`(换浏览器/设备需重新设置)。
117
170
 
118
171
  ---
119
172
 
120
- 本项目完全由 dsh + deepseek v4 flash(max) 完成。
173
+ > 本项目完全由 dsh + deepseek v4 flash(max) 完成。
package/lib/client.js CHANGED
@@ -34,6 +34,9 @@
34
34
  * the content below the run follows upward continuously — no blank
35
35
  * hole and no end snap. Cleanup runs exactly one step after the LAST
36
36
  * row's animation finishes — no dead wait before the run disappears.
37
+ * A run that keeps GROWING while it collapses (the model is still
38
+ * streaming the newest calls into it) extends the cascade to the new
39
+ * rows and re-arms the finish timer, so the tail never snaps shut.
37
40
  * When the reader is pinned near the bottom, expanding keeps the
38
41
  * bar's viewport position fixed (the product's sticky-bottom follow
39
42
  * would otherwise shove the bar out of the top).
@@ -983,13 +986,20 @@ window.__ModuleLoader__.load({
983
986
  var toggle = function () {
984
987
  var set = expanded.get(flow);
985
988
  if (set === undefined || disposed) return;
986
- if (set.has(run.headKey)) {
987
- startCollapse(flow, run, bar);
989
+ // The closure `run` is the bar's creation-time snapshot; every
990
+ // pass refreshes bar._ccxRun with the CURRENT run. When the model
991
+ // streams more calls into the same run (the newest info at the
992
+ // bottom of the flow), collapsing the stale snapshot would animate
993
+ // only the original rows and then snap the rest shut — the "runs
994
+ // briefly, then suddenly collapses" symptom.
995
+ var current = bar._ccxRun !== undefined ? bar._ccxRun : run;
996
+ if (set.has(current.headKey)) {
997
+ startCollapse(flow, current, bar);
988
998
  } else {
989
- set.add(run.headKey);
990
- cancelCollapse(flow, run);
999
+ set.add(current.headKey);
1000
+ cancelCollapse(flow, current, bar);
991
1001
  pass(flow);
992
- pinBar(bar, run);
1002
+ pinBar(bar, current);
993
1003
  }
994
1004
  };
995
1005
  bar.addEventListener('click', toggle);
@@ -1080,10 +1090,34 @@ window.__ModuleLoader__.load({
1080
1090
  }
1081
1091
  for (var j = 0; j < rows.length; j++) rows[j].classList.add(COLLAPSING_CLASS);
1082
1092
  animateRowShrink(rows, dur, step);
1093
+ // Track which rows the cascade has covered: a run that keeps
1094
+ // GROWING while it collapses (the model is still streaming the
1095
+ // newest calls into it) extends the cascade via extendCollapse and
1096
+ // re-arms the finish timer. Without this, rows added after the
1097
+ // collapse began would snap shut when the timer armed for the OLD
1098
+ // row count fires — the "runs briefly, then suddenly collapses"
1099
+ // symptom.
1100
+ bar._ccxCollapseRows = rows;
1083
1101
  // The bar flips to collapsed visuals now; the cards stay visible for
1084
1102
  // the rise (pass skips ccxMerged while the head key is collapsing).
1085
1103
  pass(flow);
1104
+ armCollapseFinish(flow, run, bar);
1105
+ }
1106
+
1107
+ /**
1108
+ * Arm (or re-arm) the collapse-finish timer. The delay covers one
1109
+ * full cascade — last row's stagger + rise + margin — measured from
1110
+ * the arm moment. `cascadeCount` defaults to the full covered row
1111
+ * set; extendCollapse passes only the NEW rows it just added, since
1112
+ * the already-covered rows have finished animating.
1113
+ */
1114
+ function armCollapseFinish(flow, run, bar, cascadeCount) {
1086
1115
  if (bar._ccxCollapseTimer !== undefined) timers.cancel(bar._ccxCollapseTimer);
1116
+ var rows = bar._ccxCollapseRows;
1117
+ var dur = durMs();
1118
+ var step = stepMs();
1119
+ var n = cascadeCount !== undefined ? cascadeCount : rows.length;
1120
+ var delay = Math.max(0, n - 1) * step + dur + 60;
1087
1121
  bar._ccxCollapseTimer = timers.after(function () {
1088
1122
  bar._ccxCollapseTimer = undefined;
1089
1123
  if (disposed || !flow.isConnected) return;
@@ -1092,21 +1126,63 @@ window.__ModuleLoader__.load({
1092
1126
  perNow.delete(run.headKey);
1093
1127
  if (perNow.size === 0) collapsing.delete(flow);
1094
1128
  }
1095
- for (var k = 0; k < rows.length; k++) {
1096
- rows[k].classList.remove(COLLAPSING_CLASS);
1097
- clearRowShrink(rows[k]);
1129
+ var all = bar._ccxCollapseRows;
1130
+ for (var k = 0; k < all.length; k++) {
1131
+ all[k].classList.remove(COLLAPSING_CLASS);
1132
+ clearRowShrink(all[k]);
1098
1133
  }
1134
+ bar._ccxCollapseRows = undefined;
1099
1135
  pass(flow); // applies ccxMerged now
1100
- }, Math.max(0, rows.length - 1) * step + dur + 60);
1136
+ }, delay);
1137
+ }
1138
+
1139
+ /**
1140
+ * A run that is mid-collapse and still GROWING (the model streams the
1141
+ * newest tool calls into it) folds the newly added rows into the same
1142
+ * rise/shrink cascade — with their own relative stagger, starting
1143
+ * immediately — and re-arms the finish timer so the last new row's
1144
+ * animation completes before the merge applies. Without this the new
1145
+ * rows sit at full height and then snap shut with the merge.
1146
+ */
1147
+ function extendCollapse(flow, run, bar) {
1148
+ var oldRows = bar._ccxCollapseRows;
1149
+ var rows = run.rows;
1150
+ if (oldRows === undefined || rows.length <= oldRows.length) return;
1151
+ var dur = durMs();
1152
+ var step = stepMs();
1153
+ // The rows that joined the run since the cascade covered it. Diff by
1154
+ // identity so appended, inserted, or replaced rows all join — the
1155
+ // streaming case appends, but never assume.
1156
+ var added = [];
1157
+ for (var i = 0; i < rows.length; i++) {
1158
+ if (oldRows.indexOf(rows[i]) === -1) added.push(rows[i]);
1159
+ }
1160
+ if (added.length === 0) return;
1161
+ for (var j = 0; j < added.length; j++) {
1162
+ var row = added[j];
1163
+ row.classList.remove(FALLING_CLASS, COLLAPSING_CLASS);
1164
+ row.style.setProperty('--ccx-i', String(j));
1165
+ row.classList.add(COLLAPSING_CLASS);
1166
+ }
1167
+ animateRowShrink(added, dur, step);
1168
+ bar._ccxCollapseRows = rows;
1169
+ armCollapseFinish(flow, run, bar, added.length);
1101
1170
  }
1102
1171
 
1103
1172
  /** Abort a pending collapse (the user re-expanded mid-animation). */
1104
- function cancelCollapse(flow, run) {
1173
+ function cancelCollapse(flow, run, bar) {
1105
1174
  var per = collapsing.get(flow);
1106
1175
  if (per !== undefined) {
1107
1176
  per.delete(run.headKey);
1108
1177
  if (per.size === 0) collapsing.delete(flow);
1109
1178
  }
1179
+ if (bar !== undefined && bar._ccxCollapseTimer !== undefined) {
1180
+ timers.cancel(bar._ccxCollapseTimer);
1181
+ bar._ccxCollapseTimer = undefined;
1182
+ }
1183
+ if (bar !== undefined) {
1184
+ bar._ccxCollapseRows = undefined;
1185
+ }
1110
1186
  var rows = run.rows;
1111
1187
  for (var i = 0; i < rows.length; i++) {
1112
1188
  rows[i].classList.remove(COLLAPSING_CLASS);
@@ -1294,6 +1370,15 @@ window.__ModuleLoader__.load({
1294
1370
  registry.set(run.headKey, bar);
1295
1371
  }
1296
1372
  updateBar(bar, run);
1373
+ // A run that is mid-collapse and still GROWING (the model is
1374
+ // streaming the newest calls into it) extends its cascade to the
1375
+ // newly added rows; the finish timer is re-armed by
1376
+ // extendCollapse. Without this the new rows would sit at full
1377
+ // height and then snap shut with the merge.
1378
+ if (bar._ccxCollapseRows !== undefined
1379
+ && run.rows.length > bar._ccxCollapseRows.length) {
1380
+ extendCollapse(flow, run, bar);
1381
+ }
1297
1382
  // The bar is the group HEADER: it stays before the run's first row,
1298
1383
  // so on expand the cards cascade downward below it. Guard against
1299
1384
  // re-inserting when already in place (insertBefore always mutates).
@@ -880,13 +880,20 @@
880
880
  var toggle = function () {
881
881
  var set = expanded.get(flow);
882
882
  if (set === undefined || disposed) return;
883
- if (set.has(run.headKey)) {
884
- startCollapse(flow, run, bar);
883
+ // The closure `run` is the bar's creation-time snapshot; every
884
+ // pass refreshes bar._ccxRun with the CURRENT run. When the model
885
+ // streams more calls into the same run (the newest info at the
886
+ // bottom of the flow), collapsing the stale snapshot would animate
887
+ // only the original rows and then snap the rest shut — the "runs
888
+ // briefly, then suddenly collapses" symptom.
889
+ var current = bar._ccxRun !== undefined ? bar._ccxRun : run;
890
+ if (set.has(current.headKey)) {
891
+ startCollapse(flow, current, bar);
885
892
  } else {
886
- set.add(run.headKey);
887
- cancelCollapse(flow, run);
893
+ set.add(current.headKey);
894
+ cancelCollapse(flow, current, bar);
888
895
  pass(flow);
889
- pinBar(bar, run);
896
+ pinBar(bar, current);
890
897
  }
891
898
  };
892
899
  bar.addEventListener('click', toggle);
@@ -977,10 +984,34 @@
977
984
  }
978
985
  for (var j = 0; j < rows.length; j++) rows[j].classList.add(COLLAPSING_CLASS);
979
986
  animateRowShrink(rows, dur, step);
987
+ // Track which rows the cascade has covered: a run that keeps
988
+ // GROWING while it collapses (the model is still streaming the
989
+ // newest calls into it) extends the cascade via extendCollapse and
990
+ // re-arms the finish timer. Without this, rows added after the
991
+ // collapse began would snap shut when the timer armed for the OLD
992
+ // row count fires — the "runs briefly, then suddenly collapses"
993
+ // symptom.
994
+ bar._ccxCollapseRows = rows;
980
995
  // The bar flips to collapsed visuals now; the cards stay visible for
981
996
  // the rise (pass skips ccxMerged while the head key is collapsing).
982
997
  pass(flow);
998
+ armCollapseFinish(flow, run, bar);
999
+ }
1000
+
1001
+ /**
1002
+ * Arm (or re-arm) the collapse-finish timer. The delay covers one
1003
+ * full cascade — last row's stagger + rise + margin — measured from
1004
+ * the arm moment. `cascadeCount` defaults to the full covered row
1005
+ * set; extendCollapse passes only the NEW rows it just added, since
1006
+ * the already-covered rows have finished animating.
1007
+ */
1008
+ function armCollapseFinish(flow, run, bar, cascadeCount) {
983
1009
  if (bar._ccxCollapseTimer !== undefined) timers.cancel(bar._ccxCollapseTimer);
1010
+ var rows = bar._ccxCollapseRows;
1011
+ var dur = durMs();
1012
+ var step = stepMs();
1013
+ var n = cascadeCount !== undefined ? cascadeCount : rows.length;
1014
+ var delay = Math.max(0, n - 1) * step + dur + 60;
984
1015
  bar._ccxCollapseTimer = timers.after(function () {
985
1016
  bar._ccxCollapseTimer = undefined;
986
1017
  if (disposed || !flow.isConnected) return;
@@ -989,21 +1020,63 @@
989
1020
  perNow.delete(run.headKey);
990
1021
  if (perNow.size === 0) collapsing.delete(flow);
991
1022
  }
992
- for (var k = 0; k < rows.length; k++) {
993
- rows[k].classList.remove(COLLAPSING_CLASS);
994
- clearRowShrink(rows[k]);
1023
+ var all = bar._ccxCollapseRows;
1024
+ for (var k = 0; k < all.length; k++) {
1025
+ all[k].classList.remove(COLLAPSING_CLASS);
1026
+ clearRowShrink(all[k]);
995
1027
  }
1028
+ bar._ccxCollapseRows = undefined;
996
1029
  pass(flow); // applies ccxMerged now
997
- }, Math.max(0, rows.length - 1) * step + dur + 60);
1030
+ }, delay);
1031
+ }
1032
+
1033
+ /**
1034
+ * A run that is mid-collapse and still GROWING (the model streams the
1035
+ * newest tool calls into it) folds the newly added rows into the same
1036
+ * rise/shrink cascade — with their own relative stagger, starting
1037
+ * immediately — and re-arms the finish timer so the last new row's
1038
+ * animation completes before the merge applies. Without this the new
1039
+ * rows sit at full height and then snap shut with the merge.
1040
+ */
1041
+ function extendCollapse(flow, run, bar) {
1042
+ var oldRows = bar._ccxCollapseRows;
1043
+ var rows = run.rows;
1044
+ if (oldRows === undefined || rows.length <= oldRows.length) return;
1045
+ var dur = durMs();
1046
+ var step = stepMs();
1047
+ // The rows that joined the run since the cascade covered it. Diff by
1048
+ // identity so appended, inserted, or replaced rows all join — the
1049
+ // streaming case appends, but never assume.
1050
+ var added = [];
1051
+ for (var i = 0; i < rows.length; i++) {
1052
+ if (oldRows.indexOf(rows[i]) === -1) added.push(rows[i]);
1053
+ }
1054
+ if (added.length === 0) return;
1055
+ for (var j = 0; j < added.length; j++) {
1056
+ var row = added[j];
1057
+ row.classList.remove(FALLING_CLASS, COLLAPSING_CLASS);
1058
+ row.style.setProperty('--ccx-i', String(j));
1059
+ row.classList.add(COLLAPSING_CLASS);
1060
+ }
1061
+ animateRowShrink(added, dur, step);
1062
+ bar._ccxCollapseRows = rows;
1063
+ armCollapseFinish(flow, run, bar, added.length);
998
1064
  }
999
1065
 
1000
1066
  /** Abort a pending collapse (the user re-expanded mid-animation). */
1001
- function cancelCollapse(flow, run) {
1067
+ function cancelCollapse(flow, run, bar) {
1002
1068
  var per = collapsing.get(flow);
1003
1069
  if (per !== undefined) {
1004
1070
  per.delete(run.headKey);
1005
1071
  if (per.size === 0) collapsing.delete(flow);
1006
1072
  }
1073
+ if (bar !== undefined && bar._ccxCollapseTimer !== undefined) {
1074
+ timers.cancel(bar._ccxCollapseTimer);
1075
+ bar._ccxCollapseTimer = undefined;
1076
+ }
1077
+ if (bar !== undefined) {
1078
+ bar._ccxCollapseRows = undefined;
1079
+ }
1007
1080
  var rows = run.rows;
1008
1081
  for (var i = 0; i < rows.length; i++) {
1009
1082
  rows[i].classList.remove(COLLAPSING_CLASS);
@@ -1191,6 +1264,15 @@
1191
1264
  registry.set(run.headKey, bar);
1192
1265
  }
1193
1266
  updateBar(bar, run);
1267
+ // A run that is mid-collapse and still GROWING (the model is
1268
+ // streaming the newest calls into it) extends its cascade to the
1269
+ // newly added rows; the finish timer is re-armed by
1270
+ // extendCollapse. Without this the new rows would sit at full
1271
+ // height and then snap shut with the merge.
1272
+ if (bar._ccxCollapseRows !== undefined
1273
+ && run.rows.length > bar._ccxCollapseRows.length) {
1274
+ extendCollapse(flow, run, bar);
1275
+ }
1194
1276
  // The bar is the group HEADER: it stays before the run's first row,
1195
1277
  // so on expand the cards cascade downward below it. Guard against
1196
1278
  // re-inserting when already in place (insertBefore always mutates).
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "dsh-toolfold",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "工具调用与思考的折叠显示:连续工具调用折叠为最后一个调用(可展开),已完成的思考随组折叠(可保留、展开时按原顺序显示),进行中的思考保持独立显示。",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/Minecraftbe/dsh-toolfold.git"
8
+ },
5
9
  "type": "module",
6
10
  "main": "lib/index.js",
7
11
  "exports": {