dsh-quote-reply —— 「引用」层(v0.4.1)
⚠️ 重启前必过门(硬规矩:动过宿主半体就必须先跑它,0 FAIL 才允许请求重启)
node _preflight.cjs :: 退出码 0 = 可以安全重启;非 0 = 先修,别重启
为什么必须是硬门:宿主半体只在 DSH 启动时加载(实测:启用/禁用、卸载重装、改 inject 全都无效)。 若它引用了不存在的 API、或与别的插件撞路由/撞上下文名,重启就是白重启一次(严重时会打断宿主 boot)。 所以"能不能重启"必须先验成一条可复跑的命令,而不是凭我的判断。
它验 6 组共 19 项:A 文件与两半体语法 · B 对 **DSH 源文件(app.asar)**核对 API 签名
(systemPrompt.context / PromptContext 字段 / webServer.register / ctx.effect / ctx.get)·
C 出厂先例(源文件里已有插件用 systemPrompt.context({name:"sandbox:policy"}) ⇒ 第三方可行)·
D 冲突(路由前缀 / 上下文名 / 槽位 id,与已装插件的 13895 个文件比对)· E 客户端夹具 · F 自证机制与"不打断 boot"。
2026-10-15 实测:PASS 18 · WARN 0 · FAIL 0。
生效规则(血泪)
| 改了什么 | 怎么生效 |
|---|---|
客户端半体(lib/client.js) |
plugin_manager set_bundle enabled=true target=dsh-quote-reply → 刷新页面 |
宿主半体(lib/index.js) |
必须重启 DSH 宿主进程(禁用/启用、卸载重装都不会重新加载它) |
| 怎么确认宿主半体真跑了 | 看 lib/.host-status.json(apply() 把注册结果写在这里);GET /quote-reply/health 应为 200 |
一句话
选中回答里的一段话 → 点「引用回复」→ 它落到输入框上方的「引用」行(和「目标」「队列」同区)。 不用再点「插入」:引用自动作为上下文随下一回合送达模型(不进你的消息正文),浮窗里可逐条展开/删除/清空。
端到端实测(2026-10-15 02:3x):推入标记「ZQ7-紫色河马」→ 下一步模型上下文里真的出现了它(
## 用户当前引用…)→ 随即清空(pending:0)。链路:客户端 →/quote-reply/quotes→ 宿主 pending →systemPrompt.context→ 模型。
为什么不是"塞进输入框"
把引用塞进输入框,会和你要说的话混在一起:分不清哪句是引用、哪句是你说的,也没法统一管理。 v0.2 因此把引用做成独立一层:引用在浮窗里,你的话在输入框里,两者分开。
怎么用(v0.2)
- 在某条回答里选中一段(或直接对着整条回答);
- 点该回答动作行里的 「引用回复」 → 上方出现/更新 「引用(N)」浮窗,这条变成 引用一/二/…;
- 浮窗里可以:点标题折叠/展开 · 逐条删除 · 清空 · 单条「插入」或「全部插入输入框」(临时逃生通道, 发出去时就会带上)。
下一阶段(已探明可行路径,尚未做)
- 发送时自动带上引用:需要一个专用发送按钮(如「发送并引用」)——宿主没有"发送前钩子",所以只能这样;
- 发送后折叠显示:引用以机器可读块随消息发出(模型能看到),你的那条消息里用样式隐去原块,
在该回合尾部显示一行 「引用」(与「已完成 用时…」同款、可点开)→ 逐条列出 引用一/二/… 与内容。
为什么不直接替换
user消息渲染器:那是shadows-shipped-ui(会接管官方渲染、丢掉官方后续特性),风险不值得。
技术要点(改它之前先读)
- 两个槽位:
conversation.input.dock(输入框上方浮窗,order 30 —— 现有占用者是goal(10)/queue(20)) 与conversation.chat.assistant-actions(每条助手消息的动作条,order 30)。 - 引用层是模块级
Map(按 sessionId 分桶):要在两个槽位之间共享,且切会话/重挂载不能丢。 暴露__quote(add/remove/clear/list/cn)供自检直接验证行为。 - 「引用回复」按钮必须
onMouseDown: e => e.preventDefault(),否则按下会先塌陷选区、拿不到"选中那段"。 apply全程不抛(服务读取经safe()、每块注册各自guard()),失败只留一行console.error。
自检
node _check-client.cjs :: 客户端半体真夹具:导出形状 · 两个槽位落点 · 引用层行为 · 敌意 ctx 不抛
(用假的 window.__ModuleLoader__ 与 React 桩把客户端半体在 Node 里真跑一遍;反证有效:把浮窗 order 改成 99,夹具立刻变红。)
它长什么样 / 怎么用
在某条已完成的回答里选中你想回应的那段文字(拖选即可);
在这条回答的动作行里点 「引用回复」(和自带的 反馈 / 跳转 按钮并排);
输入框里会出现形如
> 你选中的那段原文光标在下面,直接接着写你的问题就行。
没选中任何文字时,点它会整条引用该回答(并在旁边提示"未选中文字,已整条引用该回答")—— 这样"就想对整条回应"的场景也是一下点击。
它不做什么(边界)
- 不自动发送:只把引用放进输入框,发不发、怎么改都由你。
- 不碰你没选的内容:引用块只含你选中的那段(超长会截断并写明"已截断,原长 N 字")。
- 插入失败不静默丢:如果你点击的那一瞬间草稿被改动(或被别的东西占用),插入会被宿主拒绝; 此时引用原样保留在按钮旁,并给一个「复制保留的引用」——绝不悄悄丢弃你的操作。
技术要点(给后来改它的人)
- 落点:客户端槽位
conversation.chat.assistant-actions("Ordered actions for one finalized assistant message",ownerProps = { messageId },standardProps 自带inputActions)。本插件取order = 30(排在自带mf#feedback(10)、dsh-context#context-jump(20)之后)。 - 插入正门:
inputActions.captureInsertion()→inputActions.insertText(text, span)。 后者仅在草稿版本未变且编辑器允许编辑时插入一次可撤销的编辑,失败返回 falsy。 onMouseDown必须preventDefault():否则按下按钮会先塌陷选区,"选中一段再点"就永远拿不到那段话。- 取文本:选区优先;无选区时按
messageId找消息节点(试data-message-id等若干属性名)取正文兜底。 - 宿主半体(
lib/index.js)故意为空,只保证包根可被 import —— (踩过:宿主半体里误写成 Markdown 标题 + 孤立*/,宿主直接报failed to import,整个插件不工作。) apply全程不抛:服务读取经safe()、每块注册各自guard(),失败只留一行console.error, 绝不把异常抛进加载器(这是 2026-10-13 一次整站白屏换来的纪律)。
安装 / 卸载
:: 安装(走官方入口;本插件纯客户端,无宿主侧行为)
dsh plugin --profile desktop add link:E:\DeepSeek\skills\quote-reply\bundle
:: 或在本仓库里用 plugin_manager 的 install_bundle(同样的官方路径)
改完客户端代码要刷新页面才生效(客户端半体是随页面下发的;而"新装一个客户端插件"必须重新加载页面)。
自检(改之前先跑)
node --check lib/index.js :: 宿主半体语法
node <本目录>\_check-client.cjs :: 客户端半体语法 + 关键点在场(槽位/插入正门/防塌陷/导出形状)
No comments yet. Be the first to write one.