メインコンテンツへスキップ
X

dsh-xwide-handoff

xwide/dsh-xwide-handoff

Generates a structured handoff card so a near-full conversation continues in a fresh zero-context session: /handoff writes the card with self-incrementing titles, pins it to the top of the sidebar and prepares the new session once the context nears its threshold.

インストール

dsh plugin --profile web add github:xwide/dsh-xwide-handoff

README

dsh-xwide-handoff

给 DeepSeek Harness (DSH) 用的交接插件:一条 /handoff 把「快聊满的会话」变成「一张结构化交接卡 + 一个干净的新会话」,并在侧栏自动置顶。上下文快满时还能自动把下一棒备好。

version status platform license

中文(本页) · English · 安装与使用指南 · 更新日志 · MIT License

状态:v0.2.2 · beta。 核心流程(/handoff → 交接卡 → 新会话 → 自增命名 → 置顶)在作者机器上长期在用;跨机器安装路径(尤其 childMode: none)尚未在第二台机器验证。安装两步、配置项表、五条故障排查见 安装与使用指南。


它解决什么问题

长任务会把会话上下文顶满。官方的自动压缩是有损的:它会把细节吃掉,而且压完你还在同一个会话里,继续顶。

这个插件换一条路 —— 交接:

  1. 把「当前做到哪一步 / 改过哪些文件 / 验证过什么 / 下一步做什么」写成一张交接卡;
  2. 起一个上下文清零的新会话,把卡片作为它的第一句;
  3. 新会话自动命名(原会话名_01、_02…,落盘计数器,不重名)、自动置顶、自动归入源会话所在的项目分组。

于是你换了个房间,但接力棒没丢。

安装

# 在 DSH profile 目录里
pnpm add dsh-xwide-handoff

下载即用(不需要 npm / pnpm):把本仓库整个目录放进 <你的 profile>\local-plugins\dsh-x-wide_handoff\,然后在 profile 的 cordis.patch.yml 末尾加一行挂载,重启 DSH:

- id: dsh-xwide-handoff
  name: './local-plugins/dsh-x-wide_handoff/index.js'
  config:
    enabled: true
    commandName: handoff
    childMode: create
    recentUserMessages: 6

⚠️ 只能有一个装载实例:包自带 patch 插一条 + profile 再直装一条 = 两个实例(命令注册两次、watcher 双份)。现象是启动日志里 /handoff registered 打印两次。详见 故障排查①。

用法

指令菜单里能看到 handoff 这一条(说明命令已注册),或在输入框直接敲 /handoff:

DSH 指令菜单里的 handoff 命令

/handoff 本次重点:把 X 的解析逻辑改完;已改 a.js;未验证回归
  • /handoff 后面写的内容会进卡片的「本次交接重点」。
  • 不写也行 —— 卡片会自动带上「本轮动过的文件」和「你最近 6 条原话」。
  • 命令执行完会告诉你新会话的 id 和标题,并把它置顶。

注意:/handoff 是斜杠命令,只能由用户在输入框敲,模型不能自己调用。所以插件同时提供 A 半自动(见下),到阈值时替你把它备好。

A 半自动(autoReady)

打开后,插件监听每一轮的结束(turn/end),读最近一次请求携带的上下文量:

占用 = 最近一条 assistant/message 的 usage.inputTokens ÷ contextWindow
  • ≥ readyPct(默认 0.7) → 自动跑一遍交接:建会话、命名、置顶、注入卡片、驱动它跑完首轮(这样它在侧栏里一定看得见)。
  • 不替你切会话 —— 你从侧栏点那条置顶的新会话继续。DSH 没有「服务端让浏览器换会话」的公开接口,这是有意的边界。
  • 保险丝:同一会话只备一次、全局冷却(默认 10 分钟)、总开关默认关。

⚠️ 判占用必须用 inputTokens。usage.totalTokens 是累计消耗(见过 20 万),拿它判会 100% 误触。

配置

- id: dsh-xwide-handoff
  name: './local-plugins/dsh-x-wide_handoff/index.js'
  config:
    commandName: handoff          # 命令名,默认 handoff
    childMode: create             # none | create | fork(见下)
    recentUserMessages: 6         # 卡片带最近几条你的原话
    compact: false                # 交接前是否先本地压缩一次(本地大模型请保持 false)
    titleTemplate: '{base}_{seq}' # 标题模板,支持 {base} {seq} {note} {time}
    seqWidth: 2                   # 序号位数:_01
    maxTitleChars: 64             # 标题字符上限
    repinLimit: 3                 # 自愈重钉次数上限
    autoReady: false              # A 半自动总开关
    readyPct: 0.7                 # 触发阈值(占上下文窗口比例)
    readyCooldownMs: 600000       # 冷却
    contextWindow: 262144         # 上下文窗口(判占用用;默认取自作者的本地模型,请按自己窗口改)
    cardSections:                 # 卡片栏目开关
      guide: true                 # 「接手须知」
      files: true                 # 「本轮动过的文件」
      usage: true                 # 「源会话上下文」

childMode 三档

值行为什么时候用
create服务端直接建全新空会话(上下文清零,继承 cwd 与 agentPreset)默认推荐。不依赖客户端半边
none服务端不建会话,把卡片 JSON 塞进结果文本,由 client.js 用 GUI 原生 API 建可见会话需要「和点『新建对话』完全一致」时;要求 client.js 已正确作为 dsh.client 打进包,跨机器未验证
fork继承全部历史(上下文不清零)只想换个标题、不想清上下文的场景

⚠️ 三种模式都不能带 parentSession —— DSH 会把带 parentSession 的会话从列表和可恢复集合里排除,那正是「交接完看不见新会话」的经典成因。

交接卡长什么样

调节Qwen3.8 Agent对接本地模型_07          ← 第一行 = 目标标题(官方取名启发式直接取它)

# 交接卡 · 调节Qwen3.8 Agent对接本地模型_07

## 接手须知(先读这一段)
1. 先读完整卡片:源会话 cwd 下的 handoff/ 目录里最新那份 .md 就是本卡全文
2. 先报计划再动手:一句话说清先做哪条、大约要多久、有没有分支
3. 不要重复已完成步骤:下面「本轮动过的文件」都改过了,先读现状
4. 拿不准就去查证:文件、目录、会话记录都在磁盘上

- 源会话上下文:约 18461 tokens(最近一次请求携带量;判占用看它,不看累计消耗)

## 本轮动过的文件(3 个,自动提取自 5 次 write/edit 调用)
- `C:\…\local-plugins\dsh-x-wide_handoff\index.js`(write×1 edit×4)

## 最近你的原话(逐条原文,按时间顺序)
1. …

工作原理(给要改代码的人)

  • 命名:序号来自落盘计数器(~/.dsh/named-handoff-counter.json,按 cwd 分桶单调递增),不是从源会话标题尾号推算 —— 源会话每次都是新开的、标题没有尾号,推算永远得到 _01。名单落在 ~/.dsh/named-handoff-names.json。
  • 钉名三通道:① 卡片第一行就是目标标题(官方取名服务会读它);② ctx.sessions.using(...).binding.session.rename()(部分版本没有 using,会失败);③ session.append("session/title", …)。
  • 自愈重钉:官方取名服务会主动覆盖已钉住的标题(其注释自认 "overwriting a pinned user title is the point"),所以插件监听改名事件,发现名单内会话被非 user 来源改名就再钉一次(上限 repinLimit)。
  • prompt 需要 signal:sessionController.prompt(request, signal) 的包装层第一行就调 signal.throwIfAborted(),务必传 new AbortController().signal。
  • blank 只认 turn/start:侧栏会把 blank: true 的会话直接滤掉(标题还会显示成「新建会话」)。注入消息不会翻这个标记,所以必须真的驱动一轮。

兼容性

本插件使用了若干 DSH 内部接口(不是稳定公开契约),已在下列位置核对过:

用到的东西位置(DSH 桌面版内置包)
sessionController.prompt / create / resolveAgent@deepseek-ai/dsh-api-session-controller
session.append 的 surfaceOp 语义@deepseek-ai/dsh-session(SURFACE_EVENT_TYPES 只有 system/developer/user/assistant/message + tool/result)
system/message 必须是 surface 第一条@deepseek-ai/dsh-session-format-v3-to-v4 foldSurface()
workspaceRegistry.pinSession@deepseek-ai/dsh-workspace
projectController.baseline / assigndsh-project-groups(可选依赖,没装就跳过归组)
侧栏 blank 过滤@deepseek-ai/dsh-client-ui-workspace

DSH 升级后请复核上表。所有内部调用都写了宽容处理:失败只记日志,不让交接本身崩掉。

路线图

  • childMode: "none" 客户端半边的完整接线与回归
  • 卡片栏目可自定义模板
  • 交接卡自动带上「已跑过的关键命令」
  • 发布到 npm / DSH 插件市场,实现一键安装

License

MIT © 2026 XWIDE

関連プラグイン