中文EN

dsh-file-extras

ui已测通过npmMIT

增强 DSH Web GUI 的文件树:行内显示大小与修改时间,右键菜单支持下载、上传、复制路径。

下载量 / Downloads
1,145
依赖体积 / node_modules
0.29 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

安装

dsh plugin --profile web add dsh-file-extras

沙箱测试结果

阶段结果
1 安装✓
2 列装✓
3 挂载✓
4 启动—

四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。

README

dsh-file-extras

DSH Web GUI 的日常操作增强插件。主体是在官方「文件」页(@deepseek-ai/dsh-client-ui-sidebar-files)上补右键菜单、行内元数据、多选与删除;另外收了两件与文件树无关、但同样天天要用的能力:文件驱动的斜杠命令与输入框 ↑/↓ 历史指令回填。

需求 · 现在的行为

覆盖默认浏览器右键菜单 · 在文件树内右键弹出自己的菜单(树外一律不接管,浏览器默认菜单照旧)

文件右键 · 下载 / @ 到对话 / 复制路径 / 删除

文件夹右键 · 上传文件到此处… / @ 到对话 / 复制路径 / 删除

空白处右键 · 上传文件到此处… / 复制路径 / 刷新大小与日期 / 全选可见项(有选择时再加 清除选择 与 删除 N 项)

文件大小 + 修改日期 · 行尾显示 1.2 KB · 2026-03-04 13:06:07,按字节自动选单位(B/KB/MB/GB/TB/PB,1024 进制)

文件夹大小 + 修改日期 · 行尾显示 3 项 · 2026-03-04 13:06:07;只数第一级子级(readdir 一层,不递归)

删除(文件夹 / 单文件 / 多文件) · 走 二次确认框(列出每一项是什么、多大、文件夹含多少内容);默认移到回收站(可还原),「永久删除」藏在默认不勾的复选框后面

@ 到对话(文件 / 文件夹) · 直接在当前对话的输入框里插一枚 @ 引用芯片,和用户自己敲 @ 选出来的是同一种东西

斜杠快速命令 · ~/.dsh/commands/*.md 一个文件一条 /命令,正文可带 $ARGUMENTS 占位符;改文件立即生效

↑/↓ 快速切换历史指令 · 输入框里按 ↑/↓ 像终端一样翻出本会话自己敲过的指令,Esc 退出

斜杠快速命令

~/.dsh/commands/<名字>.md 一个文件一条全局斜杠命令,命令名就是去掉 .md 的文件名

(文法与注册表逐字一致:/^[a-z][a-z0-9_-]*$/)。

---
description: 审查当前工作区的代码改动
input-hint: 可选的补充说明
---

请审查当前工作区的代码改动,重点关注潜在 bug 和安全问题。

$ARGUMENTS

行为 · 说明

frontmatter · 读 description(发现界面里的说明)与 input-hint(输入提示),其余键忽略但容忍

$ARGUMENTS · 换成用户敲在命令名后面的原始参数;没有这个占位符时正文原样发出

改文件立即生效 · 正文是每次调用时重新读盘的,不用重启

新增 / 删除文件 · 需要重启 dsh web 才反映(注册是启动时扫一遍目录)

执行结果 · 正文作为一条普通用户消息交给 agent —— 与用户自己把这段话敲进输入框等价,不是系统注入

用的是官方 ctx.commands.register()(@deepseek-ai/dsh-commands),所以发现、补全、

command/run / command/done 生命周期事件全归官方。与 app 级命令(/goal 之类)重名时

只跳过这一条并告警,不会把其余命令一起带走。

这条能力移植自 dsh-command-md(同一作者,MIT)。

↑/↓ 快速切换历史指令

在输入框里按 ↑ 翻出本会话里自己敲过的指令,再按 ↑ 往更早走,↓ 往新的走。

第一次 ↑ 时当前草稿会存起来,一路 ↓ 翻回最新一条之后再按 ↓(或按 Esc)就原样还回来。

穿梭时输入框上方有一条 历史指令 … ↑↓ 3/12 · Esc 退出 的提示条。

键 · 接管条件 · 行为

↑ · 光标已贴在草稿最开头(或草稿为空) · 进入穿梭 / 往更早一条

↓ · 只在穿梭模式里 · 往新一条;已是最新时还回原草稿并退出

Esc · 只在穿梭模式里 · 还回原草稿并退出

任何真实输入 · 只在穿梭模式里 · 立即退出穿梭(beforeinput:打字 / 退格 / 粘贴 / 拖入 / 输入法)

/、@ 候选菜单开着时 · —— · 一律不抢键(那里 ↑/↓ 是选候选)

数据不是从聊天视图里抠的(那里只有已加载的一窗),而是走 host 半区的 RPC history:

它读 ctx.sessionQuery.readSession() 的完整事件日志,只挑 source.kind === 'user' 的真实用户指令

—— 所以「更早」那些也在,而 AGENTS.md、技能目录、定时提醒这类 agent.inject() 塞进去的系统上下文不在。

(实测一个真实会话:38 条 user/message 里只有 11 条 kind 是 user,其余 27 条是

time-context / runtime-context / skill-catalog / agent-instructions / compact-checkpoint / user-approval。)

回填用官方 inputActions.setDraft() 整条替换草稿:不碰编辑器内部,不自己实现输入框。

host 通道取不到历史时 ↑/↓ 完全不接管 —— 宁可这个功能静默失效,也不能让用户觉得键盘坏了。

这条能力移植自 dsh-message-jump 的
「输入框 ↑/↓ 历史消息穿梭」(同一作者,MIT);该插件另一半的悬浮指令导航条没有收进来。

@ 到对话

右键文件或文件夹 → @ 到对话,该路径就以引用芯片的形式进入当前对话的输入框(多选时一次插一批)。

只插入、不发送 —— 后面还要接着写字,替你按发送键是越权。

这里没有重新实现「对话里 @ 文件」:

  • 芯片交给官方会话自己的编辑器插(ctx.conversation.input.for(actx).addFiles(...),

和把文件拖进输入框走的是同一条路),所以序列化(模型看到的文本)、点击芯片在右侧栏预览、

草稿持久化、发送时展开,全部沿用官方行为;

  • 提及文本逐字复刻官方 @ 补全的语法(@deepseek-ai/dsh-file-reference 的 formatFileMention):

@src/index.ts、目录 @src/、含空格 @"my notes.txt"、含空格的目录 @"my docs/(引号故意不闭合,官方语法如此);

  • 路径按「相对会话工作区根 + 正斜杠」给(官方候选就是这个形式,模型也按这个解析)。

会话工作区与树根不一致时退回绝对路径,不硬拼一个错的相对路径。

「当前对话」取 ctx.sidebarRight.mounted —— 官方对「主栏正在显示哪个会话」唯一的公开可观察量,

文件树本身也是按这个会话渲染的。拿不到会话、会话没被 retain、输入框正忙(正在提交)时都明确报错,不静默失败。

多选怎么用

官方那棵树没有多选,本插件自己实现了一套,规矩和资源管理器一致:

操作 · 效果

普通单击 · 清空选择(并放行官方行为:文件打开、目录展开)

Ctrl / ⌘ + 单击 · 切换这一行的选中态(拦下官方行为,不会顺手打开文件或展开目录)

Shift + 单击 · 从锚点行到这一行整段选中(按可见行的 DOM 顺序)

空白处单击 · 清空选择

空白处右键 → 全选可见项 · 一次选中当前树里所有可见行

Esc · 分层收:确认框 → 右键菜单 → 选择

选中态是填色 + 一圈内描边(只填色会跟 :hover 撞成一样)。右键落在已选中的行上时,菜单作用整个选择

(标题变成 已选 N 项,删除项变成 删除 N 项,多选时不再提供只能作用于单项的下载/上传);

右键落在没选中的行上则把选择换成那一行 —— 不会出现「看着选中了 5 个,结果只删了右键的那一个」。

为什么需要 host 半区

官方文件树的数据来自 workspaceFiles Remote 命名空间,它的类型面里只有 size,没有修改时间

(WorkspaceDirectoryListing.entries[].size / WorkspaceFileStat.bytes;version 是不透明令牌,

契约上明令 never parsed)。所以 mtime 只能由插件自己从 host 侧取。

host 半区在 ctx.webServer 上挂一条前缀路由 /dsh-file-extras:

  • POST /dsh-file-extras/stat —— Connection RPC 信封,批量取 大小 / mtime / 一级子项数量;
  • POST /dsh-file-extras/history —— 某个会话里真实用户指令的全文列表(↑/↓ 回填的数据源);
  • GET /dsh-file-extras/download?root=&path= —— 流式回文件字节 + Content-Disposition;
  • POST /dsh-file-extras/upload?root=&dir=&name= —— 请求体即原始字节,写进目标目录;
  • POST /dsh-file-extras/remove —— 删除(RPC 信封),默认移入回收站,逐条独立结算。

浏览器的右键菜单注册进 shell.overlay(框架级浮层,本来就在每一列之上、滚动容器之外),

历史回填提示条注册进 conversation.input.dock(输入框卡片上方那条整宽座位)。

斜杠命令与这两者都无关,走官方 ctx.commands.register(),不经过本插件的通道。

文件

文件 · 作用

[index.js](index.js) · host 半区:/dsh-file-extras 前缀路由(RPC stat / history / remove、GET download、POST upload)+ 扫描 ~/.dsh/commands/*.md 注册斜杠命令

[lib/commands-md.js](lib/commands-md.js) · 命令文件的纯解析层:frontmatter、$ARGUMENTS、目录扫描(零依赖,可脱离 DSH 单测)

[lib/trash.js](lib/trash.js) · 跨平台「移入回收站」:Windows / Linux / macOS 三套后端;平台与家目录可注入,所以三套逻辑能在任意一台机器上实测

[lib/client.js](lib/client.js) · 浏览器半区:行内元数据 + 多选 + 右键菜单 + 删除确认框 + @ 到对话(shell.overlay)+ ↑/↓ 历史回填(conversation.input.dock)

[cordis.patch.yml](cordis.patch.yml) · loader 行(inject: [webServer, connection])

[package.json](package.json) · dsh.bundle + dsh.client

[scripts/selftest.mjs](scripts/selftest.mjs) / [scripts/dom-probe.mjs](scripts/dom-probe.mjs) / [scripts/trash-bin.mjs](scripts/trash-bin.mjs) · 两层自检 + 回收站测试清理助手

安装

dsh plugin --profile web add dsh-file-extras

或在会话里让 Agent 用 plugin_manager 的 install_bundle 指向本目录。

装完刷新一次页面(客户端 bundle 在插件激活时读入并缓存,改代码后需要重新激活才生效)。

配置

无(无 Config 字段、无运行时开关)。可调参数都在源码顶部的常量里,每个都带注释。

安全边界

  • 路由复用 connection.requestRejection():Host/Origin 检查 + 浏览器签名 cookie 认证,

非本机页面与未认证请求拿不到任何数据;

  • root 必须是绝对路径、存在、且不是盘符/文件系统根(否则「声明 root=C:\ 就能读全盘」);
  • 每个候选路径都做 realpath 后再比包含关系 —— 工作区内的软链接指向区外

…(truncated)

以上内容来自 npm 上的 README,我们只做格式化,不改内容。

版本

npm 上共发布:4,最后更新 2026-10-10

链接

GitHub · Homepage · npm

同类插件