dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
重新回答 · Answer again — DeepSeek Harness (DSH) 插件:助手消息动作条上的「重新回答」按钮,先把这一轮的旧回答退出模型上下文,再把该轮的用户提问重新提交给同一个会话。 A DSH plugin that re-answers a turn after dropping the previous answer from the model context.
在助手消息的动作条上加一个 重新回答 按钮:把该轮回答对应的用户提问重新提交给 同一个会话,并先让这一轮的旧回答退出模型上下文 —— 重答时模型看不到自己上一次 在这一轮里答了什么(日志与界面上的历史消息不动)。
按钮落在复制与分支之间(conversation.chat.assistant-actions,order: 20),样子与原生动作条按钮一致:方形图标按钮、悬停出深色提示气泡、与那条动作条一起悬停/聚焦显隐(data-clock=end 那一轮尺寸跟着原生图标走)。图标与提示气泡都是插件自带的:插件实践要求第三方插件的浏览器半边不 require Harness 客户端包,所以这里只 require('react')。
session-retryconversation.chat.assistant-actions)connection 与 agents,浏览器半边只依赖 react)宿主半边(index.js)在共享的 /api 通道上注册两条路由:
POST /api/session-retry/context { sessionId } (只读)
→ { ok, sessionId, latestTurn, cached, anchors: [
{ messageId, turn, text, promptCount, omittedParts, promptSeq, anchorSeq } ] }
POST /api/session-retry/regenerate { sessionId, messageId } (登记改写)
→ { ok, turn, promptSeq, runs, shadowed, kept, eventSeqs, deferred: true }
前者一遍扫过整条会话日志,把「动作条挂在哪条助手消息上」与「该轮的用户提问」配对,结果按会话缓存 1.5 秒。后者只认活会话:从 sessions.get(id) 拿会话、用 snapshotEvents() 现场重算锚点(与 /context 同一套 retryTargets 规则),再把该轮的可见面节点替换成空节点 —— 但不在点按钮的当场写,而是登记一份影子计划,等下一次请求真的打开一个步骤时补写(原因见下一节)。
浏览器半边(client.js)按会话注册一个动作条条目:取一次上面的表,每条动作条拿自己的 messageId 查表 —— 命中就可点,没命中就置灰并说明原因。点击后先 POST /regenerate,成功才用 session.prompt([{ type: 'text', text }], 'queue') 提交提问(与 ui-conversation 的 conversation.send 走同一条路径)—— 两步都成了才提示「已重新回答(旧回答已退出上下文…)」;改写没成就只转述原因、不提交,否则等于在旧回答还在上下文时把提问问第二遍。
改写的是可见面(surface),不是日志:会话记录、界面上的历史消息、标题与统计都原样保留,只有送给模型的消息列表少掉这一段 —— 模型能看到什么由 dsh-session 的 surface 折叠与 deriveMessages() 决定。
用法是 surface 自己的替换通道:追加一条 system/message 事件,带 surfaceOp: { op: 'replace', startSeq, endSeq } 与 sourceEventSeqs(被遮蔽节点必须列全,应用会严格校验)。载体必须是空 system 节点:应用自己的规则是「空 system/developer 节点保留可见面位置但不产生线缆消息」(dsh-session 的 deriveEventMessage),而空 user/message 会作为一条空 user 消息真的发出去。
写这条事件的时机有硬约束:system/message 属于 V4 的「步骤事件」,校验要求它的 turn/step 必须等于当前打开的那一对(dsh-session-format-v3-to-v4 的 STEP_EVENT_TYPES + requireStep)。点按钮时会话是空闲的 —— 上一轮已经 turn/end、下一轮还没 turn/start,没有任何打开的步骤 —— 此刻带着上一轮的 turn/step 写进去,日志当场就坏:
SessionFormatError: system/message does not match an open turn and step
→ stored log is corrupt ...
所以 /regenerate 只登记影子计划(deferred: true,可见面当场不动),等客户端提交的提问真的打开一个步骤时,在 agent/request 钩子里用当时打开的 turn/step 补写。这个位置既合法(步骤确实开着),又足够早 —— dsh-agent-loop 的 step() 先 step/start,然后 prepareRequest→agent/request(hook),最后才 buildRequest→deriveMessages(),所以用户这一次请求就已经看不见旧回答了。会话在补写前被关掉也没事:计划带 TTL,过期即弃,日志里什么都没写过。
影子范围怎么算:每个可见面节点取一个「内容区间终点」—— 追加型节点是自己本身就是新内容,终点是它自己的 seq;替换型节点继承被替换段的位置(压缩摘要就是这样占住早期位置的),终点是被替换段的末端。终点早于本轮提问的节点属于「提问之前的记忆」(例如提问前产生的压缩摘要),保留;终点不早于提问的节点都是这一轮的产物,影掉。要影的节点在可见面里未必连续,所以按可见面下标切成连续段,一段一次 append(每段留一个空节点占位)。这段判定是纯函数 shadowPlan(),真实日志与合成事件都能直接喂进去测。
动作条由 ui-chat 的轮次尾部渲染:每轮一条,挂在该轮最后一个产出文本的步骤里最后一条 assistant/message 上(closing = finalized.findLast(hasText))。本插件的锚点就是这条消息,所以:
turn/start 之后、锚点之前的人工消息(source.kind === 'user')。注入的上下文(time-context、skill-catalog、插件通知)与压缩检查点都不是提问;替换操作(surfaceOp 为对象,压缩复写旧消息)也不是追加,不参与判定。一轮里不止一句提问:用户在模型回答途中发话(steering / 排队)时,那句话会被接进同一个轮次。此时按顺序把该轮全部提问合并成一段重发 —— 只挑其中一句会丢掉另一句,合并则既不丢内容也只提交一次。提示语会如实写明「合并 N 条提问」。
/regenerate 要拿到内存里的会话对象才能改写;会话不在应用里打开时返回 NOT_LIVE。会话正在回答时返回 RUNNING —— 边答边改可见面属于压缩那种受控场景,这里不做(提示语会直接说「先停止再重新回答」)。react:dsh.client.inject 只声明 @deepseek-ai/dsh-client-ui-chat(插槽的属主),immediately: true 让前端在启动时就预取这个包。图标(RefreshIcon,几何照抄原生刷新图标)与提示气泡(纯 CSS 显隐,不 portal、不写 document.body)都在 client.js 里自带。connection 与 agents:agents 是 agent/request 钩子(补写那一步)必需的;sessionQuery 与 sessions 都不注入,缺失时按不可用降级回答 —— 没挂 sessionQuery 的组合里 /context 答 UNAVAILABLE,没挂 sessions 的组合里 /regenerate 也答 UNAVAILABLE,都不会把插件拖垮。agent/request(步骤已打开、消息还没装配),所以空节点的 turn/step 一定等于当时打开的那一对,日志不会被写坏;没有打开的步骤时(例如钩子被别的组合提前调用)就什么也不写,计划留到下一步。本插件是一个标准 DSH bundle:package.json 里声明了 dsh.bundle.patch 与 dsh.client,装进任何 profile 都走同一条路。先装好 DSH 桌面端(deepseek.com/download),然后下面任选一种。
https://github.com/linanbuan/dsh-session-retrySource code (zip))整个安装由 Harness 自己的插件管理器完成:把这个包写进当前 profile 的 dsh.profile.bundles,并挂上宿主半边。桌面端装插件不需要命令行,也不需要手改 profile 文件。
把仓库地址或本机目录直接交给任意会话里的 Agent,它会调用 plugin_manager 工具(action: install_bundle)走同一条路。这是官方给第三方插件推荐的安装方式。
装了 dsh CLI 的机器上:
dsh plugin --profile <profile 名> add <spec>
<spec> 与方式一相同(包名、Git 地址、压缩包链接或本地路径);参数以 dsh plugin --help 的实际输出为准。
profile 目录默认在 ~/.dsh/profiles/<profile 名>。把包放进去,并在 profile 自己的 package.json 里登记两处:
"dependencies": { "@local/dsh-session-retry": "link:<本目录绝对路径>" },
"dsh": { "profile": { "bundles": [ "…", "@local/dsh-session-retry" ] } }
手改 profile 文件要过权限审批,写坏会让 Harness 起不来,所以正常安装请走前面三种。
一个坑:在 WSL 里 ln -s 出来的链接是 Linux 符号链接,Windows 侧跑的 Electron 解析不了它,插件页会报 dsh: cannot resolve profile bundle "@local/dsh-session-retry"。要真链接就让 Windows 侧的 pnpm 去建(方式一/二),否则就用目录副本。
激活与改完代码之后怎么让它生效是两件事:
@local/dsh-session-retry;/plugins/events 的 graph 帧重新拉取 client.js,不用刷新页面。但宿主半边改了代码必须重启应用。 cordis 的插件加载器用裸 import() 加载宿主模块,Node 的 ESM 缓存按文件 URL 命中,停用再启用只会重跑缓存里那份旧模块的 apply() —— 新注册的路由不会出现。表现是:按钮是新版本,点下去却提示「宿主那半还是旧版本(404),重启应用后再试」(客户端把这条 404 专门翻译成能照着做的一句话)。所以:
- id: session-retry
disabled: true
或者直接点插件页那一行的开关;彻底移除用插件页 → 这个包 → 卸载。
system/message + surfaceOp)。原始日志与界面历史不动,可随时用「分支」另开会话保留原样。请勿在需要严格留证的一次性会话里使用。system/message does not match an open turn and step),受影响的会话日志需要单独修复;1.0.0 起改为「登记影子计划 + 在打开的步骤里补写」,不再有这个问题。若你手上是旧版本,请升级。RUNNING,提示语会说「先停止再重新回答」。connection 与 agents(后者供 agent/request 钩子用),sessionQuery / sessions 缺失时降级为 UNAVAILABLE 而不是崩溃。~/.dsh/... 形式或 <占位符>)。node test/retry-targets.mjs # 判定与影子范围的单元测试(合成事件,零依赖)
node test/client-contract.mjs # 浏览器半边在替身环境里的契约测试(零依赖)
retry-targets 覆盖锚点判定与影子范围:一轮一会话、多轮取最大轮次、一轮多句提问合并计数、提问只有非文字内容时不给锚点、该轮没有产出文本的步骤时不给锚点、注入上下文与插件通知不算提问、锚点之后的提问不算本轮、空日志/畸形事件/缺 turn/end 安全降级、影子范围的边界与不连续切段。
client-contract 按宿主客户端的加载契约搭一个最小替身(window.__ModuleLoader__ + react + slots/locale/sessions 服务),驱动真模块走完「注册 → 挂载 → 取上下文 → 点击提交 → 提示语」的全过程。替身故意不提供任何 Harness 客户端包 —— 一旦 client.js 又去 require 一个,就在那里当场失败。它验证的是模块自己的逻辑与注册形状;「按钮在真实界面里的位置与样式」不在它能证明的范围内。
真机实证(用应用自己的 Session 实现与真实会话日志跑端到端,含「补写出来的日志能被 V4 校验器读回来」这条)不在本仓库内,属于本机验证材料。
package.json bundle 清单(dsh.bundle.patch + dsh.client)
cordis.patch.yml 一行 insert:装载宿主半边
index.js 宿主半边:两条路由 + retryTargets/shadowPlan
client.js 浏览器半边:动作条按钮(手写 lazy CJS,只用 react)
test/retry-targets.mjs 判定与影子范围单元测试
test/client-contract.mjs 浏览器半边契约测试
icon.svg 插件图标
@deepseek-ai/dsh-client-ui-primitives 的外观(13px 文字、深色底、圆角、下方 6px 的摆放,以及 IconRefreshOutlineRegular 的几何),只取外观与几何,不加载该包、不复制其代码:官方包的许可为 MIT,本条按 MIT 的要求标注来源。ui-chat 的实现口径,属于接口兼容性的对齐,不含官方代码副本。surfaceOp 的 replace 语义、空 system 节点不产生线缆消息)是 DSH 官方 dsh-session 自带契约,本插件只按契约写入,不包含其代码。MIT © 2026 linanbuan