🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

联合会话视图 RyeSama/dsh-union-view

DeepSeek Harness
RyeSama10 天前有提交已提供中文文档扫描:含敏感能力

并排查看多个对话,还能焊接历史继续聊

29.3/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/28 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add RyeSama/dsh-union-view
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
dsh 原生插件 · tool
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 10 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件
查看扫描证据(共 8 处,展示前 8 条)
  • 读取本地文件
    .dsh-plugin/index.mjs:27
    if (!existsSync(SESSIONS_ROOT)) return out
  • 读取本地文件
    .dsh-plugin/index.mjs:28
    for (const proj of readdirSync(SESSIONS_ROOT, { withFileTypes: true })) {
  • 读取本地文件
    .dsh-plugin/index.mjs:33
    entries = readdirSync(projPath, { withFileTypes: true })
  • 读取本地文件
    .dsh-plugin/index.mjs:40
    if (existsSync(join(dir, LOG_NAME))) out.push({ id: s.name, dir })
  • 读取本地文件
    .dsh-plugin/index.mjs:48
    const text = zstdDecompressSync(readFileSync(join(dir, LOG_NAME))).toString('utf8')
  • 写入本地文件
    .dsh-plugin/index.mjs:58
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    .dsh-plugin/index.mjs:63
    writeFileSync(join(dir, LOG_NAME), Buffer.concat(frames))
  • 读取本地文件
    .dsh-plugin/index.mjs:96
    const st = statSync(join(s.dir, LOG_NAME))
扫描于 2026/10/31(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

README

由 DeepSeek 最新模型翻译生成
联合视图(dsh-union-view) 把好几个对话并排摊在屏幕上一起看;需要的话,还能把它们的历史装进一个新对话接着聊。 当前版本 0.4.0 · 更新记录 · MIT 先说这东西解决什么 假设你最近在干一件事,但聊得很散: - 上周定需求,在 A 对话里 - 中间反复试错,在 B 对话里 - 最后结论和踩过的坑,记在 C 对话里 现在你想回顾整件事。怎么办?点开 A 翻一遍,退出来,点开 B 翻一遍,退出来,点开 C 翻一遍。翻到 C 的时候,A 里说的什么已经忘了一半。 联合视图就是为这个做的:勾上 A、B、C,三份对话同时摊在同一个屏幕上,左右对照着看。 ┌ 对话 GAL视窗 [联合视图] 轨迹 ┐ │ 联合视图 3 个会话 · 1500 条消息 · 只读 │ │ ─── 定需求 ───┬─── 试错 ───┬─── 结论 ─── │ │ 你 14:02 │ AI 15:31 │ AI 23:44 │ │ 我想做… │ 报错了:… │ 所以最终…
展开完整文档(README 剩余 1 节)
联合视图(dsh-union-view)

把好几个对话并排摊在屏幕上一起看;需要的话,还能把它们的历史装进一个新对话接着聊。

当前版本 0.4.0 · 更新记录 · MIT

先说这东西解决什么

假设你最近在干一件事,但聊得很散:

- 上周定需求,在 A 对话里
- 中间反复试错,在 B 对话里
- 最后结论和踩过的坑,记在 C 对话里

现在你想回顾整件事。怎么办?点开 A 翻一遍,退出来,点开 B 翻一遍,退出来,点开 C 翻一遍。翻到 C 的时候,A 里说的什么已经忘了一半。

联合视图就是为这个做的:勾上 A、B、C,三份对话同时摊在同一个屏幕上,左右对照着看。

┌ 对话 GAL视窗 [联合视图] 轨迹 ┐ │ 联合视图 3 个会话 · 1500 条消息 · 只读 │ │ ─── 定需求 ───┬─── 试错 ───┬─── 结论 ─── │ │ 你 14:02 │ AI 15:31 │ AI 23:44 │ │ 我想做… │ 报错了:… │ 所以最终… │ └───────────────┴────────────┴─────────────┘

三种摆法

浏览器里点一下就能换,随时切:

| 摆法 | 什么时候用 |
| 时间线 | 想知道"当时到底先发生什么、后发生什么" |
| 分栏 | 想知道"同一时间,两边各自在干嘛" |
| 轮次对齐 | 想知道"同一个问题,两边分别怎么回答的" |

时间线分栏轮次对齐

最重要的一件事:它只是"看"

用联合视图的时候,它只读不写:

- 原对话里一个字都不会被改
- 不会把几份对话合并、搬来搬去
- AI 完全不知道你在这么看 —— 就像你把三份打印稿摊在桌上比对,桌上那台电脑(AI)并不知道

所以它很安全,随便试:看完关掉,世界和之前一模一样。

消息太多时它也不会卡死——每个视角只摆最近一段,上面会提示"更早的 N 条已折叠"。

它只显示你和 AI 说的话

对话记录里其实混着三种你不关心的东西,联合视图全部滤掉:

| 滤掉的 | 长什么样 | 为什么要滤 |
| AI 的思考过程 | 一大段"我需要先……所以我打算……" | 又长又碎,把真正的结论淹了 |
| 工具调用 | [调用 read_image]、[调用 pwsh] | 一行一条,几十条连着刷屏 |
| 系统注入 | Time sampled while preparing turn 12…、<AEGIS_DSH_ROUTING_BOOTSTRAP>、Current working directory… | 这些是宿主塞给 AI 的,不是你说的话,混在你的对话框里看着莫名其妙 |

所以一屏看下去只剩干净的对话。代价是:如果那个对话里 AI 光在调工具没怎么说话,会显得内容很少——它本来就没说几句话。

如果你想让 AI 真的看见:/weld

会写东西的一步。落在你自己机器上:新建一个对话,原对话只读。只是并排看的话不需要它。

上面说了,联合视图 AI 是看不见的。要让它真的看见,用 /weld。

最省事的用法:在联合视图里勾几个对话,工具栏右边填「每个取最近 N 轮」,点「合并为新会话」。它替你把这些填进输入框并提交,下面那行提示写着结果。等一两秒,左边对话列表最上面会多出一个「未命名」,点开就是合并好的新对话。

也可以手打:

/weld 3f2a1b 9c8d7e --turns 20

(3f2a1b、9c8d7e 是两个对话的编号前缀,--turns 20 表示每个对话只取最近 20 轮)

不打参数直接 /weld 回车,会列出最近 12 个对话的编号、时间和第一句话,照着抄就行。

它会真的造出一个新对话,把这两个对话的历史装进去。这个新对话:

- 出现在你的对话列表里,点开就能看
- 可以接着聊 —— 这时候 AI 是真的看得见这些历史了
- 原对话一个字没改(只是被读了一遍)
- 思考过程和工具调用也不带进去,只装你和 AI 说过的话

✅ 现在这条路是通的:dsh 打开新对话时会校验日志结构,所以新对话不再由我们拼,而是先建一个空对话、把合并好的内容一条条交给 dsh 自己写进去 —— 结构天然合法。视图那部分一直是稳的。

打个比方:

| | 联合视图 | /weld |
| 类比 | 把几份文件摊在桌上,自己比对 | 把几份文件装订成一本新的,递给 AI |
| AI 看得见吗 | 看不见 | 看得见 |
| 会多出一个对话吗 | 不会 | 会 |
| 原对话会变吗 | 不会 | 不会 |

为什么一定要 --turns:两个几百条消息的对话,加起来一万多条,AI 一次吃不下。所以必须截断,只取最近若干轮。

新对话的标题会显示「未命名」,因为原来的标题带引用关系,截断后对不上,干脆不要了。改个名字就行。

新对话里到底装了什么

只有你和 AI 说过的话。剥掉的是这四类:

| 剥掉的 | 为什么 |
| AI 的思考过程 | 又长又碎,而且它本来就不该算"说过的话"。内容块和 data.stream 里的逐 token 回放都要剥,漏一处就会从重建的节点里冒回来 |
| 工具调用与工具结果 | [调用 read_image] 这类记录,几十条连着刷屏,对接着聊没有价值 |
| 图片卸载、上下文压缩 | 这些是原对话的窗口管理动作,引用已经指向不存在的位置,带过去会让日志校验失败 |
| 会话开头重复的初始化事件 | 每个源都有一份,装几份进去结构就乱了 |

代价:如果某个源对话里 AI 光在调工具、没怎么说话,装进新对话的内容就会很少——原文本来如此,不是丢数据。

怎么知道对话的编号

通常在联合视图里点按钮就够了,不用记编号。真要手打,/weld 不带参数回车会列出来;也可以去 .dsh/sessions/ 目录下看,每个对话是一个以 session- 开头的文件夹,文件夹名就是编号。

安装

用插件管理器:

dsh plugin install dsh-union-view

装完重启 DSH Web(或刷新页面),对话页面的标签栏会多出一个「联合视图」。

从源码装

pnpm 用不了的时候走这条路:

node scripts/build-client.mjs # 把界面代码打包成 .dsh-plugin/client.js
node scripts/install-local.mjs # 装进 DSH

第二行脚本做三件事:把整个项目复制到 ~/.dsh/plugins/dsh-union-view、在 DSH 的 node_modules 里建一个目录联接(不需要管理员权限)、把插件名登记进配置。会先备份配置文件,可以反复跑。

用法

1. 打开任意对话,点顶部的「联合视图」标签
2. 点「选择会话」,勾上你想看的对话(可以多选,下次会自动记住)
3. 换摆法看
4. 「系统事件」开关:打开后能看到更细的过程(工具调用、轮次边界),平时关着更清爽

它不做什么

列清楚,免得误会:

- 不能在里面发消息:它是只读窗口。要接着聊,切回原对话,或者用 /weld 造一个新对话
- 不会自动更新:新开的对话不会自己冒出来,点一下「刷新」
- 不显示思考过程:只显示你和 AI 的正式发言,AI 的推理过程不带
- 看不到已折叠的历史:如果某个对话之前被压缩过(上下文太长时 DSH 会折叠旧内容),联合视图看到的也是折叠后的样子
- 不做对比和评分:它只是并排显示,不计算差异、不判断谁对谁错

给想改代码的人

展开技术细节

界面部分怎么挂上去

注册在 DSH 的 conversation.view 槽位(id: 'union',order: 6),和官方 gal-view 是同一个位置。

关键点:src/client.js 里必须写

export const inject = ['slots', 'sessions', 'uiConversation']

少写一个,DSH 就不会把对应服务挂到插件身上,ctx.sessions 永远是 undefined(插件会显示"宿主未暴露 sessions 服务")。package.json 里的 dsh.client.inject: ["slots"] 只负责让界面代码被加载,两回事。

数据从哪来

const ref = ctx.sessions.retain(sessionId, { source: 'unionView' })
await ref.ready
const chat = ctx.uiConversation.binding(ref.binding).target('chat')
const nodes = chat.getSnapshot().legacy.nodes // 对话节点数组

纯客户端读取,只用 retain 的引用计数,不调 activate、不 prompt、不 fork。

怎么铺满整个对话区

根节点加 data-union-view,useEffect 往上找第一个 position !== 'static' 的祖先(实测是 .wSkVaW_body),给它加上 data-union-fill,定位规则写在注入的 CSS 里。

踩过的坑:定位一定要写在 CSS 里,不能写内联 style——内联的 position: relative 会盖掉 CSS 里的 absolute,面板会按内容高度一路涨到十几万像素。

/weld 怎么写入新对话

DSH 有个 ctx.sessions.create(),但它只准备内存里的会话对象,不落盘、不进侧栏账本(源码注释写得很直白:persistence is a plugin concern)。

手写日志这条路走不通:宿主对从磁盘读回的日志有很严的结构校验(turn 必须连续、引用不能悬空、assistant 消息要带 stream),照着报错猜了四轮结构,一次都没过。

现在的做法是让 dsh 自己写:

1. sessionPersistence.create({ version, id, createdAt, cwd, isSeeded: false, delegationDepth: 0 }) 建日志文件和写句柄。这一步不能省:持久化插件靠 writers.get(session.id) 路由 session/event,没有写句柄时 flush 会静默返回——账本有 id、磁盘没文件。
2. sessions.create() 建活会话,把合并好的事件一条条 append()。seq 由宿主自己发,天然连续,每一步还有 surfaceManager.validateNext 校验。
3. handle.flush() 落盘。

好处是报错从「turn/start does not open the expected turn」这种无从下手的话,变成了「image/offload: target seq 87 is not a current surface node」——直接点名事件类型和序号。

走这条路顺带踩到三个坑:图片卸载/上下文压缩这类事件带着指向 surface node 的引用,合并后必然悬空(直接丢弃);assistant/message.data.stream 不能整段删(重建节点时会 for..of,字段没了就报 stream is not iterable),要留空数组;header 载荷不要带 type(会被判 unexpected field type),但必须带 version。

侧栏账本:对话列表不是扫目录得来的,是读 ~/.dsh/storages/workspace.json 里的 sessionIds 数组。所以最后还要调 workspaceRegistry.attachSession(newId),它会校验 header 里的 cwd 和真实路径一致,通过后才把编号插进数组。

改 workspace.json 只能用 JSON 解析器改。用正则删数组元素会留下孤立逗号,JSON 一坏,整个界面会变成「暂无会话」。

目录结构

src/client.js 界面(联合会话视图、三布局、合并按钮) src/merge.mjs 合并逻辑(纯函数,不碰磁盘) .dsh-plugin/index.mjs Node 半边(注册 /weld 命令、写入新对话) scripts/build-client.mjs 打包界面代码 scripts/install-local.mjs 本机安装 scripts/cleanup-test.mjs 清理测试会话(从账本和磁盘一起删) scripts/e2e-verify.mjs 自动验收:三布局截图(需要浏览器调试端口) scripts/probe-weld.mjs 自动验收:打开合并出来的新对话,检查有没有加载错误

已知限制

- 只读视图最多渲染最近 400 条消息(分栏每列独立计算);轮次对齐最多 120 轮
- 消息正文按纯文本显示,保留 **、反引号原样——这是有意的,转录要保真
- 没有跨对话搜索、没有导出
- /weld 生成的新对话,第一批请求的缓存要从零建立(新对话全新前缀,这个省不掉)
- /weld 每个源只取最近 N 轮:两个几百条消息的对话加起来上万条,不截断会顶满窗口
- /weld 生成的对话标题显示「未命名」:原标题带引用关系,截断后对不上,干脆不搬
- /weld 是数百毫秒到几十秒的操作(几千条事件逐条交给宿主写),中间没有进度条,等侧栏冒出新对话即可
- 同一个对话同时只能有一个写入者:如果目标对话正被另一个 DSH 实例占用,命令会报 session/writer-held,换一个空闲对话提交即可

许可

MIT
预览分享卡片长什么样 ↓RyeSama/dsh-union-view
分享 · DeepSeek Harness Hub
RyeSama/dsh-union-view

并排查看多个对话,还能焊接历史继续聊

回到顶部