@huanlin/dsh-plugin-merge-tool-calls 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,94 +1,97 @@
1
- <p align="center">
2
- <a href="https://dshfind.com/zh/plugins/huanlinoto/dsh-plugin-merge-tool-calls"><img src="https://dshfind.com/api/card/huanlinoto/dsh-plugin-merge-tool-calls?lang=zh" alt="dsh-plugin-merge-tool-calls card"></a>
3
- </p>
4
-
5
- # dsh-plugin-merge-tool-calls
6
-
7
- 把 WebUI 会话流中**连续相邻**的同工具调用合并为一个「主卡片 + 紧凑子行」的树状展示,减少连续读文件/搜索/编辑/执行命令时多条卡片对流的占据。默认覆盖所有内置通用行工具(`read`/`grep`/`glob`/`edit`/`write`/`bash`/`pwsh`/`web_search`/`web_fetch`/`run_code`/`cordis_package_inspect`/`cordis_runtime_inspect`),也可配置白名单。
8
-
9
- ```
10
- 合并前: 合并后:
11
- [Read] foo ▸ 内容 [Read] · 3 Files ▸(点击展开子行)
12
- [Read] bar ▸ 内容 └ foo(子行,点击展开 foo 的内容)
13
- [Read] baz ▸ 内容 └ bar(子行,点击展开 bar 的内容)
14
- └ baz(子行,点击展开 baz 的内容)
15
- ```
16
-
17
- 主行只显示数量摘要(`Read · 3 Files`),具体文件路径全部下沉到子行;子行默认折叠,点击主行以动画展开/收起。单次调用(无合并)保持原行为(主行直接显示路径并展开内容)。
18
-
19
- 纯展示层合并:会话日志、模型可见数据、详情面板均不受影响;回放确定(合并结果是聊天快照的纯函数)。
20
-
21
- ## 工作原理
22
-
23
- - 通过 keyed slot `tool.call.toolview` 的 shadow 机制(`priority: -1`,最低者渲染)接管工具的卡片;`tools: []` 时接管全部内置通用行工具(见 `src/client/tool-names.ts`),非空列表为显式白名单(任意 wire 名均可)。
24
- - 组件经 `useSession` 读取聊天快照,用 `conversationContextKey('tool-call', callId)` 定位自身节点,在 `chat.order` 上前后扫描连续调用,按 `maxGroupSize` 分组。一组只合并**同名或同家族**调用:同名必合;已知家族(`grep`+`glob`、`bash`+`pwsh`、`read`+`web_fetch`)跨名合;未知工具名(variant `others`)仅与自身合,不相关的工具不会混进同一张卡。
25
- - 组首座位渲染合并卡片;组内其余座位渲染 `null`,由注入的样式规则
26
- `[data-chat-flow-kind="tool-call"]:has([data-slot="tool.call.toolview"]:empty) { display: none; }`
27
- 将其从流中收起(与内置 `.flowItem:empty` 语义一致;渲染器为每个 toolview 包一层
28
- `data-slot` 容器,故以该容器判空)。
29
- - 主卡片镜像内置 ToolRow 的行面:按 variant 渲染标题/图标(Read/Search/Bash/Write/Edit/Code/`Tool call`,工具自定义标题如 `Pwsh`/`Inspect` 优先)。**合并组的主行摘要改为数量**(`Read · 5 Files` / `Search · 3 Queries` / `Bash · 4 Commands` 等,按 variant 选名词),不再显示首个调用的路径;单次调用(无合并)仍显示 args 摘要(读→路径、搜索→query、bash→description/command、未知工具带 `工具名 ·` 前缀),`read`/`write`/`edit` 单行的摘要是可点击文件链接。行状态、错误首行、bash 失败退出码转红点均与内置一致。
30
- - 子行紧凑(20px 高),结构为主行的尾部(`sep 点 + 路径`)。合并组内**所有调用(含首个)**均渲染为子行,所以每个文件路径都在子行上。对齐**运行时自动测量**:组件挂载后量取主行 sep 点相对卡片的偏移(并用 ResizeObserver 跟随字体/布局变化),以 `--mtc-sep-left` 自定义属性设置子行缩进——sep 点与主行 sep 点同列、路径与主行摘要同列,不依赖任何手工字体宽度常量,任意工具名均自动对齐。展开的子行卡片以负向 `calc` 抵消该缩进,左边缘与主卡片内容区对齐(占满卡片宽度,左侧不留白)。无可展开内容(如运行中的 read)的子行渲染为静态行。
31
- - 子行**默认折叠**:主行点击展开时,子行块以 `grid-template-rows: 0fr → 1fr` + 透明度过渡动画滑出(200ms),收起时反向滑回;折叠期间子行仍在 DOM 中(grid 0fr + `overflow: hidden` 收起,不卸载),所以动画平滑无闪烁。每个子行仍可独立点击展开其内容卡片。
32
-
33
- ## 配置
34
-
35
- `cordis.patch.yml` 中的插件行 config:
36
-
37
- | 字段 | 默认 | 说明 |
38
- |------|------|------|
39
- | `tools` | `[]`(全部) | 空数组 = 合并所有内置通用行工具(read/grep/glob/edit/write/bash/pwsh/web_search/web_fetch/run_code/cordis_package_inspect/cordis_runtime_inspect);非空数组 = 显式白名单(任意 wire 名,如 `['read','todo_write']`) |
40
- | `groupBy` | `adjacent` | `adjacent`:流中相邻即可合并;`step`:仅同 agent step |
41
- | `maxGroupSize` | `8` | 每组最多合并数,超出部分自动另起新组 |
42
-
43
- ## 开发
44
-
45
- 前置:本机有 DSH checkout(`../dsh`,只读,仅类型引用)。
46
-
47
- ```sh
48
- pnpm install # 安装 registry 依赖(react/vitest/tsdown/cordis/schemastery…)
49
- pnpm run typecheck # tsc --noEmit;@deepseek-ai/* 类型来自已发布的 0.1.0-rc.6 devDeps
50
- pnpm test # vitest:纯逻辑 + jsdom 组件 + 注册形态
51
- pnpm run build # tsdown + tsc → lib/index.js、lib/invariant.js、lib/client.js、lib/types/
52
- ```
53
-
54
- 注意:`@deepseek-ai/*` 是宿主提供的 peer,开发期以 devDependencies 安装**已发布**的
55
- `0.1.0-rc.6` 类型(其完整依赖图已发布,可直接安装);测试期经
56
- `vitest.config.ts` alias dsh 包指向 `tests/stubs/` 测试替身,组件测试无需宿主包。
57
- Configschemastery schema 使用 `@deepseek-ai/schemastery`(与 DSH 仓库同款,支持
58
- `z.infer`);host bundle 内联 schemastery(与范本 yet-another-subagent 一致),
59
- profile 无需额外解析。
60
-
61
- ## 运行(挂载到 profile)
62
-
63
- 开发热更新(本地 clone,改源码重建 `lib/` 即生效):
64
-
65
- ```sh
66
- dsh plugin --profile web add link:D:\Projects\deepseek-harness\dsh-plugin-merge-tool-calls
67
- ```
68
-
69
- 分发安装(二选一):
70
-
71
- ```sh
72
- dsh plugin --profile web add "github:huanlinoto/dsh-plugin-merge-tool-calls" # 源码分发
73
- dsh plugin --profile web add "@huanlin/dsh-plugin-merge-tool-calls" # npm registry 分发
74
- ```
75
-
76
- 然后由人类重启 `dsh web` 进程并硬刷新浏览器(`Ctrl+Shift+R`)。
77
-
78
- ## 检查
79
-
80
- - `pnpm run typecheck && pnpm test && pnpm run build` 全绿;
81
- - `git -C <dsh checkout> status` 干净(零源码 patch);
82
- - 浏览器验证:连续 3 `read` 主行显示 `Read · 3 Files`,点击主行以动画展开 3 个 `└` 子行(foo/bar/baz 各一行),再点击子行展开内容;
83
- `grep`/`glob` 同理;`write`/`edit` 子行展开显示 diff 卡片、`bash`/`pwsh` 显示终端卡片、
84
- 路径子行可点击在侧边栏预览;中间隔了其他节点的调用保持单卡片。
85
-
86
- ## 边界行为
87
-
88
- - 组内运行中(running)调用:主行显示数量摘要(如 `Read · 3 Files`),子行折叠时不可见,展开后各子行显示对应调用的 args 摘要路径;结果到达后原地更新。
89
- - error / interrupted 调用按内置语义着色(错误首行 / 警告状态点);bash 非零退出码按内置语义转为红色错误点。主行错误时摘要回退为错误首行(不显示数量)。
90
- - `read`/`write`/`edit` 家族的子行摘要是可点击文件链接(与内置行为一致,打开侧边栏预览);grep/glob `path` 参数是搜索目录而非文件,摘要保持纯文本,不会误开目录。单次调用(无合并)的主行摘要是文件链接。
91
- - 组被任何其他节点打断即断开;超过 `maxGroupSize` 的部分另起新组(不丢调用)。
92
- - 非聊天节点场景(如被 dispatch 为子调用)回退为普通单行,绝不空白。
93
- - 带自定义行卡片的工具(`skill`、`cordis_define`、`cordis_run`/`cordis_stop`/`cordis_undefine`,以及摘要格式特殊的 `todo_write`/`ask_user_question`)**不**默认接管,保持内置行;如需合并,用 `tools` 白名单显式加入(合并行按通用行面渲染)。
94
- - 需要浏览器支持 `:has()`(Chrome 105+ / Safari 15.4+ / Firefox 121+);不支持时仅退化为空行间距。
1
+ <p align="center">
2
+ <a href="https://dshfind.com/zh/plugins/huanlinoto/dsh-plugin-merge-tool-calls"><img src="https://dshfind.com/api/card/huanlinoto/dsh-plugin-merge-tool-calls?lang=zh" alt="dsh-plugin-merge-tool-calls card"></a>
3
+ </p>
4
+
5
+ # dsh-plugin-merge-tool-calls
6
+
7
+ 把 WebUI 会话流中**连续相邻**的同工具调用合并为一个「主卡片 + 紧凑子行」的树状展示,减少连续读文件/搜索/编辑/执行命令时多条卡片对流的占据。默认覆盖所有内置通用行工具(`read`/`grep`/`glob`/`edit`/`write`/`bash`/`pwsh`/`web_search`/`web_fetch`/`run_code`/`cordis_package_inspect`/`cordis_runtime_inspect`),也可配置白名单。
8
+
9
+ ```
10
+ 合并前: 合并后:
11
+ [Read] foo ▸ 内容 [Read] · 3 Files ▸(点击展开子行)
12
+ [Read] bar ▸ 内容 └ foo(子行,点击展开 foo 的内容)
13
+ [Read] baz ▸ 内容 └ bar(子行,点击展开 bar 的内容)
14
+ └ baz(子行,点击展开 baz 的内容)
15
+ ```
16
+
17
+ 主行只显示数量摘要(`Read · 3 Files`),具体文件路径全部下沉到子行;子行默认折叠,点击主行以动画展开/收起。单次调用(无合并)保持原行为(主行直接显示路径并展开内容)。
18
+
19
+ 纯展示层合并:会话日志、模型可见数据、详情面板均不受影响;回放确定(合并结果是聊天快照的纯函数)。
20
+
21
+ ## 工作原理
22
+
23
+ - 通过 keyed slot `tool.call.toolview` 的 shadow 机制(`priority: -1`,最低者渲染)接管工具的卡片;`tools: []` 时接管全部内置通用行工具(见 `src/client/tool-names.ts`),非空列表为显式白名单(任意 wire 名均可)。
24
+ - 组件经 `useChat` 读取聊天快照,在 order + node store 中按 call id 定位自身节点,前后扫描连续调用,按 `maxGroupSize` 分组。一组只合并**同名或同家族**调用:同名必合;已知家族(`grep`+`glob`、`bash`+`pwsh`、`read`+`web_fetch`)跨名合;未知工具名(variant `others`)仅与自身合,不相关的工具不会混进同一张卡。
25
+ - 组首座位渲染合并卡片;组内其余座位渲染 `null`,由注入的样式规则
26
+ `[data-chat-flow-kind="tool-call"]:has([data-slot="tool.call.toolview"]:empty) { display: none; }`
27
+ 将其从流中收起(与内置 `.flowItem:empty` 语义一致;渲染器为每个 toolview 包一层
28
+ `data-slot` 容器,故以该容器判空)。
29
+ - 主卡片镜像内置 ToolRow 的行面:按 variant 渲染标题/图标(Read/Search/Bash/Write/Edit/Code/`Tool call`,工具自定义标题如 `Pwsh`/`Inspect` 优先)。**合并组的主行摘要改为数量**(`Read · 5 Files` / `Search · 3 Queries` / `Bash · 4 Commands` 等,按 variant 选名词),不再显示首个调用的路径;单次调用(无合并)仍显示 args 摘要(读→路径、搜索→query、bash→description/command、未知工具带 `工具名 ·` 前缀),`read`/`write`/`edit` 单行的摘要是可点击文件链接。行状态、错误首行、bash 失败退出码转红点均与内置一致。
30
+ - 子行紧凑(20px 高),结构为主行的尾部(`sep 点 + 路径`)。合并组内**所有调用(含首个)**均渲染为子行,所以每个文件路径都在子行上。对齐**运行时自动测量**:组件挂载后量取主行 sep 点相对卡片的偏移(并用 ResizeObserver 跟随字体/布局变化),以 `--mtc-sep-left` 自定义属性设置子行缩进——sep 点与主行 sep 点同列、路径与主行摘要同列,不依赖任何手工字体宽度常量,任意工具名均自动对齐。展开的子行卡片以负向 `calc` 抵消该缩进,左边缘与主卡片内容区对齐(占满卡片宽度,左侧不留白)。无可展开内容(如运行中的 read)的子行渲染为静态行。
31
+ - 子行**默认折叠**:主行点击展开时,子行块以 `grid-template-rows: 0fr → 1fr` + 透明度过渡动画滑出(200ms),收起时反向滑回;折叠期间子行仍在 DOM 中(grid 0fr + `overflow: hidden` 收起,不卸载),所以动画平滑无闪烁。每个子行仍可独立点击展开其内容卡片。
32
+
33
+ ## 配置
34
+
35
+ `cordis.patch.yml` 中的插件行 config:
36
+
37
+ | 字段 | 默认 | 说明 |
38
+ |------|------|------|
39
+ | `tools` | `[]`(全部) | 空数组 = 合并所有内置通用行工具(read/grep/glob/edit/write/bash/pwsh/web_search/web_fetch/run_code/cordis_package_inspect/cordis_runtime_inspect);非空数组 = 显式白名单(任意 wire 名,如 `['read','todo_write']`) |
40
+ | `groupBy` | `adjacent` | `adjacent`:流中相邻即可合并;`step`:仅同 agent step |
41
+ | `maxGroupSize` | `8` | 每组最多合并数,超出部分自动另起新组 |
42
+
43
+ ## 开发
44
+
45
+ 前置:本机有 DSH checkout(`../dsh`,只读,仅类型引用)。
46
+
47
+ ```sh
48
+ pnpm install # 安装 registry 依赖(react/vitest/tsdown/schemastery…)
49
+ pnpm run typecheck # tsc --noEmit;@deepseek-ai/* 类型来自本机 DSH checkout 的 node_modules junction
50
+ pnpm test # vitest:纯逻辑 + jsdom 组件 + 注册形态
51
+ pnpm run build # tsdown + tsc → lib/index.js、lib/client.js、lib/types/
52
+ ```
53
+
54
+ 注意:alpha 版 DSH 未发 npm,`@deepseek-ai/*` peer 类型在开发期经 `node_modules/@deepseek-ai/*`
55
+ junction 指向本机 DSH checkout(`../dsh``vendor/cordis` 与 `packages/**`);`@deepseek-ai/*`
56
+ client half 全部为 type-only import,测试期仅 `dsh-client-ui-primitives`
57
+ `vitest.config.ts`alias 指向 `tests/stubs/` 测试替身,组件测试无需宿主包。
58
+ Config schemastery schema 使用 `@deepseek-ai/schemastery`(与 DSH 仓库同款,支持
59
+ `z.infer`);host bundle 内联 schemastery(与范本 yet-another-subagent 一致),
60
+ profile 无需额外解析。
61
+
62
+ ## 运行(挂载到 profile)
63
+
64
+ 开发热更新(本地 clone,改源码重建 `lib/` 即生效):
65
+
66
+ ```sh
67
+ dsh plugin --profile web add link:D:\Projects\deepseek-harness\dsh-plugin-merge-tool-calls
68
+ ```
69
+
70
+ 分发安装(二选一):
71
+
72
+ ```sh
73
+ dsh plugin --profile web add "github:huanlinoto/dsh-plugin-merge-tool-calls" # 源码分发
74
+ dsh plugin --profile web add "@huanlin/dsh-plugin-merge-tool-calls" # npm registry 分发
75
+ ```
76
+
77
+ 然后由人类重启 `dsh web` 进程并硬刷新浏览器(`Ctrl+Shift+R`)。
78
+
79
+ ## 检查
80
+
81
+ - `pnpm run typecheck && pnpm test && pnpm run build` 全绿;
82
+ - `git -C <dsh checkout> status` 干净(零源码 patch);
83
+ - 浏览器验证:连续 3 个 `read` 主行显示 `Read · 3 Files`,点击主行以动画展开 3 个 `└` 子行(foo/bar/baz 各一行),再点击子行展开内容;
84
+ `grep`/`glob` 同理;`write`/`edit` 子行展开显示 diff 卡片、`bash`/`pwsh` 显示终端卡片、
85
+ 路径子行可点击在侧边栏预览;中间隔了其他节点的调用保持单卡片。
86
+
87
+ ## 边界行为
88
+
89
+ - 组内运行中(running)调用:主行显示数量摘要(如 `Read · 3 Files`),子行折叠时不可见,展开后各子行显示对应调用的 args 摘要路径;结果到达后原地更新。
90
+ - error / interrupted 调用按内置语义着色(错误首行 / 警告状态点);bash 非零退出码按内置语义转为红色错误点。主行错误时摘要回退为错误首行(不显示数量)。
91
+ - `read`/`write`/`edit` 家族的子行摘要是可点击文件链接(与内置行为一致,打开侧边栏预览);grep/glob 的 `path` 参数是搜索目录而非文件,摘要保持纯文本,不会误开目录。单次调用(无合并)的主行摘要是文件链接。
92
+ - 组被任何其他节点打断即断开;超过 `maxGroupSize` 的部分另起新组(不丢调用)。
93
+ - 非聊天节点场景(如被 dispatch 为子调用)回退为普通单行,绝不空白。
94
+ - 带自定义行卡片的工具(`skill`、`cordis_define`、`cordis_run`/`cordis_stop`/`cordis_undefine`,以及摘要格式特殊的 `todo_write`/`ask_user_question`)**不**默认接管,保持内置行;如需合并,用 `tools` 白名单显式加入(合并行按通用行面渲染)。
95
+ - 需要浏览器支持 `:has()`(Chrome 105+ / Safari 15.4+ / Firefox 121+);不支持时仅退化为空行间距。
96
+
97
+ > v0.3.1(适配 DSH v0.1.2-rc.1):按新版 invariant 规则不再发布空样板 `./invariant` 导出(本插件无独立可分歧观察,故不发布 invariant,见 AGENTS.md 新规则)。