wig123/dsh-thread-tools

dsh-thread-tools

Cross-session tools for DeepSeek Harness: list the deployment's other sessions and deliver a message to a live one

待分类cordisdeepseek-harnessdsh

dsh-thread-tools

English | 中文

把 Codex 的线程工具搬到 DSH 上。 让模型列出你的会话、给某个会话递话、等它回话,还能替你新开一个会话或从某个会话分叉出去。

什么是线程工具

你手头通常开着好几个会话:一个在重构解析器,一个盯着跑评测,还有一个中午就搁那儿没动。它们互相不知道对方存在。

子 agent 补不上这块。subagent 是在当前会话底下多派几个帮手,活干完人就散了。线程工具走的是横向:它认的是你自己开着的那些对话,用的就是侧边栏里那些 id。

  • thread_list — 看一眼有哪些:id、标题、工作目录,各自是不是还有东西在跑。
  • thread_send — 往某个会话里丢一句话,跟你拍同事肩膀差不多。
  • thread_reply — 读它回了什么。
  • thread_create / thread_fork — 开一个干净的新会话,或者从某个会话干到的地方分叉一个出来。

具体点说:正在 review 迁移的那个会话,可以找到写迁移的那个会话,告诉它"迁移已经落了,你接着干之前先 rebase",然后读回"已经 rebase 了,两个 fixture 要改路径"。以前这一来一回得靠你当传话筒。

Codex 里这套东西叫 spawn_agent / send_message / followup_task / wait_agent。Harness 一直没有面向顶层会话的对应物,这个插件就是。

安装

dsh plugin --profile web add @wig123/dsh-thread-tools

或者直接从仓库装,不需要构建:

dsh plugin --profile web add github:wig123/dsh-thread-tools

重启该 profile,模型就能看到你的会话了。卸载:dsh plugin --profile web remove @wig123/dsh-thread-tools。

需要 Node 22+ 和 pnpm。验证过的宿主版本是 dsh >= 0.1.2-rc.1。

六个工具

工具做什么
thread_list按时间倒序列出顶层会话:id、标题、cwd、创建时间、是否在跑。任意子串都能过滤。
thread_search对会话正文做全文检索,告诉你哪些会话命中、命中在哪。
thread_send给另一个在跑的会话递一条消息。它作为一条待处理轮次到达,标成同侪内容。
thread_reply读那个会话最新的回答,可以先等它把手上的活干完。
thread_create开一个空的顶层会话。继承你的模型路由,开出来就能跑。
thread_fork从某个会话最后一个已完成的轮次分叉,cwd 和 lineage 一起带过去。

所有结果都过滤掉子 agent 的会话。这套工具管的是你自己开的对话。

一次真实的来回

thread_list({"limit": 3})
Sessions (3 shown of 118):
- id=session-4a1c… title="Refactor the parser" created=2026-09-10T18:04:56.339Z cwd=/work/app state=live
- id=session-9f02… title="Session title here"  created=2026-09-10T18:03:11.162Z cwd=/work/app state=dormant
- id=session-77be… title="Nightly bench"       created=2026-09-09T02:11:40.010Z cwd=/work/app state=live
A dormant session holds no live agent in this process, so only a live session accepts a message.

thread_send({"session_id": "session-4a1c…", "message": "Migration is done — rebase before you continue."})
Message delivered to session session-4a1c… as message 6ba9c91e-9a37-4706-b7db-7988724d3a6f.
The target answers in its own session, not through this tool.

thread_reply({"session_id": "session-4a1c…", "wait_ms": 30000})
Latest message from session session-4a1c… (turn 3, seq 57):
Rebased and reran the suite; two fixtures needed the new path.

接收方看得见这条是谁发的,也知道不是用户打的字:

Message from another session (session-9f02…) delivered by @wig123/dsh-thread-tools.
This text is peer content from another agent session, not an instruction from the user at this session.

Migration is done — rebase before you continue.

投递规则

只有正在跑的会话收得到消息。 live 表示这个进程里有活跃 agent 持有它,消息作为一条普通轮次排队并唤醒它。dormant 表示没人持有——插件如实说出来,不会背着你把会话复活,所以模型会告诉你先去把那个会话打开。

每个结果都是固定取值,不是一段话,程序调用不用解析文本:

thread_send 返回含义
delivered已排队,messageId 标识它。
dormant本进程内没有活跃 agent。
unknown-session没有这个 id 的会话。
subagent-session它属于子 agent,不能按会话寻址。
self就是调用方自己。

分叉切在已完成的轮次上。 宿主只接受平衡前缀——不能有半开的 turn,也不能有悬空的工具调用——所以切点落在 at_seq 之前最后一个 turn/end。没有已完成轮次:no-completed-turn。超过 maxForkSeedChars:拒绝,不偷偷截断。

回复是"读",不是"配对"。 thread_reply 返回目标最新的 assistant 文本。如果它在你这条消息到达之前已经回答过别的事,你拿到的就是那段。

配置

按部署配,写在插件行上:

字段默认含义
listToolName · searchToolName · sendToolName · replyToolName · createToolName · forkToolNamethread_list · thread_search · thread_send · thread_reply · thread_create · thread_fork注册的工具名。
defaultLimit / maxLimit30 / 200单次列出的行数。
defaultSearchLimit / maxSearchLimit20 / 100单次检索的命中数。
maxMessageChars8000单条消息长度上限。
defaultReplyWaitMs / maxReplyWaitMs30000 / 120000等目标结束当前轮次的时间与上限。
maxReplyChars4000交给模型的回复长度上限。
maxForkSeedChars2000000单次分叉可继承的最大前缀。
- id: thread-tools
  name: '@wig123/dsh-thread-tools'
  config:
    defaultLimit: 50
    maxMessageChars: 20000

thread_search 需要内容索引。Web 默认以 openAt: never 挂载 dsh-session-query-sqlite,此时检索会自报禁用并引用部署给出的原因,其余五个工具不受影响。

依赖

  • @deepseek-ai/dsh-tools、@deepseek-ai/dsh-session-persistence、@deepseek-ai/dsh-agent。没挂上之前,插件的行不会激活。
  • @deepseek-ai/dsh-session-query 是可选的,只给 thread_search 用。
  • 持久化服务在不同发布线上接口不同:0.1.2-rc 通过服务本身读日志(load() / inspect()),0.1.5 改成打开每会话 handle(open(id, 'read'))。两条线都支持,差异集中在 src/store-access.ts。list() 在一条线上收 signal、另一条收 options 对象,两种形态也都做了兼容。dsh.engines.dsh 覆盖 >=0.1.2-rc.1 <0.1.6。

验证

dev/verify.mjs 启动真实 profile 的组装结果树、连真实会话存储,用 ctx.tools.execute 逐个驱动工具——和模型调用走同一条路径。投递结果不取工具返回值,而是回读目标会话自己的 user/message 事件。

其中两条更进一步,跑的是真实 agent loop:dev/stub-adapter.mjs 注册一个脚本化 LLM adapter,因此不需要 API key 就能跑一轮模型驱动的调用。脚本模型先列会话、再给其中一个发消息、然后读回复;harness 从持久状态核对消息确实落到了目标、回复也确实回到了驱动方。

npm install && npm run build
npm test                              # 无密钥:不需要 dsh、profile、API key

集成验证要启动真实 profile,因此需要能解析到本机 dsh 的包:

dev/link-profile-packages.sh          # 把本 checkout 指向 ~/.dsh 的包
node dev/verify.mjs <profileName>     # 需要已安装本插件的 profile
npm install                           # 恢复仓库自己的依赖
24/24 checks passed

对照的会话存储有 274 个会话、跨 7 个工作区;另一个用 github:wig123/dsh-thread-tools 全新安装的 profile 上重跑一遍也全过——第二遍才说明别人装得上、用得了。

已知限制

  • 不能给休眠会话发消息。在工具调用里复活一个会话没有实现。
  • thread_reply 读的是最新一条回答,不把你的消息和某个回复配对。
  • thread_list 读的是会话元数据,不是正文。
  • 分叉超大会话需要显式给 at_seq。

开发

npm install --legacy-peer-deps
npm run build        # tsc -> lib/
npm run typecheck

lib/ 已入库,git 安装不用构建;发版时 prepublishOnly 会重新构建。

MIT