中文EN

dsh-session-dm

sessionnpmGPL-3.0

跨会话投递:往同工作区的另一个会话发消息,并可新建顶层会话。

下载量 / Downloads
474

安装

dsh plugin --profile web add dsh-session-dm

沙箱测试

这个插件还没测过。提交测试 →

README

dsh-session-dm

给 DSH(DeepSeek Harness) 的智能体加上跨会话能力:往另一个会话发消息、直接开一个新的顶层对话,以及等它跑完再继续,全程在同一个工作区里。

DSH 自带的会话能力是只读的——session_search / session_event_read 能看别的会话,send_message 只能到自己的子智能体树。缺的正是顶层会话之间的写方向,这个插件补的就是它。

工具 · 作用

session_dm_send · 往同一工作区的另一个会话投递一条文本。mode: "queue"(默认)排队等下一回合;mode: "steer" 插队——目标正在跑回合时直接插进当前回合,模型下一步就读到。

session_create · 在调用方工作区开一个新的顶层会话(真·新对话,不是子智能体),并带上首轮提示词。可指定标题。

session_dm_wait · 阻塞到另一个会话跑完当前回合,返回结束原因或失败信息。发完活直接等它,不用 sleep 猜。

排队 vs 插队(对齐宿主输入框自己的 beginSubmission 逻辑——只有目标正在跑回合时两者才有区别):

目标状态 · mode: "queue" · mode: "steer"

正在跑回合 · 排进队列,当前回合结束后模型才读到 · 插入当前回合,模型下一步就读到

空闲 · 立即开新回合 · 回落为排队(立即开新回合),回执说明发生了回落

冷会话 · 唤醒后开新回合 · 同左

前两个返回的是「已受理」回执,不是对方的回答。典型用法是三步走:session_dm_send 发活 → session_dm_wait 等它跑完 → session_event_read 读结果。

![npm](https://www.npmjs.com/package/dsh-session-dm)

![license](./LICENSE)

![release](https://github.com/Venshell/dsh-session-dm/releases)

会话 A                                     会话 B
   │ session_dm_send({ session_id, message })
   ▼
sessionController.resolveAgent(B)   ← 冷会话在这里被唤醒
   ├─ agent.followup(消息)          ← 进持久化 next-turn 收件箱
   └─ sessions.flush(session)       ← 等持久化确认才回执
   ▼
会话 B 把这条当普通用户输入处理
   ▲
   │ session_dm_wait({ session_id })  ← 阻塞到 turn/end,不是 sleep 猜
   │
会话 A 拿到「跑完了」→ session_event_read 读产出

安装

方式一:从 npm 装(最简单)

dsh plugin --profile <你的 profile> add dsh-session-dm

方式二:从本仓库装

dsh plugin --profile <你的 profile> add github:Venshell/dsh-session-dm

方式三:从 Release 附件装

去 Releases 下 dsh-session-dm-0.4.1.tgz:

dsh plugin --profile <你的 profile> add <下载路径>\dsh-session-dm-0.4.1.tgz

方式四:从源码目录装

git clone https://github.com/Venshell/dsh-session-dm
dsh plugin --profile <你的 profile> add <克隆路径>\dsh-session-dm
⚠️ 装完必须重启 Harness。宿主在启动时加载插件模块,新装或改过的代码只对重启后的会话生效。
⚠️ 某些 dsh plugin add 版本会把 profile 的 bundle 列表重写成只剩新装的那个。装完扫一眼 dsh.profile.bundles 是否还完整。

bundle 会自动启用哪些组件

DSH 的 profile 是分层叠加的:出厂默认层 → 各 bundle 的 patch 层(按 dsh.profile.bundles 顺序)→ 你自己 profile 的 cordis.patch.yml → --patch 覆盖层。本插件的 patch 利用这个机制,装完即把跨会话需要的组件全部打开,包括那些出厂被关掉或压根没有的:

组件 · 出厂状态 · 本 bundle 做什么

dsh-session-dm · 不存在 · insert 到 host plane:所有 preset 的会话都看得到三个工具

@deepseek-ai/dsh-tool-session-query · 不存在——可选包,任何默认层都没有 · insert(常规 id tool-session-query):挂上五个读回工具 session_search / session_event_search / session_trace / session_event_trace / session_event_read,否则模型只能发、读不回来

@deepseek-ai/dsh-session-query-sqlite · 每层都有,但搜索是关的(openAt: never:服务在,只支持精确读;内容搜索会答 SESSION_QUERY_SEARCH_DISABLED) · id 定向覆盖(幂等——这行到处都在):openAt: first-search + 持久化路径 <home>/cache/session-search.db,首次搜索才打开 SQLite

两点要说清:

  • 打开搜索是 profile 级的副作用。 覆盖 openAt 之后,SQLite FTS 内容搜索整个 profile 都开了,不只是本插件——侧边栏搜索也会跟着变。这是官方文档给的部署级开关;不想要就从 bundle 的 cordis.patch.yml 里删掉 session-query-sqlite 那一条。没有它两个工具照样能投递(回执读标题,openAt: never 下可用),只是模型搜不了历史。
  • loader 不会给 insert 去重。 如果你的 profile 层里已经自己插了 tool-session-query 行(在本 bundle 出现之前手改过 profile 的人常见),删掉其中一个——挂两次会重复注册五个工具。profile 层对自己 id 的 override 不会和 bundle 冲突:后层胜出,所以你改的 openAt / path 优先。

session_dm_send / session_create 另外消费的宿主服务(sessionController、sessions、systemPrompt、tools,以及回执标题用的 sessionQuery)任何部署都自带,无需启用。


工作原理

插件不起后台进程、不存状态、不加 schema,全部走宿主自己的会话机制:

消息投递用的是内建 schedule 服务给「原会话提醒」投递时的同一套:

ctx.sessionController.resolveAgent(target)   // 活的 Agent,或冷唤醒
  -> agent.followup(userMessage)              // 持久化 next-turn 收件箱
  -> ctx.sessions.flush(agent.session)        // 等持久化确认

会话创建驱动的是 Web UI「新对话」按钮背后的同一个宿主 API(sessionController.create)。请求优先用 workspaceId 形式,好让新会话落进调用方所在的 UI 工作区分组而不是「未分组」;匹配不到工作区时退回裸 { cwd }。两者严格互斥——宿主会拒掉同时给 workspaceId 和 cwd 的请求。

授权边界:只有当目标会话的 cwd 与调用方完全一致时才能发消息(沿用 session-query 的保守规则)。没有工作区的会话既不能发消息也不能建会话。session_create 永远在调用方自己的工作区里创建——不接受目标参数。

可追溯:每条投递的用户消息都带 source: { kind: "session-dm", fromSessionId },从目标会话的事件日志里能反查是谁发的。

模块零依赖:除 node:crypto 外不 import 任何东西,所有宿主能力都通过注入的 Cordis 上下文拿到(tools、systemPrompt、sessionQuery、sessionController、sessions)。


踩过的坑(照抄前必读)

这几条都是真金白银的调试时间换来的。

1. 裸注册工具必须写完整 JSON Schema

本插件为了零依赖,刻意绕开了宿主的 defineTool 辅助函数。那个函数平时负责把简写参数({ field: { type, required: true } })转成 JSON Schema。裸注册时没人做这层转换,简写会被原样发布:模型侧看到每个参数类型都是 object,宿主按这份错 schema 校验时把字符串实参全剥掉,execute 永远收到 {}。

所以只要绕开 defineTool,parameters 就必须是完整 JSON Schema:type: 'object' + properties + required + additionalProperties: false。

2. sessionController 有两张脸

注入的 SessionController 服务暴露的是 create({ cwd }) -> { sessionId } 和 rename({ sessionId, title })。它内部的 this.agents(ApiSessionAgentController)暴露的是 createSession({ cwd }) -> { agent }。名字像、形状不一样。调用前先把注入服务自己的方法表从宿主 bundle 里挖出来看。

3. 工作区归属要 workspaceId,且与 cwd 互斥

只用 { cwd } 创建能成功,但会跳过 workspace.attachSession,于是会话在 UI 里显示成未分组。同时传 workspaceId 和 cwd 是硬错误("accepts workspaceId or cwd, not both")。

4. ESM 缓存不会因为改文件而失效

profile 里装的这个插件是指向仓库目录的符号链接,所以源码一改磁盘上立刻生效——但运行中的宿主还握着旧模块。每次改动都要重启 Harness。

5. loader 的 patch 语义(写 patch 前必须知道)

从宿主 bundle 里扒出来的 applyEntryPatches:

  • insert 无条件追加,不去重;只是把新行加进索引,让同一个 patch 列表里靠后的 patch 能定位到它。
  • id 定向 patch(有 id 无 insert)替换指定字段,config 是整体替换不做深合并;目标行不存在就警告 + 跳过。
  • profile 层叠在 bundle 层之后,所以后层胜出。

6. 等会话结束必须盯 turn/end,不能轮询 status

宿主的开发者文档把话说死了:

Wait on durable events such as turn/end … Do not poll agent/status. whenIdle() does not mean one follow-up finished: several inputs can share one running interval.

session_dm_wait 因此以 `

…(truncated)

以上内容来自 npm 上的 README,我们只做格式化,不改内容。

版本

npm 上共发布:3,最后更新 2026-11-06

链接

GitHub · Homepage · npm

同类插件