Skip to content
dsh.fish
Bundle

dsh-session-id-for-desktop

DSH Desktop 会话行菜单扩展:复制会话 ID、永久删除会话(含存储)。Copy a session ID and permanently delete a Conversation from the sidebar row menu, built on DSH's official slots.

Source
meowmeowloong
License
MIT
Updated
Updated 8 days ago

Readme

# dsh-session-id-for-desktop

给 **DeepSeek Harness(DSH)** 左侧会话列表的行菜单补两项:**复制会话 ID** 与 **永久删除会话(含存储)**。

不改 DSH 源码,不读 React fiber,不往 DOM 上贴东西 —— 全部走 **DSH 官方插槽**。

> 实测环境:**DSH `0.2.0-rc.2`**(官方 Desktop,Windows x64;Web profile 同源)

---

## 功能

| 功能 | 入口 | 说明 |
| --- | --- | --- |
| **复制会话 ID** | 会话行 `⋯` 菜单 → 复制会话 ID | 写入剪贴板后菜单自动关闭 |
| **删除会话(含存储)** | 会话行 `⋯` 菜单 → 删除会话(红色) | 二次确认框:显示磁盘占用、明确「不可恢复」、说明附件不删 |

菜单里的顺序(官方项 + 本插件项):

```
重命名 (200)  →  复制会话 ID (150)  →  分叉 (300)  →  归档 (400)  →  删除会话 (500)
```

双击标题重命名由 **DSH 原生提供**,本插件不重复实现。

---

## 安装

**Desktop(推荐)**:DSH → **插件** → 添加插件 → 用下面任一来源安装本包

- 本地路径(开发用)
- GitHub:`github:zf-666888/dsh-session-id-for-desktop`
- npm(若已发布)

**Web profile(CLI)**:

```sh
dsh plugin --profile web add github:zf-666888/dsh-session-id-for-desktop
```

装完 **完全重启 DSH**。客户端代码字节是**启动时快照**的,只刷新页面不够。

安装后 `dsh-session-id-for-desktop` 会出现在 profile 的 `dsh.profile.bundles` 里,本包自带的 `cordis.patch.yml` 会自动把插件行插进 Loader,**不需要你改 profile 的任何文件**。

---

## 它到底删了什么

删除是不可恢复操作,所以这里写清楚:

```
$DSH_HOME/sessions/<工程目录键>/<会话 ID>/          会话日志目录(session.v<N>.jsonl.zstd + 同目录其它产物)
$DSH_HOME/storages/session_projcache/sessions/<会话 ID>.json   投影缓存
$DSH_HOME/storages/workspace.json                  从 archivedSessionIds[] 与各 workspaces[*].sessionIds[] 摘除该 ID
```

**附件不会删。** `$DSH_HOME/attachments/v1/objects/` 里是按内容寻址(sha256 分片)的对象,多个会话可能共用同一份,按会话删除会误伤其他会话。

「归档」与「删除」的区别:归档只是把会话移出列表,日志与记账位都保留,可移回;删除会把上面那些**物理抹掉**。

---

## 安全设计

- **删除前校验**:会话 ID 走 `^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`;路径段按存储后端一致的方式转义;每个候选目录再校验一次仍在 `sessions` 根之下;不跟随符号链接/junction。
- **正在运行的会话拒绝删除**:判定用 DSH 自己的 `agent/status`(`ctx.agents.get(id).status === 'running'`),不自己发明"忙碌"概念。只打开着但空闲的会话可以删。
- **只碰自己的接口**:`/api2/dsh-session-id-for-desktop/*`,仅 POST、仅 `application/json`、仅回环 `Host`(可用 `DSH_SESSION_ID_FOR_DESKTOP_TRUSTED_HOSTS` 追加受信主机名)。内容是 JSON 是为了强制 CORS 预检,而本路由不响应预检。
- **磁盘上已无该会话时也能收尾**:`inspect` 返回 `exists: false`,确认框改成"只把这一行从列表收掉",不再出现"看着能点、点了没反应"。

---

## 兼容性

| DSH | 本插件 |
| --- | --- |
| `0.2.0-rc.2`(Desktop / Web) | `0.1.x` |

依赖的宿主插槽(`dsh-client-ui-workspace` 声明,随 DSH 版本演进):

| 插槽 | kind | 用途 |
| --- | --- | --- |
| `sidebar.workspaces.session.menu.item` | `list` / `root` | 两个菜单项 |
| `shell.overlay` | `list` / `root` | 删除确认框(官方重命名对话框也在这里) |

---

## 已知限制

- **不提供右键菜单**。DSH 没有右键钩子,只能靠 DOM 去猜那个 `⋯` 按钮,脆弱且易误伤;而 `⋯` 菜单里已经有这两项,一次点击可达。
- **被删除的"打开着的空闲会话"**:磁盘与列表立刻干净,但进程内还会留着它的副本,直到重启 DSH 才彻底释放。
- **`workspace.json` 的摘除可能被回滚**:Workspace registry 在内存里另有一份状态,它下次写入会整份覆盖。残留只是一个指向"已不存在会话"的 ID,任何分组视图都不会渲染它;宿主的会话索引本身有对账逻辑。
- 只对 Web UI 生效;无头 profile 下宿主半边加载为空操作。

---

## 实现说明

### 为什么不用 `dsh-session-actions`

上游 `dsh-session-actions@0.3.1` 的浏览器半边靠 **React fiber props** 判定"这是会话行":

```js
props.node && typeof props.onRename === 'function' && typeof props.onArchive === 'function'
```

而 `0.2.0-rc.2` 的行组件签名已经变成:

```js
function SessionNodeItem({ node, currentId, now, onOpen, onRenameRequest, renderSlot, onReveal, drag, t })
```

**两个属性名都已不存在**,判定永远为假 —— 于是插件"加载成功但完全无效果",这是最难排查的一类失效。详见上游 issue:[duanyunlun/dsh-session-actions#1](https://github.com/duanyunlun/dsh-session-actions/issues/1)。

`0.2.0-rc.2` 其实**已经提供了官方插槽**,官方自己就是这么用的:

```js
renderSlot("sidebar.workspaces.session.menu.item", { sessionId, displayTitle },
           { hookContext: menuOpenState })

ctx.slots.inject("sidebar.workspaces.session.menu.item", function* () {
  yield ctx.slots.register({ name: "…", id: "pin",     order: 100, … }, PinSessionMenuItem)
  yield ctx.slots.register({ name: "…", id: "rename",  order: 200, … }, RenameSessionMenuItem)
  yield ctx.slots.register({ name: "…", id: "fork",    order: 300, … }, ForkSessionMenuItem)
  yield ctx.slots.register({ name: "…", id: "archive", order: 400, … }, ArchiveSessionMenuItem)
})
```

`sessionId` / `displayTitle` 由宿主直接给,不需要从 DOM 文本反推(两个同名会话也不会认错)。

### 文件

| 文件 | 作用 |
| --- | --- |
| `client.js` | 浏览器半边:注册两个菜单项 + 确认框。零依赖,只 require `react` 与 `@deepseek-ai/dsh-client-ui-primitives` |
| `index.js` | 宿主半边:挂 `/api2/dsh-session-id-for-desktop/{inspect,delete}` |
| `session-files.js` | 磁盘操作:定位会话目录、算大小、删日志、清投影缓存、摘工作区引用 |
| `cordis.patch.yml` | bundle 层:插入插件行 |

`index.js` 与 `session-files.js` 复用上游 MIT 代码(仅改路由前缀与插件名)。

---

## 卸载

在插件页停用/移除本 bundle,或从 profile 的 `dsh.profile.bundles` 里删掉 `dsh-session-id-for-desktop` —— 本插件是**纯增量**(patch 只有一条 `insert`,不停用任何宿主条目),移除后不留残迹。

---

## 许可

MIT。`index.js` / `session-files.js` 源自 [duanyunlun/dsh-session-actions](https://github.com/duanyunlun/dsh-session-actions)(MIT,Copyright (c) duanyunlun),详见 [LICENSE](LICENSE)。

Install

dsh plugin --profile web add github:meowmeowloong/dsh-session-id-for-desktop

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source