dsh-context-actions
Verifieddsh-context-actions · v0.1.2 · MIT · Web UI
DeepSeek Harness(dsh)Web GUI 插件:在上下文占用面板新增「压缩」与「交接」按钮。Adds Compact / Handover buttons to the dsh context panel (unofficial plugin).
Install
dsh plugin add dsh-context-actions Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
dsh-context-actions
非官方开源插件,与 DeepSeek 官方无关。 MIT License。
给 DeepSeek Harness(dsh)Web GUI 的「上下文已用」面板加两个按钮。
| 按钮 | 行为 |
|---|---|
| 压缩 | 对当前会话执行宿主命令 /compact,就地压缩上下文;执行前先探测该会话的 agent 预设是否挂载了压缩后端,没有就给出可操作的提示 |
| 交接 | 把当前会话的持久日志生成一份交接文档写到系统临时目录(%TEMP%\dsh-handover\)。默认「脚本节选」零模型调用、不消耗 token;可在设置页切换为「模型总结」(按所选模型计费),然后新建一个会话并把文档路径发过去让它接着干 |
两个按钮都出现在上下文占用面板底部(点 composer 右侧的圆环弹出),并带一行状态反馈。
安装
要求:Node.js ≥ 18,已安装并可使用 dsh(在 dsh 0.1.5-rc.1 上验证;dsh 升级后若内部结构变化可能需要跟着调整)。
从 npm(发布后):
dsh plugin --profile web add dsh-context-actions
从源码 / 本地目录(把 <仓库绝对路径> 换成实际路径,Windows 用正斜杠最稳):
dsh plugin --profile web add file:<仓库绝对路径>
官方 dsh plugin add 会自动把依赖写进 profile 的 dependencies、同步 dsh.profile.bundles,并发现包里的 dsh.bundle.patch。
安装后:
- 宿主半侧(
/handover命令)需要 重启 dsh 才生效; - 浏览器半侧(按钮)刷新页面即可(Ctrl/Cmd+R)。
验证装配:
dsh --profile web --dump-config | Select-String context-actions -Context 1,1
期望看到:
# == dsh-context-actions
- id: context-actions
name: dsh-context-actions
使用
- 打开任意会话,点 composer 右侧的上下文圆环,面板底部就是「压缩 / 交接」。
- 压缩:会话的 agent 预设挂载了
dsh-compaction-basic+dsh-command-compact时才可用(standard/ptc/cordis预设都带;minimal预设刻意不带)。结果文本会显示在按钮下方状态行里。 - 交接:
- 交接文档写入
<系统临时目录>\dsh-handover\handover-<会话短id>-<时间戳>.md; - 文档内容 = 该会话持久日志的派生历史(元信息表 + 按时间排序的对话与工具记录 + 涉及的文件 + 执行过的命令 + 接手指引),超出体积预算时省略最早的部分;
- 随后插件新建一个会话(沿用当前工作目录与模型选择),切换过去,并发送首条消息让它先读交接文档:
模型总结:文档里已经包含「待办与下一步」的计划,新会话读完文档后按计划继续推进;脚本节选:新会话读完文档后会先问你选哪种方式确定接下来的计划——① 由它根据文档生成一份「下一步计划」给你确认(只输出计划,不改文件、不执行),② 由你手动输入计划;你确认后才开始执行。
- 交接文档写入
命令行也能直接用:
/handover 生成交接文档并返回路径
/handover --where 返回交接目录(不存在则创建)
/handover --write <绝对路径> 在指定路径生成交接文档(路径必须在交接目录内)
在「设置 → 插件」里改(推荐)
插件在宿主注册了 settings namespace dsh-context-actions,并在浏览器注册了同名卡片,所以在 设置 → 插件 → 插件配置 里会出现一张「上下文动作(压缩 / 交接)」卡片:
- 卡片里的字段跟着生成方式显示:
脚本节选(默认)时只有「交接文档生成方式」一项;选成模型总结后,才出现 总结模型 provider、总结模型 model id、自定义提示词(整段可编辑的文本框); - 自定义提示词默认就显示内置那份(不再是空白框):直接在上面改,改完即保存;点「恢复全部默认」又回到内置文本;
- provider / model 是下拉框,候选来自本部署已配置的模型目录(
session/modelCatalog):provider 列出所有已配置的 provider(含当前不可用的),选中后 model 才解锁并列出该 provider 的模型;两栏的第一项都是「跟随会话(不固定)」; - 目录读不到时会自动退回手动输入(不会卡住);已保存但不在目录里的路由显示成「…(已保存但当前不可用)」,不会被清掉;
- 每项保存即写入用户设置(
settings.yaml的dsh-context-actions:段),无需重启:命令每次执行都会重新读取当前值; - 被用户改过的字段会标「已覆盖」;底部「恢复全部默认」一键清空本插件的全部用户设置(回到组合层/内置默认),没有覆盖时该按钮是灰的;
- 卡片标题/描述/字段文案随语言(中/英)切换。
命令行方式(不改文件、只影响一次调用):/handover --llm、/handover --mechanical。
配置的优先级:用户设置(设置页)> loader 行的 config(base)> 内置默认值。所以下面的 YAML 写法仍然有效,只是会被设置页覆盖。
其余策略是固定的(刻意不放进卡片):单次总结超时 180s、输出长度不设上限(由提示词要求简洁)、总结失败自动回退脚本节选并在文档顶部注明、摘要后附一份脚本节选纪要。
生成方式:脚本节选 / 模型总结
默认沿用脚本节选(零模型调用)。想固化成部署默认值(设置页的 base)时,在 profile 的 cordis.patch.yml 里给这一行加 config:
- id: context-actions
config:
handover:
mode: llm # mechanical(默认)| llm
provider: '' # 可选:总结用模型;留空 = 跟随该会话最近一次请求的路由(设置页里是下拉选择)
model: ''
prompt: '' # 可选:整段替换发给模型的提示词;留空 = 内置默认(默认已要求简洁、1-2 页,并包含下一步计划)
插件不再限制模型输出 token;输出长度由提示词控制。默认提示词要求文档控制在 1-2 页,并在「待办与下一步」里给出 1-3 条可执行的计划。
模型总结(mode: llm)怎么工作:
- 整段上下文一次发过去:把该会话的派生历史(用户需求、助手回复、工具调用与结果)整段渲染成文本,不做单条截断,加上会话元信息后一次请求发给模型;
- 只有总量超过「该会话模型上下文窗口 − 固定输出预留(8192 tokens)− 1024 tokens 余量」时,才从最早的记录开始省略(必要时再截断最早那一条),并在文档表格里写明「省略最早 N 条」;上下文窗口取自该会话最近一次请求的
request/context,拿不到时按 128k 估; - 提示词可在设置页整段替换(
prompt);默认值就是内置那份,所以框里直接能看到、直接改。内置默认要求输出固定结构:标题「交接文档:<一句话任务名>」,二级小节为 任务目标 / 当前状态 / 关键决策与约定 / 涉及的文件与命令 / 待办与下一步(1-3 条可执行的下一步计划)/ 风险与未解决的问题 / 接手建议,并附带硬性规则(只写事实、原样保留路径命令与错误原文、压缩摘要里的既有事实要带上、长工具输出只取结论、1-2 页、不写开场白); - 文档结构:元信息表(含生成方式、总结模型、喂了多少字符、输入/输出 token、耗时)+
## 交接摘要(模型生成)+## 附:对话纪要(脚本节选)+ 文件/命令清单 + 接手指引; - 失败(无路由、无适配器、超时、限流、空输出等)自动回退脚本节选,并在文档顶部注明原因;
- 成本:一次总结按所选模型计费,输入规模就是上面的「整段上下文」(超大窗口模型下通常几万到几十万 token);想省钱就给该会话选小模型,或把提示词改得更短、更严格。输出长度不再由插件设上限,由提示词与提供方共同决定。
关于「压缩」按钮和 agent 预设
Web 部署故意把压缩后端交给 agent 预设:minimal 预设的注释写着 "Context compaction is absent.",所以用它创建的会话里 /compact 根本不存在(命令目录里没有它)。此时「压缩」按钮不会瞎执行,而是提示:
当前 agent 预设未挂载压缩后端(/compact)
想让它在 minimal 会话里也能用,二选一:
- 换预设:新建会话时选
standard(或ptc/cordis)预设; - 把两行开回宿主面:在
$DSH_HOME/profiles/web/cordis.patch.yml里加(本插件的补丁层之后应用,会覆盖dsh-web-app的disabled: true):
- id: compaction-basic
disabled: false
- id: command-compact
disabled: false
注意:compaction-basic 的 auto 默认是 true,所以第 2 种做法会给 所有 会话(含 minimal)打开自动压缩,这是官方架构里刻意放在预设面的行为,请自行权衡;只想要手动 /compact 可以再补 config: { auto: false }。
实现要点
- 宿主半侧
lib/index.js:Cordis 插件(inject: ['commands', 'sessions']),注册/handover命令。文档内容来自ctx.sessions.get(agent.id).deriveMessages(),即该会话持久日志的派生历史,因此不额外请求模型;写入前校验目标路径必须落在自己的临时目录内。顺带尽力解析<DSH_HOME>/sessions/**/<sessionId>/session.*.jsonl*原始日志路径写进文档。 - 浏览器半侧
lib/client.js:window.__ModuleLoader__.load({ id, factory })形式的客户端 bundle。apply(ctx)里用ctx.inject(['sessions', 'remote', 'remote.commands', 'remote.session'], ...)等必需服务(缺服务只会让这个 fiber 挂起,不会拖垮 Web 启动),然后:MutationObserver监听 DOM,用button[aria-haspopup="dialog"]且aria-label含百分号的触发器定位上下文面板,把按钮容器插到面板末尾(面板由dsh-client-ui-conversation内部渲染,没有对外 slot,因此走 DOM 注入);- 交接流程:
remote.commands.execute(id, '/handover --where')取目录,拼文件名,--write写入,sessions.create({ cwd })建会话,sessions.open(newId)切过去,beginSubmission() + prompt()发送首条消息; - 新会话尽力沿用当前会话的模型(
projectionValues.modelSelection转remote.session.selectModel)。
源码结构
<仓库根目录>\
package.json # dsh.bundle.patch + dsh.client(platform: web) + exports["./client"]
cordis.patch.yml # 向 profile 树 insert 一行:id: context-actions
lib\index.js # 宿主半侧:/handover 命令 + 交接文档生成
lib\client.js # 浏览器半侧:面板按钮注入 + 两个动作
README.md # 使用与开发文档
CHANGELOG.md # 版本记录
LICENSE # MIT
test\ # node:test 单元测试(宿主 + 浏览器半侧)
.github\workflows\ # CI / Release
改动后同步到 profile(dsh plugin add 用的是硬链接副本,改同样的文件路径即可就地生效;新增文件需要重跑一次):
dsh plugin --profile web add file:<仓库绝对路径>
排查
| 现象 | 处理 |
|---|---|
| 面板里没有按钮 | 刷新页面;确认 dsh --profile web --dump-config 里有 context-actions 行;看浏览器控制台里 [dsh-context-actions] 前缀的告警 |
| 交接报「宿主未注册 /handover 命令」 | 宿主半侧没加载,重启 dsh |
| 交接报「拒绝写入」 | 目标路径不在 <系统临时目录>\dsh-handover 内 |
| 压缩报「当前 agent 预设未挂载压缩后端」 | 见上一节 |
| 新建会话的首条消息没送达 | 状态行停在「已新建会话,但首条消息未送达」,会话已建好,手动把文档路径发过去即可 |
已知取舍
- 上下文面板是包内实现:选择器基于
aria-haspopup=dialog加aria-label含%加role=dialog,dsh 后续版本若改结构,需要同步调整lib/client.js里的findPanel()。 - 交接文档默认是 机械摘要(日志的忠实节选):零 token、零延迟、内容可信;代价是没有「人话总结」。
开
handover.mode: llm后正文改为模型总结(模型看不到你没想到要喂给它的东西,仍受截断与预算约束),附录依旧是脚本节选,便于核对。 - 「压缩」不做任何绕过预设的 hack,避免改掉部署的自动压缩行为。
开发与测试
npm test # Node 内置 node:test,零依赖
npm run check # lib/index.js + lib/client.js 语法检查
npm run pack:dry # 预览 npm 包内容
测试分两层:
test/index.test.js:用假 ctx/session 驱动真实的/handover命令链(参数解析、配置回退、路径校验、机械折叠、llm 回退)。test/client.test.js:用假window/React 桩加载浏览器 bundle,校验 bundle 契约与「脚本节选 / 模型总结」字段显隐。
CI:push / PR 触发 Node 18、20、22 矩阵;推送 v* 标签触发 Release workflow —— 生成 GitHub Release,配置 NPM_TOKEN secret 后会同时发布到 npm(带 provenance)。
开源许可
MIT License,全文见 LICENSE。
参与贡献
- 提交 Issue / PR 前先跑:
npm test(18 个零依赖单元测试)与npm run check。 - 改完源码后用
dsh plugin --profile web add file:<仓库绝对路径>重新同步到 profile,再重启 dsh(宿主半侧)并刷新页面(浏览器半侧)验证。 - 打包预览:
npm pack --dry-run;正式打包:npm pack --pack-destination dist,产物为dsh-context-actions-0.1.0.tgz。 - 仓库地址:https://github.com/CN-Hang/dsh-context-actions;如果你 fork 后发布,请把
package.json里的author、repository、homepage换成你自己的信息。