dsh-paste-collapse
把 DeepSeek Harness 输入框里的大段粘贴折叠成 Codex 同款占位符,叠一枚可点开的胶囊,
并在模型读到这一轮之前由宿主把全文换回去。
模型收到的仍然是你粘贴的每一个字符。插件不写磁盘,也不省 token —— 占位符只是让输入框
更好读,不是压缩。
你粘贴 12000 个字符
│
▼
输入框显示 [Pasted Content 12000 chars] 外加一枚带预览的胶囊
│
▼ 你按发送
宿主把占位符换回原文
│
▼
模型读到完整的 12000 个字符
规则(与 Codex 完全一致)
| 阈值 | 1000 字符(Codex 的 LARGE_PASTE_CHAR_THRESHOLD) |
| 占位符 | [Pasted Content 12000 chars] |
| 同尺寸第二次粘贴 | [Pasted Content 12000 chars] #2,第三次 #3…… |
| 编号方式 | 按字符数分别计:3000 字符的粘贴自己从 1 开始 |
| 展开顺序 | FIFO:占位符第 k 次出现对应第 k 段原文 |
| 找不到原文的占位符 | 原样保留,照样发给模型 |
占位符文本、编号规则和 FIFO 展开都是 Codex
(codex-rs/tui/src/bottom_pane/chat_composer.rs)的移植:next_large_paste_placeholder
与 expand_pending_pastes。
胶囊
每段被折叠的粘贴都会在输入框上沿生成一枚胶囊:
- 粘贴内容的第一行,最多 16 个字符
- 字符数(缩写显示,
6.7k而不是6684) - 点一下在右侧打开全文抽屉
抽屉就是 Codex 与 WorkBuddy 那种侧边面板:全文、字符数、复制按钮、关闭按钮;
按 Esc 或点面板外即可关闭。
胶囊配色全部走 DSH 原生 CSS 变量(不写死十六进制),跟随明暗主题。
消息里也是胶囊
已经发出去的那条消息里,被折叠的粘贴同样显示成一枚胶囊,而不是一大片原文:气泡把粘贴
内容渲染成一枚小 chip,带预览和字符数,点一下还是打开同一个右侧抽屉看全文。
气泡先问客户端要原文,客户端没有再问宿主要(宿主重启之后就是这样),两边都没有就
老老实实显示纯文本,不出胶囊。
消息里这枚胶囊的两点前提
气泡里装的是展开后的全文,不是占位符。 宿主在 agent/pre-step 里把占位符换回全文,
session 存下来的、气泡显示出来的都是那份全文。所以胶囊是靠「客户端记住的粘贴原文」在
消息里定位出来的——宿主重启之后客户端与宿主都不再持有原文时,那条消息就退回纯文本。
这个槽是抢来的,不是商量来的。 宿主用 conversation.user.text.node 槽的 text key
渲染用户消息正文,本插件顶掉了这一席。浏览器里槽的优先级由 cordis-client-runner 的
facade 分配(guard.ts 的 allocatePriority()),后注册的插件半边天然拿到更小的优先级,
所以赢过宿主的 100;本插件显式传的 priority: 50 只在静态打包路径下才真正生效。两条路
都赢,但这是前提,不是巧合——别指望可以随便调注册顺序。
安装
# 在 DSH profile 里
dsh plugin --profile desktop add github:matata2017/dsh-paste-collapse
要用 github: 这种写法:本插件没有发布到 npm,npm 上的 dsh-paste-collapse
是另一个作者的包。
或者链接本地目录:
{
"dependencies": {
"dsh-paste-collapse": "link:D:\\work\\huanghao\\dsh-paste-collapse"
}
}
插件分两半:宿主端(lib/index.js)负责展开占位符,客户端端(client.js)负责把粘贴
折叠成占位符并画出胶囊。
边界
- 原文只存在内存里。 宿主用一个普通数组保存粘贴内容,不写磁盘、不上传,宿主进程
结束后就没了。 - 宿主重启后原文会丢。 占位符还留在输入框里,但宿主已经不知道原文,发送时占位符
会原样发给模型。胶囊会把它标成「原文已丢失」。 - 不省 token。 模型读到的仍然是全文,和直接粘贴一模一样。插件只解决输入框可读性。
- 宿主不答应就不折叠。 宿主最多问两次,每次最多等 5 秒,也就是最多等 10 秒;还是
没答应就把粘贴内容按原文插入,绝不丢字。 - 用户删掉占位符就作废。 客户端会通知宿主,宿主在这一轮用过之后再忘掉这段原文,
下次同样大小的粘贴重新从裸占位符开始。 - 发送出去的那一轮不会丢原文。 发送时输入框会被清空,而宿主的展开发生在清空之后,
所以原文会挺过这次清空:模型读到的是全文,不是占位符。 - 原文会逐条忘掉。 宿主保留最近交付的 50 段原文,客户端保留最近发出的 100 段,两边
独立淘汰;更早的会被丢掉,那条消息就退化成纯文本。
与 Codex 的差异
只有两处:
- 展开时机。 Codex 在输入框里展开,DSH 在宿主的
agent/pre-step里展开,也就是模型
读到这一轮之前。 - 占位符的样子。 Codex 只显示纯文本,DSH 在纯文本占位符之上再加一枚胶囊。
测试
node --check lib/marker.js
node --check lib/index.js
node --check client.js
node test/run-tests.mjs # 66 条:marker 规则 + 客户端 bundle
node selftest/host-e2e.mjs # 24 条:宿主端到端
test/client-bundle.mjs 在假 window、假 React、假 DOM 里跑 client.js,不用浏览器也能
验证输入框行为;selftest/host-e2e.mjs 在假 Cordis 上下文里跑宿主插件,DSH_HOME 指向
临时目录。
完整验收清单见 VERIFY.md。
许可
MIT