dsh-auto-collapse 0.1.6 → 0.1.7
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.en.md +100 -100
- package/README.md +101 -101
- package/lib/client.js +290 -26
- package/lib/index.js +41 -10
- package/lib/types/fold.d.ts +63 -4
- package/lib/types/index.d.ts +7 -1
- package/package.json +68 -69
package/README.en.md
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
# dsh-auto-collapse
|
|
2
|
-
|
|
3
|
-
> A DeepSeek Harness Web client plugin that auto-collapses tool cards and Think blocks into one-line summaries, so the chat keeps only what the model says.
|
|
4
|
-
>
|
|
5
|
-
> 中文: [README.md](./README.md)
|
|
6
|
-
>
|
|
7
|
-
> This plugin is listed in the Plugin marketplace: [marketplace link](https://www.dsh.so/artifact/dsh-auto-collapse/)
|
|
8
|
-
|
|
9
|
-
## What it does
|
|
10
|
-
|
|
11
|
-
`dsh-auto-collapse` is a pure front-end DOM plugin for the DeepSeek Harness Web chat UI. It collapses the working process into one-line summaries — tool calls and reasoning no longer fill the screen, giving the chat the collapsible look of the VSCode Codex desktop client, **and it also rewrites “Deep diving” to a configurable “Deep sleeping...”**. It never modifies message content — it only controls visibility of the working process.
|
|
12
|
-
|
|
13
|
-
## Preview
|
|
14
|
-
|
|
15
|
-

|
|
16
|
-
|
|
17
|
-
## Features
|
|
18
|
-
|
|
19
|
-
- **Turn-level auto collapse** (Level 1): when a turn finishes, the whole working process collapses into a `已处理 X秒` (processed in Xs) row, leaving only the model's final text. Click to expand the full workflow (context injections → thinking → tool calls → intermediate text → final text).
|
|
20
|
-
- **Second-level rows**: after expanding level one, tool-call groups and think blocks each collapse into a single chip row (`正在运行 {command}` / `运行了命令` / `已思考`), click to expand/collapse; adjacent tool groups merge, while body text serves as a hard boundary (never merged across).
|
|
21
|
-
- **Third-level think merge**: expanding `已思考` shows consecutive think rows merged into one row titled `Think · first line`, click to reveal the merged content block; raw fourth-level rows never appear.
|
|
22
|
-
- **Native visual alignment**: 16px icon box / 14px glyph / 24px line height / 16px row gap; colors use DSH native tokens (`--dsw-alias-label-*`); think and command icons come from DSH native icons (`IconThinkOutline14` / `IconApiOutline14`).
|
|
23
|
-
- **Stream-friendly**: in-place `assistant-step` body updates, React node replacement, and out-of-order history mounting are reconciled on every pass; running rows use a smooth text pulse motion, disabled by `prefers-reduced-motion`.
|
|
24
|
-
- **Complete work-node coverage**: top-level `command` / `manual-compaction`, context nodes, and image-only finals follow the same turn semantics as tool calls.
|
|
25
|
-
- **Configurable status text**: in Settings → Plugins → Plugin configuration, edit the status prompt (default `Deep sleeping...`); leaving it blank restores the official `
|
|
26
|
-
- **Fully reversible**: uninstalling (HMR stop) restores every collapsed/hidden/rewritten node.
|
|
27
|
-
|
|
28
|
-
## Install
|
|
29
|
-
|
|
30
|
-
Published npm package (recommended; uses the prebuilt release):
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
dsh plugin --profile web add "dsh-auto-collapse"
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Install from GitHub when using the development version or following `main`:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
dsh plugin --profile web add "github:a179-sanae/dsh-auto-collapse#main"
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Restart the DSH web service (or trigger plugin HMR), then hard-refresh the page (`Ctrl+Shift+R`). No configuration needed.
|
|
43
|
-
|
|
44
|
-
## Development
|
|
45
|
-
|
|
46
|
-
### Project layout
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
src/fold.ts core: FoldController (state machine) + findBlocks (block recognition) + collapse/expand logic
|
|
50
|
-
src/client.ts browser entry (plugin registration)
|
|
51
|
-
src/index.ts host half (host-side entry)
|
|
52
|
-
build.mjs build script (generates lib/client.js, lib/index.js, and lib/types/*)
|
|
53
|
-
tsconfig.build.json TypeScript declaration build configuration
|
|
54
|
-
deploy.mjs safe deploy: validate → back up → replace → verified restart → hash check/rollback; Windows uses PowerShell, Linux/macOS use lsof + ps
|
|
55
|
-
cordis.patch.yml profile tree mounting
|
|
56
|
-
test/ fake-DOM contract, race, session-switch, and 40-order permutation regressions
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Checks
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
npm run check
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Runs TypeScript checking, a fresh build, and the complete regression suite.
|
|
66
|
-
|
|
67
|
-
### Quick deploy (local dev)
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
npm run deploy
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Validates the plugin/DSH package identities and the process listening on port 3080, then creates a timestamped backup, replaces the bundle, restarts DSH, and verifies the served hash. Failures restore the old bundle. Windows, Linux, and macOS are supported; Unix systems locate DSH from `npm root -g` by default and use `HOME` for the profile path. Linux/macOS require `lsof`. Override defaults with `DSH_AUTO_COLLAPSE_LIB`, `DSH_DIR`, `DSH_WEB_PORT`, and `DSH_LOG_DIR`.
|
|
74
|
-
|
|
75
|
-
### Publishing a new version
|
|
76
|
-
|
|
77
|
-
Update the `version` in `package.json`, then publish to npm (the `prepack` hook builds automatically):
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
npm publish --access public
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
For local development, you can pack a tgz without publishing:
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
npm pack --pack-destination <local-plugin-dir>
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Point the plugin dependency in the profile's `package.json` to the new tarball and reinstall the plugin.
|
|
90
|
-
|
|
91
|
-
### Key mechanisms
|
|
92
|
-
|
|
93
|
-
- **Block recognition** (`findBlocks`): top-level nodes are classified as tool calls, command/manual-compaction cards, contexts, thinking, or body content; user/steering/turn-tail nodes are hard boundaries.
|
|
94
|
-
- **Segment reconciliation**: every pass rebuilds segments from current DOM order. The last `assistant-step` containing text or media is final; earlier bodies are intermediate work. Stable flow/node keys preserve UI state without one-shot mutation bookkeeping.
|
|
95
|
-
- **Duration**: streaming segments each track their own first running observation; historical segments parse official duration or the `timeStart`/turn-tail range. Whole minutes omit the seconds field.
|
|
96
|
-
- **React coexistence and reversibility**: replaced nodes rebind by stable key, removed level-one rows rebuild with their expansion state, and every inline `display` value is saved before mutation and restored exactly.
|
|
97
|
-
|
|
98
|
-
## License
|
|
99
|
-
|
|
100
|
-
MIT
|
|
1
|
+
# dsh-auto-collapse
|
|
2
|
+
|
|
3
|
+
> A DeepSeek Harness Web client plugin that auto-collapses tool cards and Think blocks into one-line summaries, so the chat keeps only what the model says.
|
|
4
|
+
>
|
|
5
|
+
> 中文: [README.md](./README.md)
|
|
6
|
+
>
|
|
7
|
+
> This plugin is listed in the Plugin marketplace: [marketplace link](https://www.dsh.so/artifact/dsh-auto-collapse/)
|
|
8
|
+
|
|
9
|
+
## What it does
|
|
10
|
+
|
|
11
|
+
`dsh-auto-collapse` is a pure front-end DOM plugin for the DeepSeek Harness Web chat UI. It collapses the working process into one-line summaries — tool calls and reasoning no longer fill the screen, giving the chat the collapsible look of the VSCode Codex desktop client, **and it also rewrites the official status line (“深度求索中...”, formerly “Deep diving”) to a configurable “Deep sleeping...”**. It never modifies message content — it only controls visibility of the working process.
|
|
12
|
+
|
|
13
|
+
## Preview
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **Turn-level auto collapse** (Level 1): when a turn finishes, the whole working process collapses into a `已处理 X秒` (processed in Xs) row, leaving only the model's final text. Click to expand the full workflow (context injections → thinking → tool calls → intermediate text → final text). On DSH 0.1.2+, which ships a native per-turn summary row (tool-call · message · subagent counts), the Level 1 row yields to it and is no longer shown; second-level folding is unaffected.
|
|
20
|
+
- **Second-level rows**: after expanding level one, tool-call groups and think blocks each collapse into a single chip row (`正在运行 {command}` / `运行了命令` / `已思考`), click to expand/collapse; adjacent tool groups merge, while body text serves as a hard boundary (never merged across).
|
|
21
|
+
- **Third-level think merge**: expanding `已思考` shows consecutive think rows merged into one row titled `Think · first line`, click to reveal the merged content block; raw fourth-level rows never appear.
|
|
22
|
+
- **Native visual alignment**: 16px icon box / 14px glyph / 24px line height / 16px row gap; colors use DSH native tokens (`--dsw-alias-label-*`); think and command icons come from DSH native icons (`IconThinkOutline14` / `IconApiOutline14`).
|
|
23
|
+
- **Stream-friendly**: in-place `assistant-step` body updates, React node replacement, and out-of-order history mounting are reconciled on every pass; running rows use a smooth text pulse motion, disabled by `prefers-reduced-motion`.
|
|
24
|
+
- **Complete work-node coverage**: top-level `command` / `manual-compaction`, context nodes, and image-only finals follow the same turn semantics as tool calls.
|
|
25
|
+
- **Configurable status text**: in Settings → Plugins → Plugin configuration, edit the status prompt (default `Deep sleeping...`); leaving it blank restores the official copy (“深度求索中...” in current builds). The host-appended elapsed suffix (e.g. `33秒`) is preserved.
|
|
26
|
+
- **Fully reversible**: uninstalling (HMR stop) restores every collapsed/hidden/rewritten node.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
Published npm package (recommended; uses the prebuilt release):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
dsh plugin --profile web add "dsh-auto-collapse"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Install from GitHub when using the development version or following `main`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
dsh plugin --profile web add "github:a179-sanae/dsh-auto-collapse#main"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Restart the DSH web service (or trigger plugin HMR), then hard-refresh the page (`Ctrl+Shift+R`). No configuration needed.
|
|
43
|
+
|
|
44
|
+
## Development
|
|
45
|
+
|
|
46
|
+
### Project layout
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
src/fold.ts core: FoldController (state machine) + findBlocks (block recognition) + collapse/expand logic
|
|
50
|
+
src/client.ts browser entry (plugin registration)
|
|
51
|
+
src/index.ts host half (host-side entry)
|
|
52
|
+
build.mjs build script (generates lib/client.js, lib/index.js, and lib/types/*)
|
|
53
|
+
tsconfig.build.json TypeScript declaration build configuration
|
|
54
|
+
deploy.mjs safe deploy: validate → back up → replace → verified restart → hash check/rollback; Windows uses PowerShell, Linux/macOS use lsof + ps
|
|
55
|
+
cordis.patch.yml profile tree mounting
|
|
56
|
+
test/ fake-DOM contract, race, session-switch, and 40-order permutation regressions
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Checks
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm run check
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Runs TypeScript checking, a fresh build, and the complete regression suite.
|
|
66
|
+
|
|
67
|
+
### Quick deploy (local dev)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm run deploy
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Validates the plugin/DSH package identities and the process listening on port 3080, then creates a timestamped backup, replaces the bundle, restarts DSH, and verifies the served hash. Failures restore the old bundle. Windows, Linux, and macOS are supported; Unix systems locate DSH from `npm root -g` by default and use `HOME` for the profile path. Linux/macOS require `lsof`. Override defaults with `DSH_AUTO_COLLAPSE_LIB`, `DSH_DIR`, `DSH_WEB_PORT`, and `DSH_LOG_DIR`.
|
|
74
|
+
|
|
75
|
+
### Publishing a new version
|
|
76
|
+
|
|
77
|
+
Update the `version` in `package.json`, then publish to npm (the `prepack` hook builds automatically):
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm publish --access public
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For local development, you can pack a tgz without publishing:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm pack --pack-destination <local-plugin-dir>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Point the plugin dependency in the profile's `package.json` to the new tarball and reinstall the plugin.
|
|
90
|
+
|
|
91
|
+
### Key mechanisms
|
|
92
|
+
|
|
93
|
+
- **Block recognition** (`findBlocks`): top-level nodes are classified as tool calls, command/manual-compaction cards, contexts, thinking, or body content; user/steering/turn-tail nodes are hard boundaries.
|
|
94
|
+
- **Segment reconciliation**: every pass rebuilds segments from current DOM order. The last `assistant-step` containing text or media is final; earlier bodies are intermediate work. Stable flow/node keys preserve UI state without one-shot mutation bookkeeping.
|
|
95
|
+
- **Duration**: streaming segments each track their own first running observation; historical segments parse official duration or the `timeStart`/turn-tail range. Whole minutes omit the seconds field.
|
|
96
|
+
- **React coexistence and reversibility**: replaced nodes rebind by stable key, removed level-one rows rebuild with their expansion state, and every inline `display` value is saved before mutation and restored exactly.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
package/README.md
CHANGED
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
# dsh-auto-collapse
|
|
2
|
-
|
|
3
|
-
> DeepSeek Harness Web 客户端插件:把会话里的工具卡片与 Think 推理块自动折叠成一行摘要,让界面只保留模型说的话。
|
|
4
|
-
>
|
|
5
|
-
> English: [README.en.md](./README.en.md)
|
|
6
|
-
>
|
|
7
|
-
> 本插件已收录到插件市场(Plugin marketplace):[市场链接](https://www.dsh.so/artifact/dsh-auto-collapse/)
|
|
8
|
-
|
|
9
|
-
## 这是什么
|
|
10
|
-
|
|
11
|
-
`dsh-auto-collapse` 是一个纯前端 DOM 插件,挂在 DeepSeek Harness Web 聊天界面上,把工作流程折叠成一行行摘要——工具调用、推理过程不再占据整屏,呈现接近 VSCode Codex
|
|
12
|
-
|
|
13
|
-
## 效果预览
|
|
14
|
-
|
|
15
|
-

|
|
16
|
-
|
|
17
|
-
## 特性
|
|
18
|
-
|
|
19
|
-
- **回合完成自动收起**(一级):每个回合完成后,工作过程收成一行 `已处理 X秒`,只留模型最终正文;点击展开完整工作流程(上下文注入 → 思考 → 工具调用 → 过程正文 → 最终正文)。
|
|
20
|
-
- **二级折叠行**:展开一级后,工具调用组与思考块各自折叠成一行 chip(`正在运行 {命令}` / `运行了命令` / `已思考`),点击展开/收起;相邻工具组合并,正文输出是硬边界(不会跨正文合并)。
|
|
21
|
-
- **三级思考合并**:展开 `已思考` 后,连续思考合并为一个三级思考行(标题 `Think · 第一句`),点击展开合并内容块;原始四级行不出现。
|
|
22
|
-
- **原生视觉对齐**:图标盒 16px / glyph 14px / 行高 24px / 行距 16px,颜色使用 DSH 原生 token(`--dsw-alias-label-*`),思考与命令图标取自 DSH 原生图标(`IconThinkOutline14` / `IconApiOutline14`)。
|
|
23
|
-
- **展开/收起过渡动画**:点击驱动的展开(淡入 + 4px 上移,合并思考正文带高度展开)与收起(镜像淡出,后代随祖先 seat 整体消失、无跳变)均为 180ms;仅用户点击触发动画,流式协调器决策保持瞬时。
|
|
24
|
-
- **流式友好**:同一个 `assistant-step` 原地补正文、React 换节点和历史乱序挂载都会重新协调;running 状态带文字平滑呼吸动画,`prefers-reduced-motion` 下停止动画(过渡动画同样禁用)。
|
|
25
|
-
- **完整工作类型**:除 tool-call 外,顶层 `command` / `manual-compaction`、context 和纯图片 final 都按同一回合语义处理。
|
|
26
|
-
- **可配置状态提示词**:在 设置 → 插件 → 插件配置 中可以编辑“状态提示词”,默认 `Deep sleeping
|
|
27
|
-
- **可逆**:卸载(HMR stop)时完整还原所有折叠/隐藏/改写。
|
|
28
|
-
|
|
29
|
-
## 安装
|
|
30
|
-
|
|
31
|
-
已发布 npm 包(推荐,使用构建好的版本):
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
dsh plugin --profile web add "dsh-auto-collapse"
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
从 GitHub 安装(开发版或需要跟随 `main` 分支时):
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
dsh plugin --profile web add "github:a179-sanae/dsh-auto-collapse#main"
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
安装后重启 DSH web 服务(或触发插件 HMR),页面 `Ctrl+Shift+R` 硬刷新即可生效。无需任何配置。
|
|
44
|
-
|
|
45
|
-
## 开发
|
|
46
|
-
|
|
47
|
-
### 项目结构
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
src/fold.ts 核心:FoldController(状态机)+ findBlocks(块识别)+ 折叠/展开逻辑
|
|
51
|
-
src/client.ts 浏览器端入口(注册插件)
|
|
52
|
-
src/index.ts host half(宿主端,Host half)
|
|
53
|
-
build.mjs 构建脚本(Build script):生成 lib/client.js、lib/index.js 与 lib/types/*
|
|
54
|
-
tsconfig.build.json TypeScript 声明构建配置(Declaration build config)
|
|
55
|
-
deploy.mjs 安全部署(Safe deploy):校验 → 备份 → 替换 → 身份核验重启 → 哈希验证/回滚(DSH web 输出持久化到 ~/.dsh/logs/web.{out,err}.log);Windows 使用 PowerShell,Linux/macOS 使用 lsof + ps
|
|
56
|
-
cordis.patch.yml profile 树挂载
|
|
57
|
-
test/ fake DOM 契约、竞态、会话切换与 40 组乱序排列回归
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### 检查
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
npm run check
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
依次执行 TypeScript 检查、构建和全部回归测试。
|
|
67
|
-
|
|
68
|
-
### 快速部署(本机开发)
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
npm run deploy
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
脚本先核验插件/DSH 包名和 3080 监听进程身份,再做时间戳备份、替换、重启与服务端哈希验证;失败自动恢复旧 bundle。支持 Windows、Linux 和 macOS;Unix 系统默认从 `npm root -g` 定位 DSH 包,并使用 `HOME` 定位 profile。Linux/macOS 需要安装 `lsof`。可用 `DSH_AUTO_COLLAPSE_LIB`、`DSH_DIR`、`DSH_WEB_PORT`、`DSH_LOG_DIR` 覆盖默认路径。
|
|
75
|
-
|
|
76
|
-
### 发布新版本
|
|
77
|
-
|
|
78
|
-
更新 `package.json` 中的 `version` 后,发布到 npm(`prepack` 钩子会自动构建):
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
npm publish --access public
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
本机开发也可以只打包为 tgz:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
npm pack --pack-destination <本地插件目录>
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
将 profile 的 `package.json` 中插件依赖更新为新 tgz 路径后重新安装插件。
|
|
91
|
-
|
|
92
|
-
### 关键机制
|
|
93
|
-
|
|
94
|
-
- **块识别**(`findBlocks`):顶层节点按 tool-call、command/manual-compaction、context、thinking 和正文分类;user/steering/turn-tail 是不可跨越的硬边界。
|
|
95
|
-
- **segment 协调**:每轮根据当前 DOM 顺序重建 segment;最后一个含文本或媒体的 `assistant-step` 是 final,其余正文是中间过程。稳定 flow/key 复用展开状态,不依赖一次性 mutation 事件。
|
|
96
|
-
- **时长**:流式回合按 segment 分别记录 running 起点;历史回合从官方时长或 `timeStart`/turn-tail 解析。格式 `X秒` / `X分Y秒`,整分省略秒位。
|
|
97
|
-
- **React 共存与可逆性**:节点替换后按稳定 key 重新绑定;一级行被移除会按原展开状态重建;所有 inline `display` 在首次改写前保存并精确恢复。
|
|
98
|
-
|
|
99
|
-
## 许可
|
|
100
|
-
|
|
101
|
-
MIT
|
|
1
|
+
# dsh-auto-collapse
|
|
2
|
+
|
|
3
|
+
> DeepSeek Harness Web 客户端插件:把会话里的工具卡片与 Think 推理块自动折叠成一行摘要,让界面只保留模型说的话。
|
|
4
|
+
>
|
|
5
|
+
> English: [README.en.md](./README.en.md)
|
|
6
|
+
>
|
|
7
|
+
> 本插件已收录到插件市场(Plugin marketplace):[市场链接](https://www.dsh.so/artifact/dsh-auto-collapse/)
|
|
8
|
+
|
|
9
|
+
## 这是什么
|
|
10
|
+
|
|
11
|
+
`dsh-auto-collapse` 是一个纯前端 DOM 插件,挂在 DeepSeek Harness Web 聊天界面上,把工作流程折叠成一行行摘要——工具调用、推理过程不再占据整屏,呈现接近 VSCode Codex 桌面端的折叠体验,**同时将官方运行状态文案(“深度求索中...” / 旧版 “Deep diving”)修改为可配置的“Deep sleeping...”**。它不改动消息内容,只控制工作流程的显示状态。
|
|
12
|
+
|
|
13
|
+
## 效果预览
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
## 特性
|
|
18
|
+
|
|
19
|
+
- **回合完成自动收起**(一级):每个回合完成后,工作过程收成一行 `已处理 X秒`,只留模型最终正文;点击展开完整工作流程(上下文注入 → 思考 → 工具调用 → 过程正文 → 最终正文)。DSH 0.1.2+ 自带原生回合摘要行(工具调用 · 消息 · subagent 计数)时,一级行向它让位不再单独显示,二级折叠不受影响。
|
|
20
|
+
- **二级折叠行**:展开一级后,工具调用组与思考块各自折叠成一行 chip(`正在运行 {命令}` / `运行了命令` / `已思考`),点击展开/收起;相邻工具组合并,正文输出是硬边界(不会跨正文合并)。
|
|
21
|
+
- **三级思考合并**:展开 `已思考` 后,连续思考合并为一个三级思考行(标题 `Think · 第一句`),点击展开合并内容块;原始四级行不出现。
|
|
22
|
+
- **原生视觉对齐**:图标盒 16px / glyph 14px / 行高 24px / 行距 16px,颜色使用 DSH 原生 token(`--dsw-alias-label-*`),思考与命令图标取自 DSH 原生图标(`IconThinkOutline14` / `IconApiOutline14`)。
|
|
23
|
+
- **展开/收起过渡动画**:点击驱动的展开(淡入 + 4px 上移,合并思考正文带高度展开)与收起(镜像淡出,后代随祖先 seat 整体消失、无跳变)均为 180ms;仅用户点击触发动画,流式协调器决策保持瞬时。
|
|
24
|
+
- **流式友好**:同一个 `assistant-step` 原地补正文、React 换节点和历史乱序挂载都会重新协调;running 状态带文字平滑呼吸动画,`prefers-reduced-motion` 下停止动画(过渡动画同样禁用)。
|
|
25
|
+
- **完整工作类型**:除 tool-call 外,顶层 `command` / `manual-compaction`、context 和纯图片 final 都按同一回合语义处理。
|
|
26
|
+
- **可配置状态提示词**:在 设置 → 插件 → 插件配置 中可以编辑“状态提示词”,默认 `Deep sleeping...`;留空保存后恢复官方原文(当前版本为“深度求索中...”)。宿主追加的用时后缀(如 `33秒`)保留。
|
|
27
|
+
- **可逆**:卸载(HMR stop)时完整还原所有折叠/隐藏/改写。
|
|
28
|
+
|
|
29
|
+
## 安装
|
|
30
|
+
|
|
31
|
+
已发布 npm 包(推荐,使用构建好的版本):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
dsh plugin --profile web add "dsh-auto-collapse"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
从 GitHub 安装(开发版或需要跟随 `main` 分支时):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
dsh plugin --profile web add "github:a179-sanae/dsh-auto-collapse#main"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
安装后重启 DSH web 服务(或触发插件 HMR),页面 `Ctrl+Shift+R` 硬刷新即可生效。无需任何配置。
|
|
44
|
+
|
|
45
|
+
## 开发
|
|
46
|
+
|
|
47
|
+
### 项目结构
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
src/fold.ts 核心:FoldController(状态机)+ findBlocks(块识别)+ 折叠/展开逻辑
|
|
51
|
+
src/client.ts 浏览器端入口(注册插件)
|
|
52
|
+
src/index.ts host half(宿主端,Host half)
|
|
53
|
+
build.mjs 构建脚本(Build script):生成 lib/client.js、lib/index.js 与 lib/types/*
|
|
54
|
+
tsconfig.build.json TypeScript 声明构建配置(Declaration build config)
|
|
55
|
+
deploy.mjs 安全部署(Safe deploy):校验 → 备份 → 替换 → 身份核验重启 → 哈希验证/回滚(DSH web 输出持久化到 ~/.dsh/logs/web.{out,err}.log);Windows 使用 PowerShell,Linux/macOS 使用 lsof + ps
|
|
56
|
+
cordis.patch.yml profile 树挂载
|
|
57
|
+
test/ fake DOM 契约、竞态、会话切换与 40 组乱序排列回归
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 检查
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run check
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
依次执行 TypeScript 检查、构建和全部回归测试。
|
|
67
|
+
|
|
68
|
+
### 快速部署(本机开发)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm run deploy
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
脚本先核验插件/DSH 包名和 3080 监听进程身份,再做时间戳备份、替换、重启与服务端哈希验证;失败自动恢复旧 bundle。支持 Windows、Linux 和 macOS;Unix 系统默认从 `npm root -g` 定位 DSH 包,并使用 `HOME` 定位 profile。Linux/macOS 需要安装 `lsof`。可用 `DSH_AUTO_COLLAPSE_LIB`、`DSH_DIR`、`DSH_WEB_PORT`、`DSH_LOG_DIR` 覆盖默认路径。
|
|
75
|
+
|
|
76
|
+
### 发布新版本
|
|
77
|
+
|
|
78
|
+
更新 `package.json` 中的 `version` 后,发布到 npm(`prepack` 钩子会自动构建):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm publish --access public
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
本机开发也可以只打包为 tgz:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm pack --pack-destination <本地插件目录>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
将 profile 的 `package.json` 中插件依赖更新为新 tgz 路径后重新安装插件。
|
|
91
|
+
|
|
92
|
+
### 关键机制
|
|
93
|
+
|
|
94
|
+
- **块识别**(`findBlocks`):顶层节点按 tool-call、command/manual-compaction、context、thinking 和正文分类;user/steering/turn-tail 是不可跨越的硬边界。
|
|
95
|
+
- **segment 协调**:每轮根据当前 DOM 顺序重建 segment;最后一个含文本或媒体的 `assistant-step` 是 final,其余正文是中间过程。稳定 flow/key 复用展开状态,不依赖一次性 mutation 事件。
|
|
96
|
+
- **时长**:流式回合按 segment 分别记录 running 起点;历史回合从官方时长或 `timeStart`/turn-tail 解析。格式 `X秒` / `X分Y秒`,整分省略秒位。
|
|
97
|
+
- **React 共存与可逆性**:节点替换后按稳定 key 重新绑定;一级行被移除会按原展开状态重建;所有 inline `display` 在首次改写前保存并精确恢复。
|
|
98
|
+
|
|
99
|
+
## 许可
|
|
100
|
+
|
|
101
|
+
MIT
|
package/lib/client.js
CHANGED
|
@@ -36,6 +36,7 @@ var __dshcfBundle = (() => {
|
|
|
36
36
|
// src/fold.ts
|
|
37
37
|
var STYLE_ID = "dshcf-style";
|
|
38
38
|
var DEFAULT_STATUS_TEXT = "Deep sleeping...";
|
|
39
|
+
var TURN_STATUS_COPY_RE = /Deep diving[.…]*|深度求索中\s*[.…]*/;
|
|
39
40
|
var ANIM_DURATION_MS = 180;
|
|
40
41
|
var ANIM_EASING = "ease-out";
|
|
41
42
|
var DISPLAY_OWNED_PROP = "--dshcf-display-owned";
|
|
@@ -58,6 +59,8 @@ var __dshcfBundle = (() => {
|
|
|
58
59
|
cordis_stop: "Stop",
|
|
59
60
|
cordis_undefine: "Remove"
|
|
60
61
|
};
|
|
62
|
+
var TURN_PROCESS_KIND = "turn-process";
|
|
63
|
+
var NATIVE_OPEN_ATTR = "data-open";
|
|
61
64
|
var CHIP_CSS = `
|
|
62
65
|
.dshcf-chip {
|
|
63
66
|
box-sizing: border-box;
|
|
@@ -395,6 +398,7 @@ var __dshcfBundle = (() => {
|
|
|
395
398
|
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
396
399
|
/** 自上次 pass 以来子树发生变化的 flow 顶层消息;pass 开头统一失效。 */
|
|
397
400
|
this.dirtyMessages = /* @__PURE__ */ new Set();
|
|
401
|
+
/** 在途显示动画(元素 → 记录):冲突仲裁、记账对齐与生命周期清理的依据。
|
|
398
402
|
/** 在途显示动画(元素 → 记录):冲突仲裁、记账对齐与生命周期清理的依据。
|
|
399
403
|
* 用 Map 不用 WeakMap——switchFlow/stop 需要遍历全量 cancel。 */
|
|
400
404
|
this.pendingAnims = /* @__PURE__ */ new Map();
|
|
@@ -413,6 +417,20 @@ var __dshcfBundle = (() => {
|
|
|
413
417
|
* attributes 批次(流式文本、data-state 翻转)不改变块结构,跳过全量
|
|
414
418
|
* querySelectorAll 重扫(issue #14:长会话下每轮重扫造成主线程卡顿)。 */
|
|
415
419
|
this.structureDirty = true;
|
|
420
|
+
/** 本轮 pass 的原生 turn 摘要快照(turn id → 开合态):每轮重建,不跨轮复用;
|
|
421
|
+
* data-open 翻转不改变块结构也能驱动重算,见 buildNativeTurnMap。 */
|
|
422
|
+
this.nativeTurns = /* @__PURE__ */ new Map();
|
|
423
|
+
/** 被折叠掏空后藏起的中间包装层(真机 44px vs 28px 真因:think 行全隐后
|
|
424
|
+
* 其父容器变零高度空壳,仍作为 flex item 参与父级 gap,凭空多出 16px)。
|
|
425
|
+
* 内容恢复(展开/流式追加/块转世)时同函数恢复显示;switchFlow 清空。 */
|
|
426
|
+
this.emptiedWrappers = /* @__PURE__ */ new Set();
|
|
427
|
+
/** 本轮 pass 内有过 display 实写(hide/restore 瞬时路径)。空洞发现的触发
|
|
428
|
+
* 条件之一:纯 display 收放不产生 childList,不触发 structureDirty。
|
|
429
|
+
* settle 触发的后续 pass 则由 settleFired 覆盖。读后即清(见发现调用点)。 */
|
|
430
|
+
this.displayTouched = false;
|
|
431
|
+
/** 有 fade 自然结算过(chipSettle 跑过)。结算本身不写 display,但它意味着
|
|
432
|
+
* 某行刚变隐藏——空洞可能刚形成。 */
|
|
433
|
+
this.settleFired = false;
|
|
416
434
|
/** 滚动稳定化(issue #14):flow 最近的滚动容器缓存(按 flow 身份失效)。 */
|
|
417
435
|
this.scrollContainer = null;
|
|
418
436
|
this.scrollContainerFlow = null;
|
|
@@ -448,7 +466,10 @@ var __dshcfBundle = (() => {
|
|
|
448
466
|
childList: true,
|
|
449
467
|
subtree: true,
|
|
450
468
|
attributes: true,
|
|
451
|
-
|
|
469
|
+
// 原生 TurnProcess 开合只改 attribute(成员 data-turn-process-hidden、
|
|
470
|
+
// 摘要按钮 data-open),必须进过滤器才能即时调度;插件自身从不写
|
|
471
|
+
// 这两个属性,不会自激。
|
|
472
|
+
attributeFilter: ["data-selected", "data-state", "data-turn-process-hidden", "data-open"],
|
|
452
473
|
// 流式文本更新(React 改 text node 的 data)属于 characterData
|
|
453
474
|
// mutation:不观察则二级摘要/滚动跟随只能靠偶发结构变化驱动,
|
|
454
475
|
// 变成“隔几秒跳一次”。所有文本写入都有守卫(值不变不写),
|
|
@@ -518,6 +539,17 @@ var __dshcfBundle = (() => {
|
|
|
518
539
|
return;
|
|
519
540
|
}
|
|
520
541
|
}
|
|
542
|
+
for (const el of [...this.emptiedWrappers]) {
|
|
543
|
+
if (!el.isConnected) {
|
|
544
|
+
this.emptiedWrappers.delete(el);
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (!this.isHollow(el)) {
|
|
548
|
+
this.restoreElement(el);
|
|
549
|
+
if (el.style.display === "none") el.style.display = "";
|
|
550
|
+
this.emptiedWrappers.delete(el);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
521
553
|
}
|
|
522
554
|
stop() {
|
|
523
555
|
this.disposed = true;
|
|
@@ -652,6 +684,7 @@ var __dshcfBundle = (() => {
|
|
|
652
684
|
this.currentBlocks = new Map(blocks.map((block) => [block.key, block]));
|
|
653
685
|
const segments = buildSegments(flow, blocks, (el) => this.hasBodyCached(el));
|
|
654
686
|
const liveSegmentKeys = new Set(segments.map((segment) => segment.key));
|
|
687
|
+
this.nativeTurns = buildNativeTurnMap(flow);
|
|
655
688
|
const scrollAnchor = this.captureScrollAnchor(flow);
|
|
656
689
|
for (const segment of segments) {
|
|
657
690
|
if (!segment.running) continue;
|
|
@@ -668,6 +701,12 @@ var __dshcfBundle = (() => {
|
|
|
668
701
|
if (!snapshot.closed || snapshot.running || !snapshot.hasWork) continue;
|
|
669
702
|
completedKeys.add(snapshot.key);
|
|
670
703
|
this.completedOnce.add(snapshot.key);
|
|
704
|
+
if (segmentHasNativeTurn(snapshot, this.nativeTurns)) {
|
|
705
|
+
const prev = this.segmentStates.get(snapshot.key);
|
|
706
|
+
prev?.row?.remove();
|
|
707
|
+
if (prev !== void 0) this.segmentStates.delete(snapshot.key);
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
671
710
|
let state = this.segmentStates.get(snapshot.key);
|
|
672
711
|
if (state === void 0) {
|
|
673
712
|
state = { key: snapshot.key, row: null, expanded: false, snapshot };
|
|
@@ -697,6 +736,21 @@ var __dshcfBundle = (() => {
|
|
|
697
736
|
seenBlocks.add(block.key);
|
|
698
737
|
this.reconcileBlock(block, segmentByBlock.get(block.key) ?? null, desiredHidden);
|
|
699
738
|
}
|
|
739
|
+
if (rebuildBlocks || this.displayTouched || this.settleFired) {
|
|
740
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
741
|
+
for (const block of this.currentBlocks.values()) {
|
|
742
|
+
if (block.host.isConnected) hosts.add(block.host);
|
|
743
|
+
}
|
|
744
|
+
for (const segment of segments) {
|
|
745
|
+
if (segment.finalStep?.isConnected) hosts.add(segment.finalStep);
|
|
746
|
+
for (const middle of segment.middleSteps) {
|
|
747
|
+
if (middle.isConnected) hosts.add(middle);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
this.discoverHollowWrappers(hosts, desiredHidden);
|
|
751
|
+
}
|
|
752
|
+
this.displayTouched = false;
|
|
753
|
+
this.settleFired = false;
|
|
700
754
|
for (const segment of segments) {
|
|
701
755
|
const state = this.segmentStates.get(segment.key);
|
|
702
756
|
const collapse = state !== void 0 && !state.expanded;
|
|
@@ -719,6 +773,11 @@ var __dshcfBundle = (() => {
|
|
|
719
773
|
if (segment.finalStep !== null) this.retainDisplayControl(segment.finalStep, desiredHidden);
|
|
720
774
|
}
|
|
721
775
|
this.cleanupStaleChips(seenBlocks);
|
|
776
|
+
for (const [el, record] of [...this.pendingAnims]) {
|
|
777
|
+
if (record.target === "hidden" && record.kind === "fade" && this.pendingAnims.get(el) === record && this.isAnimOverdue(record.anim)) {
|
|
778
|
+
this.finishFadeCollapse(el, record);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
722
781
|
this.restoreUnusedDisplays(desiredHidden);
|
|
723
782
|
for (const state of this.segmentStates.values()) this.placeProcessedRow(flow, state);
|
|
724
783
|
this.stabilizeScrollAfterFold(scrollAnchor);
|
|
@@ -808,6 +867,8 @@ var __dshcfBundle = (() => {
|
|
|
808
867
|
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
809
868
|
this.dirtyMessages.clear();
|
|
810
869
|
this.auditDisplays.clear();
|
|
870
|
+
this.nativeTurns.clear();
|
|
871
|
+
this.emptiedWrappers.clear();
|
|
811
872
|
this.structureDirty = true;
|
|
812
873
|
this.restoreAllDisplays();
|
|
813
874
|
restoreTurnStatus(this.turnStatusTexts);
|
|
@@ -859,7 +920,29 @@ var __dshcfBundle = (() => {
|
|
|
859
920
|
}
|
|
860
921
|
if (row.parentElement !== flow || row.nextElementSibling !== target) target.before(row);
|
|
861
922
|
}
|
|
923
|
+
/** 原生收起时隐藏本块插件自有 overlay(chip/合并行+内容块):只做
|
|
924
|
+
* display 直写,不清钉住之外的账本、不删展开态,供原生再展开后复用。
|
|
925
|
+
* 在途 WAAPI 动画不追踪取消——收尾回调只做幂等终态对齐,无残留。 */
|
|
926
|
+
hideOverlayForNativeCollapse(block) {
|
|
927
|
+
const existing = this.chips.get(block.key)?.chip;
|
|
928
|
+
if (existing !== void 0 && existing.style.display !== "none") {
|
|
929
|
+
existing.style.marginBottom = "";
|
|
930
|
+
existing.style.display = "none";
|
|
931
|
+
}
|
|
932
|
+
const row = this.mergedThinks.get(block.host);
|
|
933
|
+
if (row !== void 0 && row.style.display !== "none") {
|
|
934
|
+
row.style.display = "none";
|
|
935
|
+
const body = row.nextElementSibling;
|
|
936
|
+
if (body instanceof HTMLElement && body.classList.contains("dshcf-merged-body") && body.style.display !== "none") {
|
|
937
|
+
body.style.display = "none";
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
862
941
|
reconcileBlock(block, segment, desiredHidden) {
|
|
942
|
+
if (blockNativelyCollapsed(block, this.nativeTurns)) {
|
|
943
|
+
this.hideOverlayForNativeCollapse(block);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
863
946
|
const state = segment === null ? void 0 : this.segmentStates.get(segment.key);
|
|
864
947
|
const segmentAnimatableBlocks = segment === null ? void 0 : this.animatableSegmentBlocks.get(segment.key);
|
|
865
948
|
const animate = this.animatableKeys.has(block.key) || segment !== null && this.animatableKeys.has(segment.key) && (segmentAnimatableBlocks === void 0 || segmentAnimatableBlocks.has(block.key));
|
|
@@ -881,7 +964,7 @@ var __dshcfBundle = (() => {
|
|
|
881
964
|
existing.style.display = "none";
|
|
882
965
|
}
|
|
883
966
|
}
|
|
884
|
-
this.
|
|
967
|
+
this.syncEmptiedWrappers(block, desiredHidden);
|
|
885
968
|
return;
|
|
886
969
|
}
|
|
887
970
|
let expanded = this.blockExpanded.get(block.key) ?? false;
|
|
@@ -901,6 +984,7 @@ var __dshcfBundle = (() => {
|
|
|
901
984
|
if (chipWasHidden && animate && !(hostAnimate && block.mount === "inside")) this.revealVisual(chip);
|
|
902
985
|
if (expanded || !this.hasPendingCollapse(block)) this.unpinChipMargin(chip);
|
|
903
986
|
const chipSettle = () => {
|
|
987
|
+
this.settleFired = true;
|
|
904
988
|
if (!this.hasPendingCollapse(block)) this.unpinChipMargin(chip);
|
|
905
989
|
};
|
|
906
990
|
for (const container of block.containers) {
|
|
@@ -922,9 +1006,123 @@ var __dshcfBundle = (() => {
|
|
|
922
1006
|
} else {
|
|
923
1007
|
if (this.releaseMergedThink(block.host, animate, chipSettle)) this.pinChipMargin(chip);
|
|
924
1008
|
}
|
|
1009
|
+
this.syncEmptiedWrappers(block, desiredHidden);
|
|
925
1010
|
chip.classList.toggle("dshcf-has-body", block.mount === "inside" && this.hasBodyCached(block.host));
|
|
926
1011
|
updateChip(chip, block.rows, expanded);
|
|
927
1012
|
}
|
|
1013
|
+
/**
|
|
1014
|
+
* 掏空包装层对账(真机 44px vs 28px 真因修复)。
|
|
1015
|
+
*
|
|
1016
|
+
* 折叠把某容器的子行全部 display:none 后,该容器变零高度空壳,但仍是
|
|
1017
|
+
* 父级 flex 的 item 并参与 gap(如正文体 flex-column gap:16px),凭空多出
|
|
1018
|
+
* 一份间距。隐藏这类空壳可让 gap 塌缩;子内容恢复可见(展开/流式追加/
|
|
1019
|
+
* 块转世)时恢复显示。
|
|
1020
|
+
*
|
|
1021
|
+
* 安全边界:
|
|
1022
|
+
* - 只看 block.host 内部、不含 host 本人(宿主显隐归 segment 逻辑);
|
|
1023
|
+
* - 跳过结构 seat(data-chat-flow-kind / data-chat-anchor-key)与插件
|
|
1024
|
+
* 自身 overlay(dshcf- 前缀类);
|
|
1025
|
+
* - 只隐藏内容空洞(无可见子内容):零高度无文本的空壳 display:none 与
|
|
1026
|
+
* 保持显示像素一致,只塌缩父级 gap;任何一方恢复内容即恢复显示。
|
|
1027
|
+
*/
|
|
1028
|
+
/**
|
|
1029
|
+
* 空洞包装层兜底发现:按宿主直扫 div(仅结构变化轮次调用)。
|
|
1030
|
+
* 只做隐藏侧:空洞即藏并记入 tracked;恢复侧由 syncEmptiedWrappers 的
|
|
1031
|
+
* tracked 复核与 audit 承担。与按行 walk 共用同一安全边界(结构 seat /
|
|
1032
|
+
* 插件 overlay / 宿主本人不碰)。
|
|
1033
|
+
*/
|
|
1034
|
+
discoverHollowWrappers(hosts, desiredHidden) {
|
|
1035
|
+
for (const host of hosts) {
|
|
1036
|
+
if (!host.isConnected) continue;
|
|
1037
|
+
let divs;
|
|
1038
|
+
try {
|
|
1039
|
+
divs = [...host.querySelectorAll("div")];
|
|
1040
|
+
} catch {
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
for (const el of divs) {
|
|
1044
|
+
if (el === host || !(el instanceof HTMLElement)) continue;
|
|
1045
|
+
const cls = el.className;
|
|
1046
|
+
if (typeof cls === "string" && cls.split(" ").some((c) => c.startsWith("dshcf-"))) continue;
|
|
1047
|
+
if (el.hasAttribute("data-chat-flow-kind") || el.hasAttribute("data-chat-anchor-key")) continue;
|
|
1048
|
+
if (this.isHollow(el)) {
|
|
1049
|
+
this.hideElement(el, desiredHidden, false);
|
|
1050
|
+
this.emptiedWrappers.add(el);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
syncEmptiedWrappers(block, desiredHidden) {
|
|
1056
|
+
const flow = this.flow;
|
|
1057
|
+
if (flow === null) return;
|
|
1058
|
+
const scopeOf = (el) => {
|
|
1059
|
+
let cur = el?.parentElement ?? null;
|
|
1060
|
+
while (cur instanceof HTMLElement && cur.parentElement !== flow) {
|
|
1061
|
+
cur = cur.parentElement;
|
|
1062
|
+
}
|
|
1063
|
+
return cur instanceof HTMLElement && cur.parentElement === flow ? cur : null;
|
|
1064
|
+
};
|
|
1065
|
+
const candidates = [];
|
|
1066
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1067
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
1068
|
+
const collect = (el) => {
|
|
1069
|
+
const scope = scopeOf(el);
|
|
1070
|
+
if (scope === null) return;
|
|
1071
|
+
scopes.add(scope);
|
|
1072
|
+
let node = el?.parentElement ?? null;
|
|
1073
|
+
while (node instanceof HTMLElement && node !== scope) {
|
|
1074
|
+
if (!seen.has(node)) {
|
|
1075
|
+
seen.add(node);
|
|
1076
|
+
candidates.push(node);
|
|
1077
|
+
}
|
|
1078
|
+
node = node.parentElement;
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
for (const row of block.rows) collect(row);
|
|
1082
|
+
for (const container of block.containers) collect(container);
|
|
1083
|
+
for (const el of [...this.emptiedWrappers]) {
|
|
1084
|
+
if (!el.isConnected) {
|
|
1085
|
+
this.emptiedWrappers.delete(el);
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
const scope = scopeOf(el);
|
|
1089
|
+
if (scope === null || !scopes.has(scope)) continue;
|
|
1090
|
+
if (!seen.has(el)) {
|
|
1091
|
+
seen.add(el);
|
|
1092
|
+
candidates.push(el);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
for (const el of candidates) {
|
|
1096
|
+
if (!el.isConnected) {
|
|
1097
|
+
this.emptiedWrappers.delete(el);
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
const cls = el.className;
|
|
1101
|
+
if (typeof cls === "string" && cls.split(" ").some((c) => c.startsWith("dshcf-"))) continue;
|
|
1102
|
+
if (el.hasAttribute("data-chat-flow-kind") || el.hasAttribute("data-chat-anchor-key")) continue;
|
|
1103
|
+
if (this.isHollow(el)) {
|
|
1104
|
+
this.hideElement(el, desiredHidden, false);
|
|
1105
|
+
this.emptiedWrappers.add(el);
|
|
1106
|
+
} else if (this.emptiedWrappers.delete(el)) {
|
|
1107
|
+
this.restoreElement(el);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
/** 内容空洞判定:无可见子内容(文本/元素任一可见即非空洞)。隐藏者是谁
|
|
1112
|
+
* 不重要——零高度无文本节点 display:none 与保持显示像素一致(只塌缩父级
|
|
1113
|
+
* gap,正是要修的幻影);任何一方恢复内容,下轮 pass/audit 即恢复显示。
|
|
1114
|
+
* isDisplayed 走 getComputedStyle(桩内退化为内联,语义一致)。 */
|
|
1115
|
+
isHollow(el) {
|
|
1116
|
+
for (const child of [...el.childNodes]) {
|
|
1117
|
+
if (child.nodeType === 3) {
|
|
1118
|
+
if ((child.textContent ?? "").trim() !== "") return false;
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
if (!(child instanceof HTMLElement)) continue;
|
|
1122
|
+
if (isDisplayed(child)) return false;
|
|
1123
|
+
}
|
|
1124
|
+
return true;
|
|
1125
|
+
}
|
|
928
1126
|
ensureChip(block) {
|
|
929
1127
|
let record = this.chips.get(block.key);
|
|
930
1128
|
const validParent = record !== void 0 && (block.mount === "inside" ? record.chip.parentElement === block.host : record.chip.parentElement === block.host.parentElement);
|
|
@@ -1287,6 +1485,7 @@ var __dshcfBundle = (() => {
|
|
|
1287
1485
|
}
|
|
1288
1486
|
el.style.display = "none";
|
|
1289
1487
|
this.writtenDisplay.set(el, "none");
|
|
1488
|
+
this.displayTouched = true;
|
|
1290
1489
|
return false;
|
|
1291
1490
|
}
|
|
1292
1491
|
restoreElement(el, animate = false) {
|
|
@@ -1302,11 +1501,17 @@ var __dshcfBundle = (() => {
|
|
|
1302
1501
|
}
|
|
1303
1502
|
const original = this.originalDisplay.get(el);
|
|
1304
1503
|
if (!animate || !this.canAnimate(el) || this.hasAnimatingAncestor(el)) {
|
|
1305
|
-
if (el.style.display !== original)
|
|
1504
|
+
if (el.style.display !== original) {
|
|
1505
|
+
el.style.display = original;
|
|
1506
|
+
this.displayTouched = true;
|
|
1507
|
+
}
|
|
1306
1508
|
this.releaseDisplayLedger(el);
|
|
1307
1509
|
return;
|
|
1308
1510
|
}
|
|
1309
|
-
if (el.style.display !== original)
|
|
1511
|
+
if (el.style.display !== original) {
|
|
1512
|
+
el.style.display = original;
|
|
1513
|
+
this.displayTouched = true;
|
|
1514
|
+
}
|
|
1310
1515
|
this.writtenDisplay.set(el, original);
|
|
1311
1516
|
this.startReveal(el);
|
|
1312
1517
|
}
|
|
@@ -1378,28 +1583,48 @@ var __dshcfBundle = (() => {
|
|
|
1378
1583
|
],
|
|
1379
1584
|
{ duration: ANIM_DURATION_MS, easing: ANIM_EASING, fill: "forwards" }
|
|
1380
1585
|
);
|
|
1381
|
-
const record = { anim, target: "hidden", kind: "fade" };
|
|
1586
|
+
const record = { anim, target: "hidden", kind: "fade", settle };
|
|
1382
1587
|
this.pendingAnims.set(el, record);
|
|
1383
|
-
anim.onfinish = () =>
|
|
1384
|
-
if (this.pendingAnims.get(el) !== record) return;
|
|
1385
|
-
this.pendingAnims.delete(el);
|
|
1386
|
-
if (this.displayForeign(el)) {
|
|
1387
|
-
this.releaseDisplayLedger(el);
|
|
1388
|
-
this.schedule();
|
|
1389
|
-
anim.cancel();
|
|
1390
|
-
return;
|
|
1391
|
-
}
|
|
1392
|
-
if (el.style.display !== "none") el.style.display = "none";
|
|
1393
|
-
this.writtenDisplay.set(el, "none");
|
|
1394
|
-
settle?.();
|
|
1395
|
-
anim.cancel();
|
|
1396
|
-
this.schedule();
|
|
1397
|
-
};
|
|
1588
|
+
anim.onfinish = () => this.finishFadeCollapse(el, record);
|
|
1398
1589
|
anim.oncancel = () => {
|
|
1399
1590
|
if (this.pendingAnims.get(el) !== record) return;
|
|
1400
1591
|
this.pendingAnims.delete(el);
|
|
1401
1592
|
};
|
|
1402
1593
|
}
|
|
1594
|
+
/**
|
|
1595
|
+
* 收起 fade 终态结算(onfinish 与僵尸收割共用)。仅被同元素新动画
|
|
1596
|
+
* supersede 时早退;被 sweep 删账(元素已断连)或收割时仍执行 settle——
|
|
1597
|
+
* 回调经 controller 可达,动画必然触发 onfinish,早退会把 chip 内联 16px
|
|
1598
|
+
* 钉住永久残留。重挂载同节点不可能(React 只建新节点),重启动的新动画
|
|
1599
|
+
* 由第一条守卫覆盖,无误伤。
|
|
1600
|
+
*/
|
|
1601
|
+
finishFadeCollapse(el, record) {
|
|
1602
|
+
const cur = this.pendingAnims.get(el);
|
|
1603
|
+
if (cur !== void 0 && cur !== record) return;
|
|
1604
|
+
this.pendingAnims.delete(el);
|
|
1605
|
+
if (this.displayForeign(el)) {
|
|
1606
|
+
this.releaseDisplayLedger(el);
|
|
1607
|
+
this.schedule();
|
|
1608
|
+
record.anim.cancel();
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
if (el.style.display !== "none") el.style.display = "none";
|
|
1612
|
+
this.writtenDisplay.set(el, "none");
|
|
1613
|
+
record.settle?.();
|
|
1614
|
+
record.anim.cancel();
|
|
1615
|
+
this.schedule();
|
|
1616
|
+
}
|
|
1617
|
+
/** 动画是否已播完(终态未结算):finish 事件丢失时的兜底判定。WAAPI 桩
|
|
1618
|
+
* 无 playState/effect 时一律 false,走正常事件路径,测试行为不变。 */
|
|
1619
|
+
isAnimOverdue(anim) {
|
|
1620
|
+
const a = anim;
|
|
1621
|
+
if (a.playState === "finished") return true;
|
|
1622
|
+
if (typeof a.currentTime === "number") {
|
|
1623
|
+
const end = a.effect?.getComputedTiming?.().endTime;
|
|
1624
|
+
if (typeof end === "number" && a.currentTime >= end) return true;
|
|
1625
|
+
}
|
|
1626
|
+
return false;
|
|
1627
|
+
}
|
|
1403
1628
|
/** 轻量视觉 reveal(opacity + 4px 微位移):用于插件全资元素的即时显示
|
|
1404
1629
|
* 路径——chip(一级展开时出现)与 merged-think 行(二级展开时出现)。
|
|
1405
1630
|
* 这些元素的 display 完全由插件直写、无 React 协调竞争,因此不入
|
|
@@ -1417,7 +1642,12 @@ var __dshcfBundle = (() => {
|
|
|
1417
1642
|
}
|
|
1418
1643
|
restoreUnusedDisplays(desired) {
|
|
1419
1644
|
for (const el of [...this.controlledDisplay]) {
|
|
1420
|
-
if (
|
|
1645
|
+
if (desired.has(el)) continue;
|
|
1646
|
+
if (!el.isConnected && this.pendingAnims.get(el)?.target === "hidden") {
|
|
1647
|
+
this.releaseDisplayLedger(el);
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
1650
|
+
this.restoreElement(el);
|
|
1421
1651
|
}
|
|
1422
1652
|
}
|
|
1423
1653
|
/** 收集 audit 需要观察的 display 集合;只读取内联样式,不触发布局计算。 */
|
|
@@ -1663,6 +1893,39 @@ var __dshcfBundle = (() => {
|
|
|
1663
1893
|
if (typeof getComputedStyle === "function") return getComputedStyle(el).display !== "none";
|
|
1664
1894
|
return el.style.display !== "none";
|
|
1665
1895
|
}
|
|
1896
|
+
function buildNativeTurnMap(flow) {
|
|
1897
|
+
const states = /* @__PURE__ */ new Map();
|
|
1898
|
+
for (const el of flow.children) {
|
|
1899
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
1900
|
+
if (el.getAttribute("data-chat-flow-kind") !== TURN_PROCESS_KIND) continue;
|
|
1901
|
+
const turn = el.getAttribute("data-chat-turn");
|
|
1902
|
+
if (turn === null || turn === "") continue;
|
|
1903
|
+
const button = el.querySelector("button[data-turn-process]");
|
|
1904
|
+
if (button === null) continue;
|
|
1905
|
+
const openAttr = button.getAttribute(NATIVE_OPEN_ATTR) !== null;
|
|
1906
|
+
const expandedAttr = button.getAttribute("aria-expanded");
|
|
1907
|
+
const open = expandedAttr === null ? openAttr : expandedAttr === "true";
|
|
1908
|
+
states.set(turn, { collapsed: !open });
|
|
1909
|
+
}
|
|
1910
|
+
return states;
|
|
1911
|
+
}
|
|
1912
|
+
function elementTurn(el) {
|
|
1913
|
+
const turn = el.getAttribute("data-chat-turn");
|
|
1914
|
+
return turn === null || turn === "" ? null : turn;
|
|
1915
|
+
}
|
|
1916
|
+
function segmentHasNativeTurn(segment, nativeTurns) {
|
|
1917
|
+
if (nativeTurns.size === 0) return false;
|
|
1918
|
+
for (const block of segment.blocks) {
|
|
1919
|
+
const turn = elementTurn(block.host);
|
|
1920
|
+
if (turn !== null && nativeTurns.has(turn)) return true;
|
|
1921
|
+
}
|
|
1922
|
+
return false;
|
|
1923
|
+
}
|
|
1924
|
+
function blockNativelyCollapsed(block, nativeTurns) {
|
|
1925
|
+
if (nativeTurns.size === 0) return false;
|
|
1926
|
+
const turn = elementTurn(block.host);
|
|
1927
|
+
return turn !== null && nativeTurns.get(turn)?.collapsed === true;
|
|
1928
|
+
}
|
|
1666
1929
|
function stableElementKey(el, fallbackIndex) {
|
|
1667
1930
|
const kind = el.getAttribute("data-chat-flow-kind") ?? "node";
|
|
1668
1931
|
const key = el.getAttribute("data-chat-flow-key") ?? el.getAttribute("data-chat-anchor-key") ?? `${kind}:${fallbackIndex}`;
|
|
@@ -1776,6 +2039,7 @@ var __dshcfBundle = (() => {
|
|
|
1776
2039
|
};
|
|
1777
2040
|
for (const el of children) {
|
|
1778
2041
|
const kind = el.getAttribute("data-chat-flow-kind");
|
|
2042
|
+
if (kind === TURN_PROCESS_KIND) continue;
|
|
1779
2043
|
if (kind === "user" || kind === "steering" || kind === "turn-tail") {
|
|
1780
2044
|
flushCarry();
|
|
1781
2045
|
run = null;
|
|
@@ -2127,14 +2391,14 @@ var __dshcfBundle = (() => {
|
|
|
2127
2391
|
const statuses = flow.matches('[role="status"]') ? [flow, ...flow.querySelectorAll('[role="status"]')] : [...flow.querySelectorAll('[role="status"]')];
|
|
2128
2392
|
for (const status of statuses) {
|
|
2129
2393
|
for (const node of status.childNodes) {
|
|
2130
|
-
if (node instanceof Text && node.data
|
|
2394
|
+
if (node instanceof Text && TURN_STATUS_COPY_RE.test(node.data)) {
|
|
2131
2395
|
let record = originals.get(node);
|
|
2132
2396
|
if (record === void 0) {
|
|
2133
2397
|
record = { original: node.data, written: "" };
|
|
2134
2398
|
originals.set(node, record);
|
|
2135
2399
|
}
|
|
2136
2400
|
if (node.data !== record.written) record.original = node.data;
|
|
2137
|
-
const next = node.data.replace(
|
|
2401
|
+
const next = node.data.replace(TURN_STATUS_COPY_RE, statusText);
|
|
2138
2402
|
if (node.data !== next) {
|
|
2139
2403
|
node.data = next;
|
|
2140
2404
|
record.written = next;
|
|
@@ -2337,7 +2601,7 @@ var __dshcfBundle = (() => {
|
|
|
2337
2601
|
[
|
|
2338
2602
|
React.createElement("span", { className: "dshcf-settings-headText" }, [
|
|
2339
2603
|
React.createElement("span", { className: "dshcf-settings-name" }, "\u72B6\u6001\u63D0\u793A\u8BCD"),
|
|
2340
|
-
React.createElement("span", { className: "dshcf-settings-description" }, "\u81EA\u5B9A\u4E49\u72B6\u6001\u63D0\u793A\u8BCD\uFF0C\u53EF\u4EE5\u66FF\u6362\u539F\u6709\
|
|
2604
|
+
React.createElement("span", { className: "dshcf-settings-description" }, "\u81EA\u5B9A\u4E49\u72B6\u6001\u63D0\u793A\u8BCD\uFF0C\u53EF\u4EE5\u66FF\u6362\u539F\u6709\u7684\u8FD0\u884C\u72B6\u6001\u6587\u6848\uFF08\u6DF1\u5EA6\u6C42\u7D22\u4E2D...\uFF09\uFF0C\u7531\u63D2\u4EF6dsh-auto-collapse\u63D0\u4F9B")
|
|
2341
2605
|
]),
|
|
2342
2606
|
dirty ? React.createElement("span", { className: "dshcf-settings-pending" }, "\u672A\u4FDD\u5B58") : null,
|
|
2343
2607
|
ChevronIcon(open)
|
|
@@ -2358,11 +2622,11 @@ var __dshcfBundle = (() => {
|
|
|
2358
2622
|
className: "dshcf-settings-input",
|
|
2359
2623
|
type: "text",
|
|
2360
2624
|
value: text,
|
|
2361
|
-
placeholder: "
|
|
2625
|
+
placeholder: "\u6DF1\u5EA6\u6C42\u7D22\u4E2D...",
|
|
2362
2626
|
disabled: !writable,
|
|
2363
2627
|
onChange: (event) => edit(event.target.value)
|
|
2364
2628
|
}),
|
|
2365
|
-
React.createElement("p", { className: "dshcf-settings-hint" }, "\u4E3A\u7A7A\u65F6\u6062\u590D\u9ED8\
|
|
2629
|
+
React.createElement("p", { className: "dshcf-settings-hint" }, "\u4E3A\u7A7A\u65F6\u6062\u590D\u5B98\u65B9\u9ED8\u8BA4\u6587\u6848\uFF08\u6DF1\u5EA6\u6C42\u7D22\u4E2D...\uFF09")
|
|
2366
2630
|
]),
|
|
2367
2631
|
React.createElement("div", { className: "dshcf-settings-footer" }, [
|
|
2368
2632
|
failed ? React.createElement("p", { className: "dshcf-settings-failed", role: "status" }, "\u672C\u90E8\u7F72\u6CA1\u6709\u63A5\u53D7\u8FD9\u4E9B\u503C\uFF0C\u5DF2\u4FDD\u7559\u4F9B\u4F60\u4FEE\u6539\u3002") : null,
|
package/lib/index.js
CHANGED
|
@@ -1,24 +1,55 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
3
2
|
const name = "dsh-auto-collapse";
|
|
4
3
|
const inject = [];
|
|
5
4
|
const DEFAULT_STATUS_TEXT = "Deep sleeping...";
|
|
6
|
-
const AUTO_COLLAPSE_SETTINGS_NAMESPACE =
|
|
5
|
+
const AUTO_COLLAPSE_SETTINGS_NAMESPACE = "dsh-auto-collapse";
|
|
7
6
|
const AUTO_COLLAPSE_SETTINGS_SCHEMA = z.object({
|
|
8
7
|
statusText: z.string().default(DEFAULT_STATUS_TEXT)
|
|
9
8
|
});
|
|
9
|
+
const FIBER_DISPOSED = 4;
|
|
10
|
+
const FIBER_UNLOADING = 5;
|
|
11
|
+
function isUnloading(ctx) {
|
|
12
|
+
const state = ctx?.fiber?.state;
|
|
13
|
+
return state === FIBER_UNLOADING || state === FIBER_DISPOSED;
|
|
14
|
+
}
|
|
15
|
+
function installSettingsSection(ctx, ns, schema, entry, hooks) {
|
|
16
|
+
;
|
|
17
|
+
ctx.inject(["settings"], (sctx) => {
|
|
18
|
+
const scope = sctx.settings.register(ns, schema, {
|
|
19
|
+
base: entry,
|
|
20
|
+
...hooks.validate === void 0 ? {} : { validate: hooks.validate }
|
|
21
|
+
});
|
|
22
|
+
hooks.setSource(() => scope.get());
|
|
23
|
+
sctx.effect(() => () => {
|
|
24
|
+
if (isUnloading(ctx)) return;
|
|
25
|
+
hooks.setSource(() => entry);
|
|
26
|
+
hooks.onChange();
|
|
27
|
+
});
|
|
28
|
+
hooks.onChange();
|
|
29
|
+
scope.watch(() => {
|
|
30
|
+
if (isUnloading(ctx)) return;
|
|
31
|
+
hooks.onChange();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
10
35
|
function apply(ctx, config = {}) {
|
|
11
36
|
let current = () => ({ statusText: config.statusText ?? DEFAULT_STATUS_TEXT });
|
|
12
|
-
installSettingsSection(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
37
|
+
installSettingsSection(
|
|
38
|
+
ctx,
|
|
39
|
+
AUTO_COLLAPSE_SETTINGS_NAMESPACE,
|
|
40
|
+
AUTO_COLLAPSE_SETTINGS_SCHEMA,
|
|
41
|
+
{
|
|
42
|
+
statusText: config.statusText ?? DEFAULT_STATUS_TEXT
|
|
17
43
|
},
|
|
18
|
-
|
|
19
|
-
|
|
44
|
+
{
|
|
45
|
+
setSource: (source) => {
|
|
46
|
+
current = source;
|
|
47
|
+
},
|
|
48
|
+
onChange: () => {
|
|
49
|
+
void current;
|
|
50
|
+
}
|
|
20
51
|
}
|
|
21
|
-
|
|
52
|
+
);
|
|
22
53
|
}
|
|
23
54
|
export {
|
|
24
55
|
apply,
|
package/lib/types/fold.d.ts
CHANGED
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
* - 全部完成 → 标题 = 类型总结(编辑了文件 / 运行了命令 / 已思考 /
|
|
13
13
|
* 上下文注入),摘要清空;出错 → 红色,中断 → 琥珀。
|
|
14
14
|
*
|
|
15
|
-
* 另外把官方 ChatView
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* 另外把官方 ChatView 尾部的运行状态行文字(新版 "深度求索中..." / 旧版
|
|
16
|
+
* "Deep diving...",见 TURN_STATUS_COPY_RE)替换为可配置的状态提示词
|
|
17
|
+
* (默认 "Deep sleeping...";流光特效在 CSS 上,替换文本节点不影响,
|
|
18
|
+
* 宿主追加的用时后缀原样保留)。React 重渲染会恢复原文,pass() 每轮自愈改回。
|
|
19
|
+
* 设置为空时不替换,等价于恢复官方原文。
|
|
19
20
|
*
|
|
20
21
|
* 点击一行展开,再点收起;折叠态下若有行被选中(详情联动)自动展开。
|
|
21
22
|
*
|
|
@@ -80,6 +81,7 @@ export declare class FoldController {
|
|
|
80
81
|
private bodyTextCache;
|
|
81
82
|
/** 自上次 pass 以来子树发生变化的 flow 顶层消息;pass 开头统一失效。 */
|
|
82
83
|
private dirtyMessages;
|
|
84
|
+
/** 在途显示动画(元素 → 记录):冲突仲裁、记账对齐与生命周期清理的依据。
|
|
83
85
|
/** 在途显示动画(元素 → 记录):冲突仲裁、记账对齐与生命周期清理的依据。
|
|
84
86
|
* 用 Map 不用 WeakMap——switchFlow/stop 需要遍历全量 cancel。 */
|
|
85
87
|
private pendingAnims;
|
|
@@ -98,6 +100,20 @@ export declare class FoldController {
|
|
|
98
100
|
* attributes 批次(流式文本、data-state 翻转)不改变块结构,跳过全量
|
|
99
101
|
* querySelectorAll 重扫(issue #14:长会话下每轮重扫造成主线程卡顿)。 */
|
|
100
102
|
private structureDirty;
|
|
103
|
+
/** 本轮 pass 的原生 turn 摘要快照(turn id → 开合态):每轮重建,不跨轮复用;
|
|
104
|
+
* data-open 翻转不改变块结构也能驱动重算,见 buildNativeTurnMap。 */
|
|
105
|
+
private nativeTurns;
|
|
106
|
+
/** 被折叠掏空后藏起的中间包装层(真机 44px vs 28px 真因:think 行全隐后
|
|
107
|
+
* 其父容器变零高度空壳,仍作为 flex item 参与父级 gap,凭空多出 16px)。
|
|
108
|
+
* 内容恢复(展开/流式追加/块转世)时同函数恢复显示;switchFlow 清空。 */
|
|
109
|
+
private emptiedWrappers;
|
|
110
|
+
/** 本轮 pass 内有过 display 实写(hide/restore 瞬时路径)。空洞发现的触发
|
|
111
|
+
* 条件之一:纯 display 收放不产生 childList,不触发 structureDirty。
|
|
112
|
+
* settle 触发的后续 pass 则由 settleFired 覆盖。读后即清(见发现调用点)。 */
|
|
113
|
+
private displayTouched;
|
|
114
|
+
/** 有 fade 自然结算过(chipSettle 跑过)。结算本身不写 display,但它意味着
|
|
115
|
+
* 某行刚变隐藏——空洞可能刚形成。 */
|
|
116
|
+
private settleFired;
|
|
101
117
|
/** 滚动稳定化(issue #14):flow 最近的滚动容器缓存(按 flow 身份失效)。 */
|
|
102
118
|
private scrollContainer;
|
|
103
119
|
private scrollContainerFlow;
|
|
@@ -165,7 +181,39 @@ export declare class FoldController {
|
|
|
165
181
|
private createProcessedRow;
|
|
166
182
|
private syncProcessedRow;
|
|
167
183
|
private placeProcessedRow;
|
|
184
|
+
/** 原生收起时隐藏本块插件自有 overlay(chip/合并行+内容块):只做
|
|
185
|
+
* display 直写,不清钉住之外的账本、不删展开态,供原生再展开后复用。
|
|
186
|
+
* 在途 WAAPI 动画不追踪取消——收尾回调只做幂等终态对齐,无残留。 */
|
|
187
|
+
private hideOverlayForNativeCollapse;
|
|
168
188
|
private reconcileBlock;
|
|
189
|
+
/**
|
|
190
|
+
* 掏空包装层对账(真机 44px vs 28px 真因修复)。
|
|
191
|
+
*
|
|
192
|
+
* 折叠把某容器的子行全部 display:none 后,该容器变零高度空壳,但仍是
|
|
193
|
+
* 父级 flex 的 item 并参与 gap(如正文体 flex-column gap:16px),凭空多出
|
|
194
|
+
* 一份间距。隐藏这类空壳可让 gap 塌缩;子内容恢复可见(展开/流式追加/
|
|
195
|
+
* 块转世)时恢复显示。
|
|
196
|
+
*
|
|
197
|
+
* 安全边界:
|
|
198
|
+
* - 只看 block.host 内部、不含 host 本人(宿主显隐归 segment 逻辑);
|
|
199
|
+
* - 跳过结构 seat(data-chat-flow-kind / data-chat-anchor-key)与插件
|
|
200
|
+
* 自身 overlay(dshcf- 前缀类);
|
|
201
|
+
* - 只隐藏内容空洞(无可见子内容):零高度无文本的空壳 display:none 与
|
|
202
|
+
* 保持显示像素一致,只塌缩父级 gap;任何一方恢复内容即恢复显示。
|
|
203
|
+
*/
|
|
204
|
+
/**
|
|
205
|
+
* 空洞包装层兜底发现:按宿主直扫 div(仅结构变化轮次调用)。
|
|
206
|
+
* 只做隐藏侧:空洞即藏并记入 tracked;恢复侧由 syncEmptiedWrappers 的
|
|
207
|
+
* tracked 复核与 audit 承担。与按行 walk 共用同一安全边界(结构 seat /
|
|
208
|
+
* 插件 overlay / 宿主本人不碰)。
|
|
209
|
+
*/
|
|
210
|
+
private discoverHollowWrappers;
|
|
211
|
+
private syncEmptiedWrappers;
|
|
212
|
+
/** 内容空洞判定:无可见子内容(文本/元素任一可见即非空洞)。隐藏者是谁
|
|
213
|
+
* 不重要——零高度无文本节点 display:none 与保持显示像素一致(只塌缩父级
|
|
214
|
+
* gap,正是要修的幻影);任何一方恢复内容,下轮 pass/audit 即恢复显示。
|
|
215
|
+
* isDisplayed 走 getComputedStyle(桩内退化为内联,语义一致)。 */
|
|
216
|
+
private isHollow;
|
|
169
217
|
private ensureChip;
|
|
170
218
|
private suppressBlock;
|
|
171
219
|
private retainDisplayControl;
|
|
@@ -249,6 +297,17 @@ export declare class FoldController {
|
|
|
249
297
|
* 淡完 onfinish 写 display:none 并保持双条目(镜像 hideElement 终态契约)。
|
|
250
298
|
* fill:'forwards' 占位到终态写入后释放;无几何锁、无 gap 补偿。 */
|
|
251
299
|
private startFadeCollapse;
|
|
300
|
+
/**
|
|
301
|
+
* 收起 fade 终态结算(onfinish 与僵尸收割共用)。仅被同元素新动画
|
|
302
|
+
* supersede 时早退;被 sweep 删账(元素已断连)或收割时仍执行 settle——
|
|
303
|
+
* 回调经 controller 可达,动画必然触发 onfinish,早退会把 chip 内联 16px
|
|
304
|
+
* 钉住永久残留。重挂载同节点不可能(React 只建新节点),重启动的新动画
|
|
305
|
+
* 由第一条守卫覆盖,无误伤。
|
|
306
|
+
*/
|
|
307
|
+
private finishFadeCollapse;
|
|
308
|
+
/** 动画是否已播完(终态未结算):finish 事件丢失时的兜底判定。WAAPI 桩
|
|
309
|
+
* 无 playState/effect 时一律 false,走正常事件路径,测试行为不变。 */
|
|
310
|
+
private isAnimOverdue;
|
|
252
311
|
/** 轻量视觉 reveal(opacity + 4px 微位移):用于插件全资元素的即时显示
|
|
253
312
|
* 路径——chip(一级展开时出现)与 merged-think 行(二级展开时出现)。
|
|
254
313
|
* 这些元素的 display 完全由插件直写、无 React 协调竞争,因此不入
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
/** Host 插件名。 */
|
|
2
2
|
export declare const name = "dsh-auto-collapse";
|
|
3
|
-
/** Host 侧不注入额外服务;settings
|
|
3
|
+
/** Host 侧不注入额外服务;settings 是可选的运行能力,由下方接线惰性接入。 */
|
|
4
4
|
export declare const inject: string[];
|
|
5
5
|
/** 插件配置。 */
|
|
6
6
|
export interface Config {
|
|
7
7
|
/** 自定义状态提示词;留空恢复官方 "Deep diving..."。 */
|
|
8
8
|
statusText?: string;
|
|
9
9
|
}
|
|
10
|
+
/** settings 消费者钩子,与官方 SettingsSectionHooks 同形。 */
|
|
11
|
+
export interface SettingsSectionHooks<T> {
|
|
12
|
+
setSource(source: () => T): void;
|
|
13
|
+
onChange(): void;
|
|
14
|
+
validate?(value: T): void;
|
|
15
|
+
}
|
|
10
16
|
/** Host 插件体:注册设置命名空间。 */
|
|
11
17
|
export declare function apply(ctx: unknown, config?: Config): void;
|
package/package.json
CHANGED
|
@@ -1,69 +1,68 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-auto-collapse",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "DeepSeek Harness Web 客户端插件:把会话里的工具卡片与 Think 推理块折叠成一行,折叠态实时显示当前正在进行的工作(工具名 + 正在执行的命令/参数、或思考内容),运行中带流光动画;点击展开/收起。让界面只保留模型说的话。",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/a179-sanae/dsh-auto-collapse.git"
|
|
9
|
-
},
|
|
10
|
-
"homepage": "https://github.com/a179-sanae/dsh-auto-collapse#readme",
|
|
11
|
-
"bugs": {
|
|
12
|
-
"url": "https://github.com/a179-sanae/dsh-auto-collapse/issues"
|
|
13
|
-
},
|
|
14
|
-
"main": "lib/index.js",
|
|
15
|
-
"types": "lib/types/index.d.ts",
|
|
16
|
-
"exports": {
|
|
17
|
-
".": {
|
|
18
|
-
"types": "./lib/types/index.d.ts",
|
|
19
|
-
"default": "./lib/index.js"
|
|
20
|
-
},
|
|
21
|
-
"./client": {
|
|
22
|
-
"types": "./lib/types/client.d.ts",
|
|
23
|
-
"default": "./lib/client.js"
|
|
24
|
-
},
|
|
25
|
-
"./package.json": "./package.json"
|
|
26
|
-
},
|
|
27
|
-
"dsh": {
|
|
28
|
-
"bundle": {
|
|
29
|
-
"patch": "./cordis.patch.yml"
|
|
30
|
-
},
|
|
31
|
-
"client": {
|
|
32
|
-
"inject": [
|
|
33
|
-
"@deepseek-ai/dsh-client-
|
|
34
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
35
|
-
],
|
|
36
|
-
"platform": "web"
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "node build.mjs",
|
|
41
|
-
"deploy": "node deploy.mjs",
|
|
42
|
-
"test": "node test/run-all.mjs",
|
|
43
|
-
"typecheck": "tsc --noEmit --pretty false",
|
|
44
|
-
"check": "npm run typecheck && npm test",
|
|
45
|
-
"prepack": "node build.mjs"
|
|
46
|
-
},
|
|
47
|
-
"files": [
|
|
48
|
-
"lib",
|
|
49
|
-
"cordis.patch.yml"
|
|
50
|
-
],
|
|
51
|
-
"keywords": [
|
|
52
|
-
"dsh",
|
|
53
|
-
"deepseek-harness",
|
|
54
|
-
"client-plugin",
|
|
55
|
-
"ui",
|
|
56
|
-
"collapse",
|
|
57
|
-
"auto-collapse",
|
|
58
|
-
"chat"
|
|
59
|
-
],
|
|
60
|
-
"license": "MIT",
|
|
61
|
-
"devDependencies": {
|
|
62
|
-
"esbuild": "0.25.0",
|
|
63
|
-
"typescript": "5.9.3"
|
|
64
|
-
},
|
|
65
|
-
"peerDependencies": {
|
|
66
|
-
"@deepseek-ai/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-auto-collapse",
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "DeepSeek Harness Web 客户端插件:把会话里的工具卡片与 Think 推理块折叠成一行,折叠态实时显示当前正在进行的工作(工具名 + 正在执行的命令/参数、或思考内容),运行中带流光动画;点击展开/收起。让界面只保留模型说的话。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/a179-sanae/dsh-auto-collapse.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/a179-sanae/dsh-auto-collapse#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/a179-sanae/dsh-auto-collapse/issues"
|
|
13
|
+
},
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./client": {
|
|
22
|
+
"types": "./lib/types/client.d.ts",
|
|
23
|
+
"default": "./lib/client.js"
|
|
24
|
+
},
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"dsh": {
|
|
28
|
+
"bundle": {
|
|
29
|
+
"patch": "./cordis.patch.yml"
|
|
30
|
+
},
|
|
31
|
+
"client": {
|
|
32
|
+
"inject": [
|
|
33
|
+
"@deepseek-ai/dsh-client-modules",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
35
|
+
],
|
|
36
|
+
"platform": "web"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "node build.mjs",
|
|
41
|
+
"deploy": "node deploy.mjs",
|
|
42
|
+
"test": "node test/run-all.mjs",
|
|
43
|
+
"typecheck": "tsc --noEmit --pretty false",
|
|
44
|
+
"check": "npm run typecheck && npm test",
|
|
45
|
+
"prepack": "node build.mjs"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"lib",
|
|
49
|
+
"cordis.patch.yml"
|
|
50
|
+
],
|
|
51
|
+
"keywords": [
|
|
52
|
+
"dsh",
|
|
53
|
+
"deepseek-harness",
|
|
54
|
+
"client-plugin",
|
|
55
|
+
"ui",
|
|
56
|
+
"collapse",
|
|
57
|
+
"auto-collapse",
|
|
58
|
+
"chat"
|
|
59
|
+
],
|
|
60
|
+
"license": "MIT",
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"esbuild": "0.25.0",
|
|
63
|
+
"typescript": "5.9.3"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
67
|
+
}
|
|
68
|
+
}
|