DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Drag — DeepSeek Harness 插件(DSH Plugin)
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins
D

dsh-drag

Drag

将会话行从 dsh 侧边栏拖出,或将文件行从右侧的 Files 面板拖出,然后拖放到聊天区域,即可在撰写器草稿中插入引用标签(@[title](dsh-session:…) 或 @path)。

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add github:ttmouse/dsh-drag#30852e8c9010b2a8539cc2f9992104e7739ba98e
README兼容性版本

兼容性与来源证明

Drag 以 dsh-drag 发布,当前版本为 0.1.2。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
github
Registry 更新时间
2026/10/14

版本

0.1.2stable
2026/10/14
0.1.1stable
2026/10/8
0.1.0stable
2026/9/30

相关插件

正在加载相关插件…

最新版
0.1.2
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
未提供
文件数
未提供
Surface
web
许可证
MIT
发布源
github
GitHub
★ 0
周下载
0
最近提交
2026/10/14
查看源码 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题

相关插件

继续浏览 productivity-workflow 分类下经过校验的插件。

Acp App@deepseek-ai/dsh-acp-appdsh ACP 配置文件包:基于 dsh-base 的仅限自动化的 JSON-RPC stdio 和进程生命周期管理Experimental Schedule Bundle@deepseek-ai/dsh-experimental-schedule-bundle为 Web 组合添加 Schedule、其任务页面和时间上下文的可选捆绑包Ab Ocrdsh-ab-ocr--- description: "外部 ocr 工具:逐页识别扫描的 PDF 或图像,仅在处理完最后一页后按顺序合并页面,并使用模型确定的标题级别保存为一个 Markdown 文档。" kind: "package-reference" ---Web All@linxin666/dsh-web-allDSH Web UI 全家桶聚合插件:一键安装全部功能插件(task-board / git-graph / pet / remote-web-ui / web-ui-settings / skin-center / community-plugins / compat shim)。compat 桥接层已并入本包(src/client),无需独立 compat npm 包。

README

dsh-drag

把侧边栏会话列表里的一个会话,或右侧文件面板里的一个文件/文件夹, 拖到聊天记录区域松开,一条规范的引用就会以引用 chip 的形式插进当前输入草稿—— 发送前还能再检查一眼。会话拖出的是 @[标题](dsh-session:…) mention; 文件拖出的是宿主自己的文件引用 token(@路径,带空格时是 @"路径"), 与输入框里用 @ 挑选文件得到的插入结果完全同形。dsh Web GUI 插件, 声明兼容 DSH >=0.1.5-rc.2 <0.2.0-0 || >=0.2.0-rc.2 <0.3.0-0 (web 端 0.1.5-rc.2 起、桌面端 0.2.0-rc.2 起)。

两种拖拽来源

来源谁提供的拖拽放下的结果
侧边栏会话行宿主本来就有(ui-workspace 的 SessionNodeItem 在 dragstart 里写 text/plain,工作区树的行还带 data-row-key="session:<id>")会话引用 chip(appearance: 'session')
右侧文件面板的行插件自己激活——宿主在那张表上完全没有拖拽(见下)文件/文件夹引用(appearance: 'file' / 'folder')

其它一切拖拽源(工作区分组行、纯文本、面板的「其它」占位行)一律不激活。

为什么能成立

  • 侧边栏的会话行本来就是原生 HTML5 drag 源:ui-workspace 的 SessionNodeItem 在 dragstart 里 dataTransfer.setData('text/plain', node.id)(已在 0.1.5-rc.2 的 bundle 里核对)。缺的只是放下的一侧。
  • 右侧文件面板完全没有拖拽:dsh-client-ui-sidebar-files 把每个条目渲染成 <li data-files-entry="file|directory|other" data-files-path="<绝对路径>"> 包一个 <button>,整份 bundle 里 draggable 与任何 drag 监听都是 0 次 (0.2.0-rc.2 核对)。所以插件自己把它变成 drag 源——而且激活必须发生在 pointerdown,不能是 dragstart:浏览器只从「当时已 draggable」的元素发起 拖拽,没有该属性的行连 dragstart 都不会触发,在那里面写 draggable="true" 是永远执行不到的死代码(第一版恰好犯了这个错,桌面端表现为 按下拖动毫无反应;scripts/browser-drag.mjs 就是为此存在的回归闸门——jsdom 对任何合成事件都照单全收,看不见浏览器的这道门)。pointerdown (document 捕获)是拖拽阈值之前来得及写属性的最后一刻,写上后属性留在行上, 与宿主会话行天生 draggable 同一形态;行被宿主重渲染后由下一次按下重新激活。 payload 用插件自己造的 application/x-dsh-drag-file 类型,同时镜像一份到 text/plain,这样万一放下没被接住,也只是多插一段路径文本而不是什么都不发生。
  • 放下走的是编辑器自己的引用通道:conversation.input.for(actx).insertReference —— 与输入框内 @// 挑选流程同一条 span-CAS 路径,插进去的是真正的 chip 节点,而不是一段文本。
  • 文件引用的文本形态按宿主自己那条文法拼(dsh-file-reference/grammar 的 formatFileMention):无空白就是 @路径,有空白就加引号 @"路径",目录多带一个 尾斜杠;路径里含引号或控制字符时该文法无法安全表示,拒绝插入并提示,而不是 拼出一个会被解析成别的路径的 token。这与 ui-reference 的 @ 挑选器完全一致。
  • dragover/drop 挂在 document 捕获阶段并 stopPropagation():输入框是 Lexical 编辑器,它在编辑器根节点上自己监听 drop,会把 text/plain 载荷 (也就是裸 session id)当文本贴进草稿——冒泡阶段拦已经太晚,捕获阶段才行。 dragstart/dragend 仍走冒泡:会话行的 payload 由它自己的 handler 先写。
  • dragstart 里把 effectAllowed 从行自带的 move 放宽成 copyMove:声明 move 的拖拽配 dropEffect = 'copy' 不是合法组合,Chrome 宽容、严格的引擎会 直接拒绝这次放下。
  • 拖回侧边栏([role="tree"] 里的任何行)永远不拦截:官方的行内重排序行为 原样保留。右侧文件面板不在此列——它的行是插件自己激活的来源,在它上面放下同样 要接住(何况那张表面本来也没有 role="tree",见下)。

行为

  • 按住一个会话行开始拖动,指针进入聊天区域 → 接受投放(光标出现"复制"角标);
  • 在记录区域内松开 → 引用 chip 插入草稿(span 取自投放瞬间的 caretSpan()
    • snapshot.draftRev);
  • 按住右侧文件面板里的一个文件/文件夹行拖动 → 同上;文件出 @路径、 文件夹出 @路径/ 且用文件夹样式 chip;
  • 松开在侧边栏 → 什么都不发生(官方拖拽继续接管);工作区分组行、面板的 「其它」占位行、纯文本等拖拽源一律不激活;
  • 聊天列([data-conversation-content])永远优先放行:轨迹视图那类自带 role="tree" 的 JSON 树渲染在聊天列内部,不能被当成侧边栏而拒收;
  • 拖放当前会话自己 → 提示「当前会话不需要引用自己」;插入被拒绝(相位/版本 不匹配)→ 提示「会话引用插入失败」/「文件引用插入失败」;找不到可插入的 composer(主面板没有会话)→ 静默不插,但会在控制台留下 [dsh-drag] drop ignored: … 一条警告。

不画任何自己的浮层:拖动时的视觉反馈只有浏览器原生的拖影(跟随鼠标的会话行 副本)和上面那个复制角标。早期版本额外弹了一个「松开以引用会话『标题』」气泡, 结果就是光标旁边出现两条标题,纯噪音,已删除。

安装

# 从 GitHub(收录 dsh-market 后市场走的也是这条源)
dsh plugin --profile web add github:ttmouse/dsh-drag

# 或本地开发
cd ~/.dsh/profiles/web && pnpm add "dsh-drag@link:/path/to/dsh-drag"

lib/ 已随仓库提交(pnpm build 的产物),所以 GitHub 安装不需要构建授权。 dsh.profile.bundles 里加入 "dsh-drag"(dsh plugin add 会自动加),重启 dsh web 并硬刷新浏览器。

宿主版本声明

package.json 声明 dsh.manifestVersion: 1 与 engines.dsh: ">=0.1.5-rc.2 <0.2.0-0 || >=0.2.0-rc.2 <0.3.0-0"。

  • 下限 0.1.5-rc.2 是实际核对过 bundle 的版本;0.2.0-rc.2 是桌面端内置的那条 线。0.2.0-rc.2 曾经让拖放静默失效:那一版把 SessionListState.current 删了 (状态只剩 { ids, byId, phase, projectionsBySession }),而插件当时正用它解析 投放目标,于是 resolveFace() 恒返回 null——拖拽会 arm、dragover 会接受并显示 复制角标,但松开后既不插 chip 也不报错。现在的解析法见下面「投放目标怎么来的」, 旧宿主仍走 current 回退。教训:只做「这些 API 还在」的存在性核对不足以声称兼容, 真正要核的是取值语义。
  • 为什么要两段 ||:node-semver 只在范围里存在「同 major.minor.patch 且自身带 预发布标签」的比较符时才放行预发布版本——<0.2.0-0 与 >=0.2.0-rc.2 就是为此 存在的。dsh-market 读这个字段时用 includePrerelease: true,于是 0.1.5-rc.2 起的每个 0.1.x / 0.2.x 预发布宿主都落在范围内,0.1.4 及以下、0.2.0-rc.1 与 0.3.0 起不在。
  • 这两个字段是声明,不强制:官方 @deepseek-ai/dsh-package-manifest 明说 「当前安装器与加载器不强制检查 dsh.manifestVersion 或 engines.dsh」,它们 只决定市场卡片显示的宿主要求与兼容性过滤。

开发

pnpm install
pnpm build   # scripts/build.mjs — src/client.js 包进 loader 闭包写出 lib/
pnpm test    # test/*.mjs + scripts/interaction.mjs + scripts/browser-drag.mjs(真实 Chrome,缺浏览器时自动跳过)
  • test/session-resolution.mjs:闸住「当前会话」的两种列表形态——0.2.0-rc.2 的 { ids, byId, phase, projectionsBySession }(无 current,靠 retainedBy.mainView 选出主面板会话)与旧宿主的 current,外加「主视图没有会话时惰性无操作」。把这处 解析回滚成 sessions.list.getSnapshot().current,只有它会红(apply-wiring.mjs 喂的是插件自己假设的形状,看不见这类漂移)。

  • test/smoke.mjs:假 doc 手势驱动 createDropSurface 全部分支(mention 编码 对齐宿主 dsh-session-reference 的规范形、侧边栏放行、dragend 之后 drop 失效、 自引用提示、插入拒绝提示、apply() 的 dock 槽与 inject 面、bundle 形状),外加 文件引用文法与 data-files-* 标记的逐条断言(@路径 / @"带 空格" / 目录尾斜杠 / 含引号路径必须拒绝 / 「其它」占位行不是来源),以及 「data-row-key="" 不算行身份」——把 rowKeyOf 的空串特判改回 ?? null, 只有它那一条会红。

  • test/apply-wiring.mjs:只经 apply(ctx) 驱动整条链路(假 document + 假宿主服务):断言四个 document 监听真的被装上、dragover/drop 的相位是 捕获、接受时确实 stopPropagation、effectAllowed 被放宽、一次完整拖放手势能 插 chip、span 取的是 caretSpan() + snapshot.draftRev、侧边栏与工作区行一律 放行、surface 挂在 inject scope 上(不是 root fiber)、dispose 撤干净。0.1.0 的 bug 是 apply() 压根没挂 surface(模块导出的 createDropSurface 自己是对的, 老测试直接调它所以全绿)——这个文件就是为它存在的回归闸门:把 src/client.js 回滚到修复前,只有它会红;把上面任一条改坏也会红。

  • scripts/interaction.mjs:jsdom + 真实冒泡事件渲染真实 loader bundle,只调 apply(ctx),在真实 DOM(侧边栏树 + 右侧文件面板 + 聊天区)上跑完整手势, 并断言插件没有往文档里插任何节点(自定义浮层回归会直接红)。14 条检查里 8 条 专管文件面板:按下把行就地激活成 draggable(激活只发生在 pointerdown, 从未按下的行必须保持未激活)、一次文件手势插一条 @README.md、目录带尾斜杠且 用 folder chip、带空格路径走引号文法、含引号路径被拒并提示、「其它」行不激活、 在文件面板自己身上放下也要接住。把文件激活那一段改成 const fileRow = null, 只有这个文件会红。

  • scripts/browser-drag.mjs:真实 Chrome + 真实输入事件的端到端闸门,是 jsdom 唯一替代不了的检查。jsdom 对任何合成事件照单全收,看不见「浏览器只从已 draggable 的元素发起拖拽」这道门——第一版把激活写在 dragstart 里,jsdom 十四条全绿,真机上按下毫无反应,就是从这道门漏出去的。此脚本把构建产物装进 真实页面(宿主服务打桩),用 Playwright 的鼠标按下文件行拖到聊天区,断言真的 插入了引用;把激活挪回 dragstart 它立刻变红(已实测:文件 0 条插入、会话行 正常——与桌面端报障现象逐字吻合)。需要本机有 Playwright 与 Chrome,缺失时 跳过并明确声明,不算失败。

改完 pnpm build 不需要重启 dsh web:bundle 路由按内容哈希取当前字节, 浏览器硬刷新即可拿到新版本。

浏览器半(lib/client.js)零依赖、零 external:不 require 任何模块,宿主服务 全部经 cordis ctx 注入。

投放目标怎么来的

投放时现算目标输入机:先解析「主面板正在展示的会话」→ sessions.scope(id) → conversation.input.for(actx),再取 caretSpan()(detect 坐标的光标 span)和 snapshot.draftRev(输入机版本号,span CAS 用)调 insertReference。所以拖放面 不依赖任何槽位渲染,挂载只发生在 apply() 里。

那一步「当前会话」在两个宿主形态间取值(currentSessionIdOf):

  • 0.1.5-rc.x 及以前:sessions.list.getSnapshot().current 直接给 id;
  • 0.2.0-rc.2 起:该字段没了,改成宿主自己反复用的那条谓词——byId 里 retainedBy.mainView > 0 的那一行就是主面板里的会话(ui-layout、ui-cordis、 ui-open-in-app、ui-settings-general、ui-workspace 的 mainSessionId 与 ui-session 的 isMain 全是这个语义)。

先读 current、读不到再扫 byId,所以同一份构建同时服务两种宿主。

那一步「这是哪一行」

onDragStart 只认两种行,判定顺序固定:

  1. 文件面板的行——closest('[data-files-entry][data-files-path]') 命中且有 data-files-entry 为 file 或 directory(other 是面板自己的、打不开的挂载 点占位,不是引用来源)。路径本来就是绝对值,所以不需要解析工作区根目录。
  2. 侧边栏会话行——data-row-key="session:<id>" 优先(工作区树给会话行渲染了 这个属性);没有该属性时退回到 text/plain 载荷,此时它必须通过 id 形状 正则才算数。

这里有一条不要放宽的约束:那个 id 正则(/^[A-Za-z0-9_-]{1,128}$/)的字符类 不含冒号,而工作区分组行往 text/plain 里放的是 workspace:<id> —— 正是这个正则 把工作区拖拽挡在门外。一旦把它放宽到允许 :,拖一个工作区分组就会插进一条会话 引用。 另外 data-row-key="" 不算行身份(rowKeyOf 返回 null),因为 overSidebar 把「键存在」当成「指针在侧边栏上」的证据,空串若算数就会让聊天区域 拒绝放下。

conversation.input.dock 里仍注册了一个渲染 null 的条目(id: dsh-drag, order 40)——目前没有任何代码消费它的 inject 面,它是上一版设计的遗留,可以 直接删掉;留着只是无害。文档级拖放面在 apply() 里直接挂 document。

License

MIT