Yinxe/dshp

archive/file-change-viewer

接管对话流的 edit/write 行:外壳与折叠沿用官方原生行,展开后每个文件一张代码卡片——文件类型图标、增删统计、块级折叠、高亮/±差异切换、语法高亮统一 diff(行号为文件真实行号),改动两侧多显示可调的上下文行。另带 patch 工具(测试版,默认关):一次调用把多处零散修改/多文件补丁原子化应用(*** Begin Patch 信封,全有或全无)。已归档并冻结在固定版本:作者不再为它适配新版 DSH。

UI 개선deepseek-harnessdshmcwiki

@dshp/file-change-viewer

已归档(2026-10-20),不再维护。 本插件已从 plugins/ 移入 archive/,源码与 lib/ 构建产物仅作历史参考;安装路径为 ./archive/file-change-viewer,后续不再适配新版 DSH;文中「更新」节仅作历史保留。

DeepSeek Harness(DSH)文件修改查看器:接管对话流里的 edit / write 行——行的外壳与折叠行为完全沿用官方原生行(和「思考 / 读取」行同一套样式,默认同样折叠,也可在设置里改成默认展开),展开后每个文件块是一张代码卡片:卡头是「文件类型图标 + 文件名 + 增删统计(图标 + 绿增红删)」并带该块自己的折叠箭头与「高亮 / ± 差异」切换(各带图标),卡内是带语法高亮的统一 diff(新增行绿底、删除行红底、行号是文件里的真实行号,删除行不占号)。每张卡片还会在改动两侧多显示几行没受影响的上下文(默认 3 行,设置里可调 0 / 3 / 5 / 8)——这几行取自文件当前内容,模型只圈 1 行上下文时也看得清改动落在哪。另外它还带一个 patch 工具(测试版,默认关,设置里可开):一次调用把多处零散修改 / 多个文件的补丁应用下去(*** Begin Patch 信封,全有或全无),批量改文件从此不必退化成 bash + sed/python——那种改法在界面上只留一行 Bash,看不见任何 diff,还得整份重写或逐个替换。

全局默认值在设置 → 左侧导航「File Change View」(本插件自己的设置节)里配置——展示方式是两张并排的单选卡,卡里直接画出两种视图的真实效果,另有上下文行数与默认展开开关;这些值持久化在 profile 条目 dshp-file-change-viewer 的 config:(0.1.7 契约:条目导出 Config schema + volatile 字段,设置页经 settings.update 写入即热更新)。会话页头右侧还多了两个只作用于当前会话的快捷开关(一键展开 / 收起本会话的所有文件改动、切换差异视图),它们不改上面的全局默认,也不写持久化配置。

内置的文件修改行是默认折叠且只有 -/+ 行色的:DSH 用 DisclosureRow 承载折叠语义,折叠态下 DiffBlock 根本不挂载,所以「用 CSS 把行撑开」走不通。本插件按官方扩展点注册 tool.call.toolview 的同名 key(官方文档:a key the shipped composition already covers is replaced, not shared),用同一批官方 primitives 重新组装这一行。

设计原则:只改渲染,不碰数据;能不改的就不改。不注册工具、不改任何文件——内容全部来自官方 dsh-tool-fs 已给出的 meta.diffs 与调用参数,行的几何全部来自官方 DisclosureRow 默认样式,配色全部来自官方 token。Host 半只多做两件事:把自己的 settings 命名空间注册进设置系统,并提供两条同源 /ext 路由供设置节读写这两项偏好。

长什么样

▸ 编辑 · src/client/index.ts            +58 -43          ← 原生行:默认折叠(可配成默认展开),与读取/思考行一致
   ╭──────────────────────────────────────────────────╮
   │ ▾ 📄 src/client/index.ts          ⊕37 ⊖26  [⌘高亮][⑂± 差异] │  ← 卡头:类型图标 + 文件名 + 图标化统计 + 两块控件
   │    1  const RAW_MAX_CHARS = 4000;                 │
   │    2                                              │
   │    3  /** 生成「按行上底色」的 CSS 规则。 */        │
   │       -  * 官方 CodeBlock 没有逐行样式的 API,      │  ← 红底 = 删除
   │    4  +  * 官方 CodeBlock 在行号模式下…            │  ← 绿底 = 新增
   ╰──────────────────────────────────────────────────╯
  • 行是原生行:DisclosureRow 默认样式(24px 行高、16px 图标位、13px 标色、hover 图标↔箭头交叉淡出),本插件只把官方 ToolRow 里那几条工具专属样式(分隔点、路径链接、摘要、统计、Inspect 药丸)镜像过来——不自造行外壳、不覆盖背景。
  • 统计绿增红删:+58 用 --dsw-alias-state-success-primary,-43 用 --dsw-alias-state-error-primary(官方行内是单一灰色,这里按要求分色)。
  • 卡片头:官方 FileTypeIcon(按扩展名判类型)+ 文件名 + 图标化增删统计(IconPlusOutline16 / 一图元减号 SVG,绿增红删)+ 该块自己的折叠箭头 + 该块自己的「高亮 / ± 差异」(IconCodeOutline16 / IconBranchOutline16)。官方代码块自带的语言标签栏与复制按钮继续隐藏(不需要)。
  • 编辑行的 leading 图标沿用官方 IconEditOutline16(与内置文件修改行同一个)。
  • 失败时 summary 位只显示首行错误(红字,官方 ToolRow 同做法),不显示路径与统计。

patch 工具(批量 / 零散修改,测试版 · 默认关)

它是测试版能力,默认不注册。 到 设置 → File Change View → 工具 打开「启用 patch 工具(测试版)」, 模型才会看到这个工具;关掉即刻消失(正在进行的那次调用不受影响)。开关值是条目 config: 里的 patchTool 字段(volatile),也可以直接用 composition 的 config: { patchTool: true } 在部署层打开—— 设置页一开一关走 settings.update → loader/volatile-update,不需要重启 dsh web;手工编辑条目 config 则重启生效。

官方的文件工具是 read / write / edit:write 要重发整份文件,edit 一次只能替换一处。所以「一个文件里改 8 处、顺带再改 3 个文件」这种活儿,agent 往往会退化成 bash + sed/python 一把梭——改动藏在命令里:界面上只留一行 Bash,没有 diff、也没法整体回滚。本插件补上这个缺口:一次调用改多处 / 多文件,而不用重写任何一份文件(省 token、不会误覆盖整份文件、每一段改动都进差异卡片可审查)。

补丁格式是 Codex / opencode apply_patch 的 *** Begin Patch 信封,不是 patch(1) 的 unified diff:

patch(patch: "*** Begin Patch
              *** Update File: src/a.ts
              @@
              -const a = 1;
              +const a = 11;
              *** Add File: notes/t.md
              +| a | b |
              *** End Patch")
性质说明
格式*** Begin Patch / *** End Patch 信封,段落是 *** Add File: 与 *** Update File:;一次可带多个文件、每个文件多个 @@ 片段
定位不写行号:片段靠自己的上下文内容定位,五级回退(精确 → 去尾空格 → 两端 trim → Unicode 标点归一 → 忽略一切空白但要求全文件唯一);@@ 锚点 用于消歧——锚点写成本段第一行也认;*** End of File 钉在文件尾
追加只有 + 行的片段(没有上下文、没有删除)追加到文件末尾——这是上游行为,照抄
新建文件*** Add File: <路径> + 整段 + 行,父目录自动创建
原子性全有或全无:先把每个文件读进来、在内存里全部应用成功才落盘;任一片段对不上就整体失败,绝不写一半
后端走 ctx.fs:同样吃沙箱模式、fs/write-intent 观察策略、fs/observed 记账;不绕过 seam
行尾/BOMCRLF 文件归一后应用、写回时恢复整份 CRLF;带 UTF-8 BOM 的文件改完 BOM 还在
范围只做「新建 / 修改」:*** Delete File: 与 *** Move to: 解析层认识、执行层明确拒绝(见下)
渲染结果元数据带 diffs,于是这张调用也用它自己的差异卡片显示(按片段各一张卡、统计只算真正变化的行、行号是落盘时算出的真实行号)

一次调用就成功的几条设计

模型第一次调用 patch 失败、第二次才改对,是最常见的浪费。真实会话里抓到的两类根因,以及对应的处理:

现象根因现在怎么处理
写了 @@ Line 1: Hello DSH,下一行又是 -Line 1: Hello DSH,报「找不到这一段的上下文」模型把 @@ 当小节标题用(像 unified diff 的 hunk 头),而它是锚点;旧实现匹配到锚点后从下一行开始找,于是要改的那一行被锚点本身"吃掉"锚点命中后从锚点自身开始找:锚点是上方一行 → 继续往下找;锚点就是改动行 → 当场命中。工具描述也把「锚点是文件里真实存在的一行、不是标题」写明白了
凭记忆写的那一行与文件只差几个空格(.dashing{ vs .dashing {),报「找不到」前四级回退吃缩进与行尾空白,**吃不了「有没有空格 / 空格在哪」**的差异加第五级:忽略一切空白比较,但配唯一性安全阀——全文件只有一处能对上才采用,两处以上就不猜(宁可失败,也不改错地方)
一个片段里堆了多处删除,但它们不相邻片段语义要求旧内容在文件里连续;模型把它当成「一次批量」报错时点破这一点,并教它「每处单独写一个 @@ 片段 / 把中间那些没改的行也写成上下文行」(判定是精确的:按顺序都能找到、但彼此不连续)
只发了 *** Begin Patch,里面是空的生成被截断 / 模型先写信封再想内容报错分开说:空补丁(教它段落怎么写)、缺 *** End Patch(提示可能被截断)、整段写成 unified diff(给出可照抄的正确格式,并提醒小改用 edit 更省事)
重试时又发了一遍同样的改动上一次其实已经改好了检测「新内容已经在文件里、旧内容不在」,提示「这段改动看起来已经应用过,确认后把这一段从补丁里去掉」(只是提示,不动磁盘)

报错不再只说「找不到」:applyChunksToText 失败时会在文件里找与模型写的那一行最像的真实行,把行号、原文与「差在哪」一起回给模型:

patch 无法应用:在 /path/to/s.css 里找不到这一段的上下文:
  .dashing{animation:dashFlash .18s ease-out;}
文件第 64 行最像(相似度 94%),实际是:
    .dashing { animation: dashFlash .18s ease-out; }
(只有空格 / 制表符的差别(有没有、在哪里))
把这一行**原样**(连同缩进)抄进 - 行再试一次;改动上下各留一行上下文会更稳。
文件没有被修改。

三段信息各有用途:原文回显让它能对照;文件里的真实行让它照抄就能过;差异原因(缩进 / 行内空白 / 标点 / 第几个字符起不同)让它知道下次别再凭记忆写。相似度不够高时改成明确提示「这段文本在文件里不存在,先 read 再写」。诊断只在失败路径上跑,并且有行数上限(2 万行以上不扫)。

为什么不做删除与改名:这两件事本来就有官方出口——bash 的 rm / mv(git 跟踪的用 git rm / git mv),而且与 patch 吃同一份沙箱策略,越界一样会被拒。反过来,把它们塞进 patch 的收益只是省一次调用,代价却很实:

  • 删除不可逆,而 patch 的「全有或全无」只覆盖验证阶段——落盘仍是逐个执行的,第 3 个文件失败时前两个已经改了、删掉的也回不来;
  • 改名同样会不可逆地动到另一个路径;
  • 跨平台另有一套命令转义要维护(POSIX rm 与 Windows 的 pwsh 语法不同),等于把一段在 CI 上跑不到的代码交付出去。

所以模型真写出这两段时,patch 会在读任何文件、问任何审批之前整体拒绝,并给出替代做法:

patch 只做「新建 / 修改」,不执行删除与改名,这份补丁里有:
  *** Delete File: obsolete.txt
删除请用 bash 的 `rm`(git 跟踪的用 `git rm`)、改名请用 `mv`(跟踪的用 `git mv`);两者与 patch 吃同一份沙箱策略,越界一样会被拒。把这几段从补丁里去掉、只留增改,patch 就能继续。

客户端仍然把「它本来打算做的改名」画出来(卡头 旧 → 新)并打 rejected 角标——用户要看到的是模型想干什么。

什么时候用哪个:一处小改动 → edit;新建 / 整份重写 → write;多处零散或跨文件 → patch;删除 / 改名 / 构建 / 测试 / git → bash。

两种展示方式(设置 → File Change View)

方式组件形态
高亮(默认)官方 CodeBlock单个代码块里放完整统一 diff(LCS 结果):未变行只出现一次,删除行红底、新增行绿底,删行不占行号;代码原样(不加 - / + 标记)
± 差异官方 DiffBlock + 中性上下文行逐行 -/+ 红绿文字(紧凑),超长中部折叠;自带复制按钮与 └ +A -B 页脚。喂给它的是语义变更(只含真正变化的行),所以它的 +A -B 与高亮视图、行头统计永远一致

设置页里这两种方式是两张并排的单选卡,每张卡下面用同一份渲染代码(diffView.ts,与工具行走的就是这一套)画一段真实样张(src/greet.ts 的一次 +2 -1 小改),选中即生效——挑的时候看到什么,会话里就是什么。卡片整块可点(样式里给样张加了 pointer-events:none,官方组件自带的复制按钮在样张里不会抢走点击)。

两个视图都会在改动两侧多显示几行没受影响的上下文(contextLines,默认 3 行)。这几行取自文件当前内容——Host 的 /locate 路由拿文件定位这段 hunk 时,顺手把它两侧各最多 8 行原文回给客户端(响应里只有行号与这十几行,整份文件不回传),客户端按偏好截。

为什么 ± 差异视图的上下文是「另外画的一行行中性文字」:官方 DiffBlock 的行 kind 只有 path | del | add | gap(别的 kind 直接 throw),画不了中性的上下文行——上下文一旦塞进它的 diffs,就会变成绿色的 + 或红色的 -,把没改的行说成改了。所以本插件把上下文单独渲染成中性行贴在它上下:上文 → DiffBlock(红绿改动) → 下文,拼起来正好是一份标准的 unified diff。

官方 DiffBlock 进卡后还会自带三样冗余,被结构性清掉(选择器全走它自带的 data-diff 根 + 子元素位置):body 首行那条完整路径(官方 buildRows 无条件插入,600 字重 + 56px 右留白,读起来像正文里横插的标题)、右上角复制按钮(卡头视图切换旁再浮一个)、块底 └ +A -B · N 个文件 footer(与卡头统计同口径重复报数)。卡头已有文件名与统计,这三样只会在上下文与改动之间制造噪声。

上下文行虽然不是官方组件画的,但读起来必须像同一块代码,所以做了三件事对齐:① 排版逐像素镜像官方——同一字体 token(--dsw-font-markdown-code-block,实测 11px/19px + code 字族)、行盒与官方 .line 同为 22px 最小盒高;② 衔接处零缝隙——有上文时把 DiffBlock 改动区自己的顶部内边距归零(+ .ctx + .diff > div:first-of-type),上下文行与 ± 行逐像素相接;③ 横向滚动收敛到一处——外层 diffWrap 是整份 diff(上文 + 改动 + 下文)唯一的滚动容器:官方 .body 的滚动被结构性覆盖关掉(data-diff 根下第一个 div,注意不能写 :first-child——body 前面还有个复制按钮 <button>),上下文行不自己滚。超长行因此只有一根滚动条、一条轴,不会出现「上下文一根条、改动另一根条、各滚各的」。

高亮视图的行底色铺满整条滚动宽度:官方 .line 是普通块盒,宽度只到 code 可视宽度;white-space:pre 下超长文字以行内溢出延伸到盒外——直接在 .line 上叠底色的话,往右滚,红绿条会在行中间断开(文字还在、底色没了)。所以行盒被显式撑宽:code > :global(.line) { box-sizing:border-box; width:max-content; min-width:100% }——短行底色照旧铺满整卡,长行底色跟着文字走到行尾。border-box 必须显式写:行号列是 .line 自己的 padding-inline-start,默认 content-box 下 min-width:100% 只约束内容宽,行号内边距叠加在外,背景会凭空多出约一个行号列宽。.line 是官方全局类名(官方自己的 CSS Module 里就是 :global(.line)),所以这里必须写 :global 形态,构建产物里它是合法的裸类名(check-client.mjs 的哈希护卫已为它豁免)。

行号列从卡片左边框让出一档 14px:官方把行号画在行盒的最左缘(绝对定位的 ::before 落在 inset-inline-start:0),而上面为了让底色铺满整卡把 pre 的横向内边距归零了——官方默认那 16px 一拿掉,行号列就紧贴卡片边框。行号列宽是 max(2, 位数)ch(官方按代码行数内联写在块根上),位数一多(3–4 位)数字会把整列填满,等于压在边框上。所以行盒补一条 padding-inline-start: calc(var(--dsl-code-block-line-number-width) + 26px)(26px = 新的 14px 档 + 原有的 12px 行号/正文间隙),行号本身用 .line::before { inset-inline-start: 14px } 同步右移。14px 不是随手取的数:它正是本插件另一套版式那一档(.ctx 的 padding:0 14px、官方 DiffBlock 的 .body{padding:12px 14px}),于是高亮与 ± 两个视图的行号列、正文列落在同一个 x 上(无头 Chrome 像素实测:两视图的行号 ink 都自卡片左缘 +15px 起、正文 ink 同一 x)。设置页样张的 .pvCode 复刻同两条规则,样张与真用起来一致。

± 差异视图:上下文行带真实行号,改动行的行号列放 + / -。官方 DiffBlock 的行没有编号机制,上下文行的行号由本插件用 CSS 计数器(dshp-fcv-num)补上——上文从「上文首行的真实行号」起数,下文的起点 = 上文首行 + 上文行数 + 语义新增行数(删除行不占号,与高亮视图同口径);起点由 JSX 内联成 .ctx 容器上的 counter-reset,定位不到(locate 没回结果)时不带号、绝不编数。行号列的宽窄(3ch + 12px 间隙)、颜色与淡化与高亮视图的官方行号一致。

改动行不编号,而是把 + / - 画在同一列里:diffSignRules 生成的动态 CSS 给 del / add 行让出与上下文行号同一列(padding-inline-start: calc(3ch + 12px)),并把官方行内的 - /+ 前缀压掉(::before{content:none}),改在自己的 ::after 里画符号(del 红 / add 绿)——于是「上下文行号 / 改动符号 / 正文」三列严格对齐(无头 Chrome 实测:三行正文左缘都是同一个 x)。这里还有一条必需的配套规则:改动行自己得是那个 ::after 的包含块(.diff[data-diff] > div > div { position:relative })——官方行是 static,不定位的话 inset-inline-start:0 会向上找到官方块根(整张卡)的左缘,+ / - 就比上下文行号左出一档、还紧贴卡片边框(行号列与符号列错开 14px)。不这样做的话,官方的行内前缀会把改动行的正文多推两个字符,上下文行与改动行的正文就错开了。区间用 div:nth-of-type 而非 nth-child:官方块超长折叠时行间会插一个 <button>(展开其余行),:nth-of-type 按元素类型计数、button 不占 div 的位,索引与行序严格一致;已隐藏的 path 行仍占 div 索引(display 不影响选择器),所以区间统一 +1 偏移。设置页的样张不传 hunk 类名,所以样张里的改动行保持官方原样(行内前缀、不与上下文对齐)。

三层优先级:单块点击 > 会话级覆盖 > 全局偏好

四项设置(展示方式、上下文行数、编辑 / 写入是否默认展开、patch 工具开关)里,前三项都有两个入口,第四项只有一个:

层入口落盘作用范围
单块自己的临时点击卡片头上的折叠箭头 / 「高亮 · ± 差异」两个药丸不落盘那一个文件块
会话级覆盖会话页头右侧的两个快捷开关不落盘当前会话的所有文件改动
全局偏好设置 → File Change View(单选卡 / 下拉 / 开关)条目 config:所有会话的默认值
  • 「patch 工具开关」不参与这套优先级:它不是渲染偏好,而是 Host 半注不注册那个工具的开关,改完立即生效。
  • 全局偏好改动立即经 /ext/dshp-file-change-viewer/config 写进 profile 条目 dshp-file-change-viewer 的 config:(Host 调 settings.update),保存成功 / 失败都在设置节里如实反馈。

会话页头的两个快捷开关(只影响当前会话)

会话标题栏右侧(官方「在应用中打开」、会话日志下载的同一排)多出一组胶囊按钮:

[ ⌄ 收起改动 ] | [ </> 高亮 ]  ↺      ← 当前会话已被临时改动(↺ = 恢复跟随偏好)
[ › 展开改动 ] | [ ⑂ ± 差异 ]         ← 跟随设置页里的全局默认
  • 左边那个一键展开 / 收起本会话的所有文件改动(行与行内文件块一起),右边那个切换本会话的差异视图;
  • 它们改的是内存里的会话级覆盖(src/client/session.ts):一个 /config 请求都不发,持久化配置纹丝不动,换一个会话立刻回到全局默认;
  • 覆盖生效时胶囊边框变品牌色并多出一个「恢复跟随偏好」的小按钮,点它即回到默认;
  • 点页头那一下是真正的一键全改:连你之前手动折叠过的行也会翻过去(覆盖带一个版本号 rev,行组件发现覆盖换版就作废自己那份临时状态)。改完之后你再单独点某一行 / 某一块,仍然由你说了算。

「全局偏好里的默认展开」只影响之后新渲染的编辑 / 写入操作(已经在会话里的行保持原样,不会被开关抽走或撑开);想对当前这个会话立刻生效,用页头那两个开关。行内不再放全局切换按钮——逐行记忆只会让界面自相矛盾。

安装

方式一(推荐):克隆 monorepo 安装 —— 更新只需 git pull,旧版 DSH 可 checkout tag

# 1. 克隆 monorepo(lib/ 构建产物已提交,clone 下来就能用,无需 build)
git clone git@github.com:Yinxe/deepseek-harness-plugins.git
cd deepseek-harness-plugins
pnpm install

# 2. 本地安装到 profile(路径按你执行命令时的 cwd 解析)
dsh plugin --profile web add ./archive/file-change-viewer

# 3. 重启生效
dsh web
  • 更新:仓库内 git pull + dsh web(最快——lib 已提交,未改 src 无需 build);兼容旧版 DSH:按 compat.json 的 tag git checkout <tag> 后重跑上面的 add。

dsh plugin 会把包写进 profile 的 dsh.profile.bundles —— 无需手动改配置文件。

改源码后:src/client 改完跑 pnpm --filter @dshp/file-change-viewer build 重新打出 lib/host.js + lib/client.js,然后 dsh web 重启(client 半强刷页面即可)。

验证:pnpm --filter @dshp/file-change-viewer test 会跑 node --check 两份产物 + Host 冒烟(scripts/check-host.mjs,152 项:命名空间与 Config 导出契约、configure({ auto: false })、补丁消毒、/ext 三条路由、非法补丁不落库、patch 工具注册契约与动态开关(默认关 / 开了就注册 / 关了立刻反注册,由 loader/volatile-update 驱动)、全有或全无、写前预检、删除 / 改名拒绝、BOM 与沙箱提权、locate 回行号 + 两侧上下文且同一文件只读一次)+ patch 纯函数(scripts/check-patch.mjs,74 项:多文件 / 多片段 / 五级模糊定位(含唯一性安全阀)/ End of File 锚点 / 行尾与 BOM / 现场回归(@@ 当标签用、行内空白差异、多处删除不相邻)/ 信封诊断(空补丁 / 缺收尾 / 写成 unified diff)/ 失败诊断(最近的真实行 + 差异原因 + 已应用提示))+ Client 无头渲染断言(scripts/check-client.mjs,207 项,含一层迷你 React,能验证「偏好实时作用于行 / 换调用复用实例 / 会话页头开关只改当前会话」这类带生命周期的行为);界面上让 AI 改任意一个文件,展开「编辑」行后每个文件块应是带语法高亮的卡片、+A -B 与路径链接正常,且统计与 ± 视图只报真正变化的行数;在设置 → 「File Change View」里改一次偏好,profile 条目 dshp-file-change-viewer 的 config: 应立即出现对应字段,页头那两个开关则不该在配置里留下任何东西。

一键 AI 安装:把下面这段发给你的 DSH AI 即可:

帮我安装文件修改查看器插件(monorepo Yinxe/deepseek-harness-plugins,子目录 archive/file-change-viewer,包名 @dshp/file-change-viewer):
1. git clone monorepo 并 pnpm install
2. dsh plugin --profile web add ./archive/file-change-viewer
3. dsh web 重启,然后改一个文件,确认 edit / write 卡片默认就是展开的、代码有语法高亮
4. 如果这个会话里还有动态注册的同名插件(cordis_define 定义的,@ 引用能列出),用 cordis_undefine 卸掉,避免两份注册互相顶替

方式二:从 Release 安装(无需 clone;更新需手动重跑命令)

latest 滚动版(跟随 main 最新构建;滚动更新 = 重跑同一条 add 命令):

dsh plugin --profile web add \
  https://github.com/Yinxe/deepseek-harness-plugins/releases/download/latest/dshp-file-change-viewer-latest.tgz

dsh web   # 重启生效

历史版本(兼容旧版 DSH)

main 永远跟随最新 DSH。老版本 DSH 用户装静态历史版本:按 compat.json 记录的兼容 tag,到 Releases 找对应版本 Release,资产名 = dshp-file-change-viewer-<tag>.tgz:

dsh plugin --profile web add \
  https://github.com/Yinxe/deepseek-harness-plugins/releases/download/<tag>/dshp-file-change-viewer-<tag>.tgz

dsh web   # 重启生效

更新

三种安装方式对应的更新方式

安装方式更新命令说明
方式一 clone(推荐)仓库内 git pull + dsh web最快;未改 src 免 build;旧版 DSH 用 git checkout <tag>
方式二 Release重跑同一条 add 命令 + dsh webURL 直装对 update 免疫
方式三 git 依赖dsh plugin --profile web update @dshp/file-change-viewer + dsh web一条命令;git 解析约 35s/插件(实测)

功能

部分内容
Host(src/host/ → lib/host.js)name / inject: ['webServer'] / Config 导出 / apply:① 上交条目 Config schema(三项偏好全是 volatile 字段,权威存储在 profile 条目 config:)+ settings.configure({ auto: false }) 关掉自动设置页;② 三条同源路由 /ext/dshp-file-change-viewer/{state,config,locate} 供设置节读写与行号定位;③ patch 工具(测试版,默认关:patchTool 一开一关就注册 / 反注册,热更新走 loader/volatile-update,注册面在 ctx.inject(['tools','fs']) 的注入作用域里)。没有事件;这一行同时是 DSH 从 Host Loader 的条目里扫描 dsh.client 声明来发现并下发客户端 bundle 的入口——没有它,lib/client.js 不会被加载(详见 src/host/index.ts 头注释)。
Client(src/client/ → lib/client.js)用 priority: -1 注册 tool.call.toolview 的 edit / write / str_replace_editor 三个 key(影子化官方内置行;同 key 同 priority 会抛错,所以必须比内置的 0 更小),带 locale: 'conversation' 取文案;行外壳用官方 DisclosureRow 默认样式(不自造卡片),展开后每个 hunk 一个自己的折叠块,块内是纯高亮代码(官方 CodeBlock 去掉语言栏/复制/外壳);diff.ts 推导模型与 LCS 统一 diff,lang.ts 映射 26 个官方语法 id,行内没内容时给等待 / 截断兜底文案;diff.ts 把每个 hunk 跑一次 LCS(raw / rows / changed 三种口径),统计与 ± 视图都用语义变更;prefs.ts 管全局偏好的读写往返与订阅(设置节与工具行共用同一个 store),FileChangeViewerSection.ts 是设置 → 「File Change View」这一节的内容(展示方式两张样张单选卡走 viewCards.ts),api.ts 封装两条 /ext 路由;diffView.ts 把「高亮 / ± 差异」两种视图的渲染收口(工具行与设置页样张共用同一份代码),SessionControls.ts + session.ts 是会话页头那两个快捷开关与它们的内存级会话覆盖。
同源路由GET /ext/dshp-file-change-viewer/state(当前生效偏好)/ POST …/config(保存补丁 → settings.update)/ POST …/locate(真实行号定位,只回整数、不回文件内容)。业务错误一律 200 + ok:false,跨站 / 方法错误 4xx。
工具注册一个 patch 工具(*** Begin Patch 信封,一次可改多处 / 多文件,全有或全无):经 ctx.fs 读写,吃沙箱模式与观察策略,结果元数据带 diffs 交给卡片渲染;删除 / 改名明确拒绝并指路 bash。

行与文件块行为

| 场景