Nalleyer
dsh-enter-send
dsh plugin: switch the chat composer between Enter-send and Ctrl+Enter-send from Settings → General.
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 14, 2026
- Updated
- Aug 17, 2026
Introduction
dsh-enter-send
DeepSeek Harness(dsh)客户端插件:在 设置 → 常规 页面新增"发送快捷键"选项,重映射聊天输入框的发送 / 换行键位。
| 选项 | Enter | Ctrl/Cmd+Enter |
|---|---|---|
| Enter 发送(默认,保持 dsh 原生行为) | 发送 | 换行 |
| Ctrl+Enter 发送 | 换行 | 发送 |
- Shift+Enter 两种模式下都是换行(浏览器默认行为,不拦截)。
- 中文输入法(IME)组合输入中的 Enter 一律放行,不会误发。
- 默认值
enter下普通 Enter 行为与官方完全一致,升级无感知。 - 设置持久化到
$DSH_HOME/settings.yaml(namespaceenter-send.mode);同时写入浏览器localStorage作为非本机访问/内存模式下的兜底,重启 Web 后仍会恢复上次选择。
安装
前置条件
- dsh ≥
0.1.0-rc.6(web 平台) - 从 git 安装时,安装过程会运行
prepare脚本自动构建:构建工具链为 esbuild(npm 生态,仅需 Node.js,随 devDependencies 自动安装);本机装有 bun 时自动改用 bun 构建 - 运行测试需要 bun(仅开发用)
方式一:从 GitHub 安装(源码,安装时自动构建)
dsh plugin --profile web add github:Nalleyer/dsh-enter-send
git 安装拉取的是源码,不会附带构建产物,因此 pnpm ≥ 10 在得到显式允许前会拒绝运行 prepare 构建脚本——首次 add 会失败,dsh 会打印修法:把 pnpm 提示的确切包键写进该 profile 的 pnpm-workspace.yaml:
allowBuilds:
dsh-enter-send: true
然后重新执行 add。请注意:该授权允许包代码在安装时于你的机器上执行(不在任何沙箱内),只对源码可信的包授权,并建议锁定 commit(github:Nalleyer/dsh-enter-send#<sha>)。
本包声明了 dsh.bundle 清单,add 后加载行会自动写入 profile 的 patch 层,无需手动编辑;重启 dsh web 即可生效。
方式二:tarball / npm 安装(预构建产物,无需构建授权)
# tarball(仓库内 pnpm pack 产出)
dsh plugin --profile web add ./dsh-enter-send-0.1.0.tgz
# 或 npm 发布后按包名安装
dsh plugin --profile web add dsh-enter-send
本地开发(--patch overlay,不写入 profile)
# 注意:--patch 必须放在转发给 web app 的未知选项(如 --port)之前:
# commander 遇到未知选项就将其及后续 token 当作位置参数,--patch 放后面
# 会报 unknown option;也不能写成父级形式(dsh --patch ... web)。
dsh web --patch ./cordis.patch.yml --port 8091
卸载
dsh plugin --profile web remove dsh-enter-send # 移除依赖与加载层
# 卸载后 $DSH_HOME/settings.yaml 中残留的 enter-send 段无害,可手动删除
插件打包与安装的官方说明见 dsh 开发文档。
构建与测试
node scripts/build.mjs # 产出 lib/index.js(host 半)+ lib/client.js(浏览器半)
# 构建优先用 esbuild(npm),无 esbuild 时回退 bun
bun test # keymap 逻辑单测 + bundle 形态冒烟测试(需要 bun)
工作原理
- 拦截层:
document捕获阶段 keydown 监听(addEventListener(..., true)),先于 React 委托的 composeronKeyDown。命中条件:目标是 composer 的 textarea(带独有data-phase标记、非 readOnly/disabled、非 IME 组合isComposing || keyCode === 229)。 - 发送:拦截后向 textarea 派发合成的无修饰 Enter
keydown(bubbles: true),冒泡到 React root 触发 composer 原生提交路径——草稿 / 附件 / 队列 / busy 仲裁全部复用,不重复实现。合成事件会再次经过捕获阶段,由同步标志防重入。 - 换行:
document.execCommand("insertText", "\n"),触发原生input事件,草稿同步与 Shift+Enter 路径一致。 - 持久化:浏览器半通过
settingsScope绑定enter-sendnamespace,host 半注册 schemastery schema(mode: "enter" | "ctrl-enter"),读写$DSH_HOME/settings.yaml;选择同时存入浏览器localStorage,在非 loopback/内存模式下也能跨重启保留。
目录结构
├── package.json # dsh.bundle + dsh.client 清单(platform: web)
├── cordis.patch.yml # bundle 配置层(dsh.bundle.patch),兼本地开发 --patch
├── scripts/build.mjs # 构建脚本(bun build + __ModuleLoader__.load 包装)
├── src/
│ ├── types.ts # 模式类型与 settings 常量(双端共用)
│ ├── locales.ts # 中/英字典 + LocaleNamespaceMap 声明
│ ├── node/index.ts # host 半:注册 settings namespace(dsh-settings)
│ └── client/ # 浏览器半:
│ ├── index.ts # apply 入口:设置行 + settingsScope + keymap + 样式
│ ├── EnterSendRow.tsx # 设置行组件(仿官方 EnterBehaviorRow)
│ ├── keymap.ts # 按键重映射核心(纯逻辑,可单测)
│ ├── styles.ts # 设置行样式(运行时注入 <style>,随卸载清理)
│ └── contract.d.ts # 仅类型:引入 settings.general.item 插槽契约
├── lib/ # 构建产物(.gitignore,不入库)
└── test/ # bun test:keymap 逻辑 + bundle 形态冒烟
兼容性说明
- 插件通过 composer textarea 的
data-phase属性识别目标,与官方 composer 内部实现耦合;若上游调整该标记,需同步更新isComposerTarget。 document.execCommand("insertText")已被标记废弃,但 Chromium 全版本支持;若未来被移除,需改用beforeinput/InputEvent注入(见src/client/keymap.ts内注释)。