Chuyển đến nội dung chính

dsh-plugin-session-resources

Đã xác minh

dsh-plugin-session-resources · v0.2.0 · MIT · Giao diện web

DeepSeek Harness out-of-tree plugin: a file panel beside the conversation — the session's workspace as a tree, the files it produced pinned above, and a mark on every row it touched.

Cài đặt

dsh plugin add dsh-plugin-session-resources

Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.

Mã nguồn

Phát hành lên npm mà không có repository công khai. Hãy kiểm tra nội dung package trước khi cài.

Thẻ

Readme

dsh-plugin-session-resources

npm license DeepSeek Harness

English | 中文

对话旁边的文件面板。 会话工作区以目录树呈现、可逐层展开,本次会话产出的文件置顶,动过的每一行带一个圆点。点文件即打开;对话区会让出宽度而不是被盖住。

dsh plugin --profile web add -w dsh-plugin-session-resources

安装

必须带 -w profile 目录本身是 pnpm 工作区根,不带的话 pnpm 会以 ERR_PNPM_ADDING_TO_ROOT 拒绝并且什么都不装。

每个 profile 单独安装。 面板只出现在装了这个包的 profile 里,所以桌面端要自己装一次:

dsh plugin --profile desktop add -w dsh-plugin-session-resources

安装时不会索要构建权限。 浏览器半边是手写的纯 JS,由应用自己的模块加载器载入,没有构建步骤也没有 prepare 脚本——pnpm 不会要求你为这个包的代码放行。

没有任何配置项。

之后更新:

dsh plugin --profile web update dsh-plugin-session-resources

面板里有什么

Session log 旁边的按钮打开它。三样东西共用这块区域:

本次产出 置顶,是这次会话写过的文件。下面的树里也有它们——它们和别的文件一样在磁盘上——所以这是故意重复。目录列表回答「这里有什么」,答不了「这次对话刚做出了什么」,而后者恰好是一轮结束时最想知道的;在两百个同级文件里翻找就是这一段要省掉的事。

工作区目录树 是会话自己的项目目录,展开某个文件夹时才去取那一层,取到后留着,所以折叠再展开不花代价。目录排在文件前面。目录过大时会截断并明说,而不是把不完整的列表当成完整的。

圆点 标记本次会话动过的行:实心表示产出,空心表示只读取过。具体哪些工具经手写在圆点的 tooltip 里——这个信息值得留,但不值得像以前那样以文字形式占着文件名旁边的位置。

筛选框在已展开过的文件夹范围内按名字过滤。它只覆盖已加载的部分,这是诚实的:没展开的文件夹内容根本不在这一侧,而每敲一个键就去遍历整个磁盘是另一件事了。

目录数据从哪来

应用自带的 host.listDirectory 做不到。它是给「选目录」用的,在那里文件不是目标,所以它刻意丢掉所有非目录条目——而整张 RPC 表里没有别的路由会返回文件名。

所以宿主半边自己开了一条通道,通过 connection.rpc.handle 注册。这是官方支持的加通道方式:它把通道置于 /api 路由用的同一道请求围栏之后,并把注册绑定到本插件的 fiber 上。列目录用的是 fs.listDir,这是应用里唯一同时返回文件和目录的读法。

围栏有两层。 通道只接受 loopback,所以只有本机的页面能调用。在此之内,每个路径都相对该会话自己的工作区根解析,再用 fs.contains 校验,因此 .. 和指向别处的绝对路径都会被拒。对已附着的会话,根由会话存储给出而不采信调用方;对只是从历史里打开、存储答不出来的会话,根来自调用方,此时起作用的就是那道 containment 校验。

任何东西都不会进入模型。 没有工具、没有提示词段落、没有请求。面板无法从对话里被查询。

没有目录数据时

有些部署给不出:没有组装文件系统服务的,或者不转发这条通道的外壳。面板会用一行说明这件事,并回退到两个不需要宿主参与的列表——本次会话产出的文件,和它作为来源用过的文件,从会话日志里已有的工具活动折叠而来。

这个回退视图正是这个插件原本的全部内容,而且它至今仍在为圆点和置顶段提供数据,所以它是一个真正的视图,不是一句道歉。

一次调用如何成为一条记录

判定依据是渲染意图,而不是工具名diff 卡片,或 kindedit 的通用卡片(str_replace_editor 的 insert 就是这个形状),算一次改写;read 卡片或 kindread 的通用卡片算一次读取。读意图而不读名字,才使得一个还没被写出来的文件工具只要声明自己做什么就能加入这套账本——这里没有任何名单需要维护。

tool/call 开始折叠,tool/result 完成折叠。这个方向是被迫的而非选择:改写的文件身份只可能存在于 call 视图里,因为 kind: 'edit' 的通用卡片在 call 时的 locations 里点名文件,而它的 result 视图一个都不点。但是否计入由 result 决定——尚未落定的调用还什么都没写,失败的调用不能认领文件,即使它的工具已经在 call 时展示了 diff。两种失败信号都会检查:内部失败身份(data.error,工具抛异常)和面向模型的标志(isError,工具自报失败)。

同时被读和被写的路径算产出:会话写过的文件就是产物,无论中途是否也读过它。

card 以下的每个字段都是不可信的。线上 schema 只锁定 for 判别式和「存在一个带 card 标签的对象」,所以版本不匹配或异常插件可以送来没有路径的 diff 卡片、或没有 pathlocations 条目。分类器按结构收窄并丢弃读不出来的部分,因此一个畸形视图的代价是少一个圆点,而不是整个面板坏掉。

打开文件与显示所在文件夹

点击一行会把路径交给宿主,走的是工具行用的同一条路由。打开失败时保持沉默:路径不可用时原生应用会自己弹对话框,而文件列表里的一行不是报告这件事的地方。

行上的文件夹按钮,以及头部那个针对工作区根的按钮,只有在宿主报告自己能触达桌面并且当前页面是本机时才出现。别的机器上的浏览器不该能让宿主的桌面弹出窗口——这与应用给它自己的「产出文件」快捷方式设的条件相同。

布局

面板是对话主体右侧的一块区域,从会话头部下方开始。拖动它的边缘可在 240 到 720 像素之间调整宽度,方向键也可以。

会话头部永不移动。 让出宽度的是它下方的滚动主体,也就是读者正在看的那部分;标题、工具行和对话标签保持满宽。消息会在剩余空间里自己重新居中,因为它们本来就是靠自动外边距撑住的。

配色、左边缘、拖拽药丸和安静滚动条都用应用自己的 token 与既有做法,不是在这里另做决定,所以浅色和深色跟随宿主,本包内没有任何主题判断。

图标

九个彩色 badge 加十二个 Lucide 线性图标,badge 优先、线性兜底,另有二十二个按语言的代码 badge 由同一套页面几何组合而成。源文件、家族表和生成常量的生成器都在 assets/file-icons/

已知限制

  • 账本只覆盖已加载的历史窗口。 如果某次调用的 tool/call 已经滚出窗口,它会留下一个待定上下文且不产生记录,直到更早的一页补上开头——所以长会话最早那批圆点要往回翻页才会出现。树不受影响,它读的是磁盘。
  • 树不监听变更。 展开文件夹时取一次,之后一直保留到你手动重新读取(头部那个按钮)。为一个侧面板配文件监听,是用很多机械去回答一个按钮就能回答的问题。
  • 发现类调用与网页结果不进账本。 glob/grep 的路径和 web_search 的来源确实是溯源信息,但一次发现调用可能点名几百个没人打开过的路径。
  • 删除与移动不产生圆点。 删掉的文件不是产物,移动的目标又落在已验证的改写词汇之外。
  • 只被 shell 命令碰过的文件没有圆点。 终端卡片点名的是工作目录而不是文件,所以一次重定向写出的文件没有可折叠的位置信息。它仍会出现在树里,因为树读的是磁盘。
  • 没有工作区根的会话没有树。 明说这件事,胜过退回到某个读者从未问起的目录。

不卸载而移除面板

在 profile 自己的 cordis.patch.yml 里按 id 覆盖——不要再 insert 一次,同一个 id 插两次会以 duplicate loader entry id 启动失败:

- id: session-resources
  disabled: true

开发

从本地检出安装需要 file: 前缀:

dsh plugin --profile web add -w "file:/path/to/dsh-plugins/packages/session-resources"

pnpm 是链接而不是拷贝:profile 的 node_modules 里那个包目录是指向存储的 junction,里面每个文件都是指向你检出目录中同一个文件的硬链接——一个 inode,多个名字。所以就地编辑一个文件会同时到达所有 profile,不需要安装步骤。

需要安装的情况是新增或删除文件,因为新文件还没有链接。另外,任何以「写临时文件再改名覆盖」方式保存的编辑器也需要,那会替换 inode 并让 profile 悄悄指着旧内容。重新链接很便宜,所以值得养成的习惯是改完就跑一次,而不是去推断这次属于哪种情况:

dsh plugin --profile web install

运行中的应用不会重读上面任何东西。 浏览器刷新页面即可拿到新的客户端代码,但宿主半边只在启动时加载一次——改过它要重启 dsh web。桌面外壳两边都在启动时加载,所以无论改哪边都要重启。

许可

MIT。随包的 Lucide 线性图标为 ISC,见 assets/file-icons/LUCIDE-LICENSE.txt