DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Session Reattach — DSH Plugin for DeepSeek Harness
← Plugins

dsh-session-reattach

Session Reattach

Session reassignment: reassigns ungrouped sessions to matching workspaces based on their canonical cwd, modifying only workspace ownership records without touching session files.

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add dsh-session-reattach@0.1.2
READMECompatibilityVersions

Compatibility and provenance

Session Reattach is published as dsh-session-reattach and currently resolves to version 0.1.2. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
npm
Registry updated
9/14/2026

Versions

0.1.2

README

🌏 English · 中文

dsh-session-reattach

把游离(未分组)会话按 cwd 送回它该在的工作区
只改归属记录 · 不碰会话文件 · 默认 dry-run

特性 · 用法 · 安装 · 快速上手 · 判定规则 · 安全边界 · 与同类插件 · 常见问题 · 开发


会话被移动过、或在工作区还不存在时就创建过之后,它可能永远回不到任何工作区分组里 —— 侧边栏里只剩一个「未分组」。这个插件补上那个缺失的入口:把会话拖进它该在的工作区文件夹。

名字

reattach(重新归属):会话文件一个字节都不动,只把 workspace.json 里那份归属记录改对。 之所以叫「归位」而不是「移动」,是因为它做不到任意移动 —— 见判定规则: DSH 只接受 cwd 与工作区目录完全一致的归属,所以这个插件能把会话送回它本来就在的那个目录, 但不能把它迁到别的目录去。

特性

  • 补的是 DSH 的一个真实空白 —— Workspace.attachSession 只在新建会话 / 新建子代理时被调用;历史会话的自动收养在 WorkspaceRegistry 初始化时只跑一次(initialized 落盘即永不再跑);而全部 @Remote(...) 端点里没有任何 attach 类方法。于是「会话移动过 ⇒ 永久游离」没有任何修复入口,本插件就是那个入口。
  • 只改归属,不碰会话文件 —— 不做任何文件写入,会话日志(session.jsonl.zstd)与首帧 cwd 都不动。与「改会话首帧 cwd 把自己挂到别处」是两条路。
  • 写入权威只有 DSH —— 归属校验与落盘全部走 Workspace.attachSession / detachSession;插件自己不做路径判定以外的任何决定,也不直接写 workspace.json。
  • 先清后挂 —— 归位前先从其余工作区清掉该会话的陈旧槽位。这不是洁癖:attachSession 不复核别的工作区,而 registry 启动校验会因为同一会话被两个工作区记账而直接抛错、整个工作区域不可用;顺序错了就会踩这个坑(审计已证实)。
  • 同 canon 判定 —— 用 fs.realpath 把会话 cwd 与工作区 path 归一到同一套 canon 再比较:尾斜杠、..、符号链接、大小写差异都不会误判;判定结果与真实 WorkspaceEntity 逐条一致(审计逐条比对过)。
  • 默认 dry-run —— 只有显式 "dryRun": false(或命令里写 apply)才写入;"false" 字符串、0、null、[] 一律仍是 dry-run。
  • 拖拽时只高亮合法落点 —— 开始拖动的那一刻就算出该会话唯一能去的工作区(浏览器侧用 SessionSummary.cwd 预筛),只有那个分组接受放置;其余分组显示禁止光标。判断只作提示,最终以宿主判定为准。
  • 失败关闭 —— 清槽位失败就放弃 attach(绝不冒双归属的险);会话数量超上限会明说「还有 N 个更早的会话未参与判定」,不假装看全了;上游改了侧边栏结构就整体静默(什么都不做,绝不写错数据)。
  • 零运行时依赖 —— 不 import 任何 DSH 包,也不装任何第三方包,只用 ctx 上的服务;纯 JS 全平台可用。

用法

入口作用怎么触发
拖拽把「未分组」里的会话拖进它该在的工作区分组侧边栏里直接拖;cwd 匹配的分组会亮虚线框
/reattach只报告:将归位哪些会话、哪些不能归位及原因输入框敲,不经过模型
/reattach apply执行归位同上
/reattach apply subagents连带处理子代理会话(默认跳过)同上,参数可任意顺序

拖到别的分组不会被写入(禁止光标)。若该会话本来就不在可归位集合里(cwd 已失效、没有对应工作区、已归档……),插件不猜原因,而是放行落点、请宿主给出准确结论,再用右下角提示条显示 —— 所以你看到的一定是真实原因。

/reattach 是纯宿主命令:不消耗 token、不受模型判断影响,也是拖拽万一失效时的兜底通道。

下面是示例(分类口径与数字取自一次真实 dry-run:78 个会话、10 个工作区):

会话归位 · dry-run(未写入任何内容)

可归位 2 个:
• session-xxxx → 「team-manage」(/Users/me/Project/team-manage)
• session-yyyy → 「dsh-palimpsest」(/Users/me/Project/dsh-palimpsest)

无匹配工作区 1 个:
• session-zzzz:/Users/me/Downloads

cwd 无法校验 1 个:
• session-wwww:cwd 已失效(目录被移动、改名或不可访问)(/Users/me/old-project)

跳过:已归档 39 个、子代理会话 11 个
已归属、无需动作:26 个

执行:/reattach apply,或把会话直接拖进侧边栏里对应的工作区分组。

安装

从 npm / GitHub

dsh plugin --profile web add dsh-session-reattach
# 或
dsh plugin --profile web add github:cnkids/dsh-session-reattach

从本地路径(开发中)

在插件目录下执行(link: 会让源码改动直接生效,不必每次重装):

dsh plugin --profile web add "link:$(pwd)"

安装命令会把依赖写进 profile 的 package.json,并把声明了 dsh.bundle 的包追加进 dsh.profile.bundles。

装完必须重启 dsh web —— 宿主插件由 cordis loader 在启动时装配,本机 profile 没有开 patchReload: live,hmr 在 dsh-base 里默认 disabled: true。

本插件是 web profile 专用:宿主半边需要 webServer 与 connection 两个服务(后者由 dsh-client-connection 的宿主半边提供)。缺任意一个,cordis 会把插件停在「等待依赖」而不启用 —— 这是刻意的失败关闭,不会留下一个没有守卫的写入端点。

快速上手

装好重启后,先看一眼有没有要修的(这一步不写任何东西):

/reattach

确认列表没问题再执行:

/reattach apply

日常更省事的是直接拖:在侧边栏展开「未分组」,按住某个会话行开始拖动,cwd 匹配的那个工作区分组会立刻亮起虚线框,拖进去松手即可 —— 右下角提示条会告诉你结果,侧边栏自动刷新。

判定规则

匹配 = realpath(会话头 cwd) === workspace.path

工作区 path 在建记录时就已是 fs.realpath 的结果(无尾斜杠、无 ..、符号链接已解开),所以两端都走 realpath 才是同 canon 比较。以下写法都算匹配(会被归一掉):/work/、/work/.、/work/sub/..、指向同一目录的符号链接,以及大小写不敏感卷上目录名的大小写差异。

匹配不上的五种成因,各有独立文案:

#成因归类与提示
1会话头没有 cwd跳过:无法校验
2cwd 不是绝对路径跳过:非绝对路径
3目录已删 / 改名 / 不可访问跳过:cwd 已失效
4路径存在但不是目录跳过:不是目录
5能解析、是目录,但与该分组的 path 不同拖错分组:该目录属于另一个工作区就点名「只能归位到 X」;没有任何工作区记录该目录则提示先在侧边栏为它创建工作区

为什么不做任意迁移:DSH 的 attachSession 会拒绝 cwd 与工作区目录不一致的归属,这是宿主的硬校验,不是本插件的限制。所以拖拽在这里的语义是「把它送回它本来就在的那个文件夹」。

安全边界(请如实理解)

这是一条会写工作区归属的通道,所以边界要说清楚,而不是笼统说「安全」:

  1. 请求信任 —— 路由与 /api 用的是同一套守卫:Host 必须是回环或受信、Origin 存在时必须与 Host 同源、Sec-Fetch-Site: cross-site 直接拒,之后还要过浏览器认证。实测未认证 / 跨源 / Origin: null / 伪造 Host 一律 401/403,且先拒绝、后不解析业务体(审计对运行中的宿主实测)。
  2. 默认不写 —— 只有显式 "dryRun": false 才落盘;类型混淆打不开这个开关。
  3. 失败关闭 —— 清槽位失败就放弃 attach;计划数据形状不对时前端不猜落点;上游改了侧边栏 DOM,拖拽整体静默。
  4. 不越权 —— 只用 attachSession / detachSession,不写文件、不起子进程、不联网。

说清楚代价与做不到什么:

  • 它会 mutate 非目标工作区:归位一条会话时,插件对每个非目标工作区调用一次 detachSession;DSH 的 mutate 会顺带持久剪枝该工作区里「已索引 cwd 与记录不符」的候选。剪枝判据是持久事实(会话头没有 cwd、或 cwd 解析到别处),不是「目录暂时不可达」,所以外置卷临时卸载不会掉归属;而被剪掉的会话在目录恢复后会被本插件重新归位。不这么做的替代方案是留下双归属,让下次启动 validateStoredState 直接抛错。
  • 它靠侧边栏的结构工作:会话行锚点 [role="treeitem"][aria-selected]、工作区分组行锚点 [role="treeitem"][aria-expanded][draggable="true"],以及内置 onDragStart 把会话 id 写进 dataTransfer 的 text/plain 这一行为。上游改了其中任何一处,拖拽会静默失效(识别不到就什么都不做),/reattach 命令不受影响。
  • 分组靠标题匹配:DOM 行不携带工作区 id,只能按可见标题匹配。标题重复时可能高亮到同名的另一个分组 —— 那种情况下宿主仍会拒绝并给出「只能归位到 X」,最坏结果是一句明确报错。
  • /state 的成本与调用方相同源:每次请求对每个会话做 realpath + stat(本机 78 会话约 10 ms 量级)。宿主侧刻意不加缓存(缓存会把刚变化的归属喂给写入判定,让一次合法归位变成失败);调用方必须已认证,而同源脚本本就能调 /api 做成本更高的事。

与同类插件的差异

这个方向上的常见做法是「会话移动器」(拖拽迁移、批量操作、回收站、分组合并),本插件的取舍是只做归位:

本插件移动器类插件
能力只重新归属:把 cwd 有效但没被记账的会话挂回匹配的工作区任意跨工作区迁移、批量、回收站、分组
会话文件不碰(不重写首帧 cwd)常需改写会话首帧或搬动会话目录
允许的落点只有 cwd 与工作区目录完全一致的那一个(宿主硬校验)由插件自己定义,通常任意
写入面两个已存在的宿主 API(attachSession / detachSession)自建存储 / 索引与迁移逻辑
运行时依赖零(不 import 任何 DSH 包)常见对 DSH 内部包的直接依赖
安全审计有(8 条 PoC:真 socket、真实 WorkspaceEntity、运行中宿主的守卫探测;产物不随仓库分发)未见

与 dsh-palimpsest 的关系(一处常见误判):palimpsest 的硬范围只比对会话头 cwd,候选来自 sessionQuery.listSessions()(live + 全部持久化会话),与工作区归属无关。所以一个「未分组」会话只要 cwd 等于当前会话的 cwd,palimpsest 今天就能读到它 —— 本插件修的是侧边栏分组与工作区记账,不是 palimpsest 的可见性。两者解耦,不需要为彼此改一行代码。

常见问题

装完没反应? 必须重启 dsh web(宿主插件只在启动时装配)。重启后 /reattach 应出现在命令面板里。

拖不动 / 拖进去没反应? 先确认那个会话的 cwd 对应的目录存在且是目录;目录已删的会话只能在报告里看到「cwd 已失效」。如果是上游改了侧边栏导致拖拽整体失效,用 /reattach(功能不受影响),并在 issue 里带上你的 DSH 版本。

拖错分组会怎样? 不会写错。cwd 不匹配的分组不接收放置;万一同名标题让它放行了,宿主仍会拒绝并提示「只能归位到 X」。

会不会动我的会话? 不会。插件只调用 attachSession / detachSession 改归属,会话日志与首帧 cwd 一个字节都不改。

为什么不能把会话移到别的项目? 因为 DSH 只接受 cwd 与工作区目录完全一致的归属。想换项目,得先改会话首帧 cwd(那是另一类工具的事),本插件刻意不碰文件。

子代理会话要不要一起归位? 默认跳过(它们嵌在父会话下,单独归位收益低)。需要时用 /reattach apply subagents。

已归档的会话为什么不动? 归档会话在侧边栏本就不可见,而且归档不会摘掉归属槽位,所以没有补写的必要。

没有对应工作区的会话怎么办? 插件只报告、不创建(职责边界就是「归位」)。按提示先在侧边栏为那个目录创建工作区,再归位。

会不会拖慢 DSH 启动? 不会。插件不做任何启动期扫描或索引。

连不上网能用吗? 可以。插件不联网、零运行时依赖。

为什么叫 reattach? 见名字。

开发

npm test                       # 121 个用例:纯逻辑单测 + 宿主装配 + HTTP 真 socket + 浏览器半边行为
npm run coverage               # 同上,并生成 coverage/lcov.infonode scripts/build-client.mjs  # 改了 client-core.js / client-dom.js 后必须重新生成

覆盖率(Node 内置统计):行 99.60%、函数 99.13%、分支 96.24%。

改了 lib/client-core.js 或 scripts/client-dom.js 一定要重新生成 lib/client.js;忘记生成会让 test/client.test.mjs 的漂移检查失败。浏览器端 bundle 必须是 classic script(内置模块系统用 <script src> 加载),既不能写 import / export,也不能被 Node 直接 import —— 这就是「纯逻辑单独成模块 + 生成」的原因。

安全审计:8 条可独立运行的 PoC(真 socket 的 413/HTTP 语义、与真实 WorkspaceEntity 的逐条比对、以及针对运行中宿主的守卫探测)在仓库外维护,不随仓库分发;结论已并入上文「安全边界」。

SonarQube:

npm run coverage        # 先产出 coverage/lcov.info,否则覆盖率是空的
npm run sonar           # = ./scripts/sonar-check.sh,分支自动取当前 git 分支
npm run sonar -- main   # 显式指定分支

令牌绝不写进仓库。本机约定是在 ~/.zshrc 里按项目放一个变量:

export SONAR_TOKEN_DSH_SESSION_REATTACH=sqp_xxxxxxxx

scripts/sonar-check.sh 会优先取它并映射成 SONAR_TOKEN,未设置时回退通用 SONAR_TOKEN;脚本还负责分支与版本号(不带 sonar.branch.name 时结果会写进 SonarQube 主分支)。

代码结构:lib/core/ 是不依赖 DSH 的纯逻辑(路径 canon、计划、执行、渲染),lib/host.js 是与 ctx 的唯一接触面,lib/http.js 与 lib/command.js 是两个入口,lib/index.js 是插件入口;浏览器半边是 lib/client-core.js + scripts/client-dom.js(生成 lib/client.js)。

HTTP 契约(供二次开发与排查):

方法路径说明
GET/session-reattach/state工作区清单 +「可归位会话 → 目标工作区」映射,供前端预筛落点
POST/session-reattach体 {dryRun?, sessionId?, workspaceId?, includeSubagents?};不写 dryRun 即 dry-run

single.code 取值:ok / already-accounted / cwd-invalid / no-workspace / workspace-mismatch / workspace-not-found / session-live / session-archived / session-subagent / session-not-found;内部故障一律 internal-error(细节只进宿主日志)。

发布(npm + GitHub Actions)

v* tag 由 .github/workflows/release.yml 接管:校验 tag 与 package.json 版本一致 → npm ci → npm audit → npm test → npm publish --provenance(OIDC,无需任何 token secret)→ 建 GitHub Release 并附 .tgz;main 推送与 PR 由 ci.yml 跑同一套测试与审计。

首次发布必须先手工做一次:npm 没有 pending publisher,包在 registry 上不存在时无法配置 Trusted Publisher(会得到误导性的 404 … is not in this registry)。顺序是:

# ① 手工首发(会要 2FA 或一个启用了 bypass-2fa 的 granular token)
npm login
npm publish --access public

# ② 到 npmjs.com 的包设置里配置 Trusted Publisher → GitHub Actions:
#    组织/用户 = cnkids   仓库 = dsh-session-reattach
#    工作流文件名 = release.yml
#    环境 = 留空(发布 job 不引用 environment,两边必须一致,否则 OIDC 声明不匹配)

# ③ 推仓库与 tag;首个 tag 会在「已存在同版本」时**跳过** npm 步骤并照常建 Release
git remote add origin git@github.com:cnkids/dsh-session-reattach.git
git push -u origin main
git tag v0.1.1 && git push origin v0.1.1

从下一个版本开始,trusted publishing 才真正接管:改 package.json 版本 → 同步本文件版本记录 → 提交 → git tag vX.Y.Z && git push origin vX.Y.Z,CI 直接发布(provenance 自动附带)。

三个容易踩的点(都是实测教训,不是理论):

  • npm ci 需要 package-lock.json —— 本仓库零运行时依赖,所以 lockfile 很小,但必须有(poc8 会断言它在)。
  • Trusted Publishing 要求 npm ≥ 11.5.1,而 Node 22 自带 npm 10.x;版本不够时 npm publish 会返回误导性的 404。发布 job 因此固定 npm install -g npm@11.5.1。
  • 发布 job 不引用受保护环境:environment 的审批门在 GitHub 侧故障时会卡死发布;npm 侧的 Environment 也必须留空才能与之一致。发布靠 OIDC 短时令牌 + provenance 兜底。

版本记录

版本变更
0.1.2只改文档与仓库形态:安全审计产物(报告 + 8 条 PoC)移出仓库改为本地维护,README 不再指向仓库内路径、CI 取消审计步骤;测试夹具里的真实会话 id 换成合成 UUID。lib/ 无改动;相对 0.1.1,发布物只差 README 与版本号
0.1.1安全审计落地:体积超限不再拆连接(413 必达)、路由加错误边界(宿主故障回可诊断的 JSON 500 而不是空 400)、会话数量截断如实上报(不再谎报「没有可归位的会话」);完成安全审计(8 条 PoC:真 socket、真实 WorkspaceEntity、运行中宿主的守卫探测;产物不随仓库分发);README 按 dsh-palimpsest 版式重排并补安全边界;用例 105 → 121
0.1.0首个版本:宿主侧归位核心(realpath 同 canon 判定、五类分类、先 detach 后 attach)、HTTP 路由(复用 /api 守卫、默认 dry-run)、/reattach [apply] [subagents] 命令、浏览器半边拖拽落点(预筛高亮 + 提示条)、零运行时依赖

许可证

MIT

stable
9/14/2026

Related plugins

More verified plugins in productivity-workflow.

Codex Ui@michengai/dsh-codex-uiDSH Codex UI — 为 DeepSeek Harness Web 提供 Codex 风格侧栏、工作区会话树、全局搜索和轮次导航 · A Codex-style sidebar, workspace session tree, global search, and turn navigation for DSH WebAcp App@deepseek-ai/dsh-acp-appThe dsh ACP profile bundle: automation-only JSON-RPC stdio and process lifecycle over dsh-baseAutomation@michengai/dsh-automationExecute coding tasks on a schedule in an independent DSH Session, with dual-entry management via a Web settings page and Agent.Easyrewritedsh-easyrewriteDSH Web内目前最无感的消息撤回、重编辑插件,原版体验,兼容性强,功能简单可开关,设置丰富,现代化轻量ui框架。The most seamless message recall & re-edit plugin for DSH Web — native experience, strong compatibility, simple toggles, rich settings, modern lightweight UI. DSH Web で最もシームレスなメッセージ撤回・再編集プラグイン——ネイテ
Latest
0.1.2
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
104.8 kB
Files
16
Surface
web
License
MIT
Source
npm
GitHub
★ 0
Weekly downloads
0
Last push
9/14/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in