sunshaobei
dsh-file-mention
支持dsh @引用文件及文件夹
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 16, 2026
- Updated
- Aug 16, 2026
Introduction
dsh-file-mention
Codex 式 @ 文件引用,DeepSeek Harness (DSH) Web 插件。
三个能力,全部挂在官方扩展点上,不改动 DSH 任何内置包。输入框的 ↑/↓ 输入历史 已拆分为独立插件 dsh-input-history(发送落库 localStorage 全局环、同步召回),本插件不再包含历史逻辑:
| 功能 | 说明 |
|---|---|
| @ 文件/文件夹提及 | 输入框敲 @ 弹出工作区文件与文件夹菜单(git 索引优先、嵌套 git 仓库递归展开、fzf 风格模糊过滤);菜单行显示文件名/文件夹名主标题(文件夹带 /)+ 全路径小字副标题,宽度与输入框一致;选中插入纯文本 @token 相对路径 ——token 整体着色(无胶囊宽度上限),路径明文随行,模型零搜索直达文件;文件夹引用注入 kind="directory" |
| token/胶囊点击预览 | 点击输入框内的着色 token 或会话气泡里的灰色胶囊,直接在右侧 better-sidebar 编辑器打开该文件预览(未安装时回退系统默认打开方式);pick 的 token 精确解析,手输 token 在 stem 唯一时也可解析 |
| 菜单样式覆盖 | 注入一段 scoped CSS(:has([data-source="file"])),只影响文件组菜单:满宽 + 两行布局;其他 trigger 菜单(斜杠命令、子代理)保持内建尺寸 |
安装
dsh plugin --profile web add /path/to/dsh-file-mention
dsh.bundle.patch 声明会让 CLI 自动把本包追加进 profile 的 bundles(宿主半随之挂载)。然后重启 DSH 桌面应用并刷新页面。零运行时依赖,无 peer / 构建要求。
卸载:dsh plugin --profile web remove dsh-file-mention。
开发迭代:改完代码运行 scripts/deploy.sh(同步 lib 到 profile 安装目录)后重启。
架构
┌ composer (ui-conversation, 不改) ─────────────────────────────┐
│ @ 触发 → inputTriggers 管线 → 本插件 source "file" │
│ candidates: host files.index + dirs 合并模糊评分 │
│ 行 = name(文件名/文件夹名,文件夹带 /) │
│ + description(全路径副标题) │
│ onPick → insert-text → "@token rel/path "(无占位/无胶囊)│
│ lexicon → 文件 + 文件夹全部可着色 token → 平台 scanTextRefs │
│ subscribeLexicon → 索引落定后通知管线重扫(着色及时生效) │
│ 发送 → 草稿即文本,chip-free 跳过序列化直落 sink │
│ token/胶囊点击(捕获)→ pick 登记 / 唯一 stem 反解 → 绝对路径 │
│ → betterSidebar.openFile → 回退 workspaces.openPath │
│ 菜单 CSS → :has([data-source="file"]) 满宽 + 两行行布局 │
└──────────────────────────────────────────────────────────────┘
│ /file-mention/api (POST JSON, 浏览器信任围栏)
┌ host 半 ────────────┴────────────────────────────────────────┐
│ files.index git ls-files --cached --others --exclude-standard │
│ 嵌套 git 仓库条目(dir/)递归展开(同上限/深度界) │
│ 目录 roll = 文件祖先推导 + 嵌入式仓库 dir/ 条目 │
│ (失败/非 git → opendir walk 兜底,含空目录, │
│ TTL 10s 缓存) │
│ files.read 遗留接口(纯文本模式不再调用;保留供测试覆盖) │
└──────────────────────────────────────────────────────────────┘
- 客户端 bundle 以
window.__ModuleLoader__.load约定注册,不 require 任何模块(纯逻辑,无 React),规避 externals 漂移,无需打包器。 - 菜单渲染、键盘仲裁、IME 守卫、纯文本装饰(
mark.textRef着色)、气泡 refChip 投影全部由 DSH 内建管线承担。 - 与
@subagent、@cordis两个@源共存(组名file,order 2)。
引用格式(宿主侧注入,dsh-at-file 同款)
选中文件插入干净纯文本提及 @token (token = 去扩展名文件名);发送后的处理全部在宿主侧 agent/pre-step 边界完成:
- 宿主扫描发出的用户消息里的
@token,按「pick 注册(精确)→ 索引唯一 stem」解析,stat校验存在后,注入一条独立的引用消息给模型:
<workspace-reference path="lbk-flutter-common/modules/fiat/lib/screens/otc/home/otc_home_tab_page.dart" kind="file" />
-
文件夹同样可引用:目录与文件共享同一索引与 token 语法(
src/app/→@app),选中或手输命中后注入<workspace-reference path="lib/" kind="directory" />(目录 path 保留尾部/);token/胶囊点击预览对目录走系统打开方式(跳过 better-sidebar 编辑器) -
输入框、草稿、用户气泡永远干净——只显示着色 token,路径不出现在任何用户可见文本里
-
模型直接从引用消息拿精确路径,零搜索
-
菜单 pick 时客户端通过
mentions.register把token → 相对路径精确映射登记到宿主(歧义 stem 也能精确解析);手输 token 靠全工作区唯一 stem 兜底 -
注入消息的 source 为
at-file-mention(与 dsh-at-file 相同形状),不进入输入历史 -
token 语法
@[\w-]+:点文件(.gitignore)与多点名(archive.tar.gz)插入@basename但不着色、不注入引用 -
手动键入
@token(token 命中 lexicon)同样着色;唯一 stem 的手输 token 发送时同样被注入引用 -
token/胶囊点击预览:pick 的 token 按 pick 时登记的绝对路径打开;手输/气泡 token 在 stem 唯一时反解打开;歧义 stem 点击不动作
已知限制
- 非 git 目录靠默认忽略表兜底(node_modules/dist/build/隐藏目录等),不解析 .gitignore 本身;隐藏目录与忽略目录(node_modules 等)既不出现在文件也不出现在文件夹菜单
- git 路径下目录由文件祖先推导:不含任何已跟踪文件的空目录不会出现(git 本身不跟踪空目录);walk 兜底路径下空目录可见
- 索引上限 200000 文件(
truncated截断,目录 roll 同界);文件/文件夹新建后最长 10s + 重开菜单可见 - 含空格的路径无法靠键入 query 命中(触发 token 遇空白终止,Codex 同款约束),仍可从菜单点选
- 手输/气泡胶囊的点击解析依赖 stem 唯一:歧义 stem 不动作(pick 的 token 不受影响,pick 时已精确登记);文件夹与文件同名 stem 同样视为歧义
- 菜单行 React key 含文件名:同名文件并列时开发构建会有 duplicate-key 告警(按索引 pick,功能不受影响)
- 菜单 CSS 用结构选择器(
[role=option]span 顺序)而非平台哈希类:平台 MenuView 大改时覆盖规则失配,退回内建单行样式(无害降级) - 点击命中依赖 DOM 结构约定(
data-composer-card、mark[data-decoration]、span[data-ref-chip]),DSH 前端大版本变更时需适配
测试
node --test test/host.test.js test/client.test.js
27 个用例:目录遍历/深度/上限、嵌套仓库展开、目录 roll 推导、路径围栏、信任围栏(loopback/trusted/cross-site/origin)、读取(截断/二进制/越界)、路由分发、模糊评分(含目录行)、token/stem 派生(含目录尾部斜杠)、mention 扫描/引用形式(file + directory)/pre-step 注入/pick 注册校验、joinPath、命中测试、API 封包。(↑/↓ 输入历史相关用例已随拆分迁移至 dsh-input-history。)