# dsh-interconnect

> 在多个 DSH 实例间通过共享密钥 WebSocket 互通：互发文本消息、推送会话生命周期事件、枚举对端在线会话。

## Metadata

- Author: [@Chinesezjc](https://github.com/Chinesezjc)
- Repo: <https://github.com/Chinesezjc/dsh-interconnect.git>
- GitHub: [Chinesezjc/dsh-interconnect](https://github.com/Chinesezjc/dsh-interconnect)
- Stars: 34
- Language: TypeScript
- License: [MIT](https://spdx.org/licenses/MIT.html)
- Topics: `deepseek-harness`, `dsh`, `dsh-plugin`, `interconnect`
- Forks: 4
- Open Issues: 1
- Last push: 2026-08-19T08:14:02.000Z
- Added: 2026-08-14T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:Chinesezjc/dsh-interconnect
```

## Wiki

## 一句话定位
dsh-interconnect 是 DSH 上的跨实例互通插件。它让一台机器上的一个 DSH 实例能向同机器、另一台机器、或另一台机器上的别的 DSH 实例发送文本消息、互相推送会话生命周期事件，并枚举对端当前正在运行的会话，所有传输都走共享密钥鉴权的 WebSocket 持久链路。

## 核心能力
- 通过持久 WebSocket 链路在多个 DSH 实例之间投递文本消息，按 instanceId 查对应链接寻址
- 实时推送本机会话生命周期事件(创建/销毁/状态变更/subagent 结束)给所有配置的对端
- 提供四个模型可见工具：interconnect_send、interconnect_ping、interconnect_list、interconnect_reply
- 支持按消息唤醒对端已持久化但未运行的 session(resume 默认关闭，接收方可一票否决)
- 用 sender 身份记录让 reply 双向多轮链式回信不必再次寻址对端地址
- 用共享 token + timing-safe 校验的 Bearer 鉴权，未配置 token 时 fail-closed

## 技术实现
- **语言**: TypeScript (ES2022 / ESM)
- **关键依赖**: `@deepseek-ai/cordis` (Cordis 服务框架) / `@deepseek-ai/dsh-llm` (用户消息构造) / `@deepseek-ai/dsh-host-webserver` (WebSocket upgrade 路由) / `ws` (WebSocket 实现) / `@deepseek-ai/schemastery` (Config 校验)
- **架构模式**: 通过 `cordis.patch.yml` 把两个 host 插件注入 host composition；`interconnect` 是 `Service` 类，注入 `webServer / agents / credentials` 三个依赖；`tool-interconnect` 通过 `defineTool` 把四个工具注册到 `ctx.tools` 工具面
- **入口文件**: `src/index.ts` (根 re-export) / `src/interconnect/index.ts` (host 服务) / `src/tool-interconnect/index.ts` (模型工具)

## 适用场景
当你需要让两台或更多 DSH 实例协同工作时(例如让一台机的 agent 主动给另一台机的某个 session 派活、汇总不同实例上的 agent 输出，或在多个实例之间做事件同步)装上 dsh-interconnect，两端用共享密钥 WebSocket 通道互通，模型也能用工具主动寻址和投递；普通单机用户通常用不到这个插件。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DeepSeek Harness (DSH) | 未声明 | peer 依赖 `@deepseek-ai/dsh-{agent,api-remotes,credentials,llm,session,subagent,host-apiproxy,host-webserver,tools,invariants}` 与 `@deepseek-ai/cordis`，本仓库用 sibling checkout 链接到 dsh monorepo 源码；无显式 engines 版本约束 |
| Node.js | >=22.0.0 | esbuild 编译目标为 node22，devDep `@types/node ^24.0.0` |
| 凭据 DSH_INTERCONNECT_TOKEN | 必填 | 共享密钥，两端 credentials 必须设置相同的值；缺失时服务端 fail-closed 返回 403 |
| `ws` 包 | * (peer) | 由宿主 DSH profile 的 node_modules 提供；纯 JS，无原生依赖 |

## 安装方式
```bash
dsh plugin --profile web add github:Chinesezjc/dsh-interconnect
```

## 配置项
| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| `instanceId` | 字符串 | 本实例自报 id，出现在 ping/send/list 的回包里，也是对端如何寻址到本机的身份 | `'dsh'` |
| `requestTimeoutMs` | 整数 (≤60000) | 出站请求等待结果的超时阈值，超时即返回 `unreachable` | `10000` |
| `peers` | `{ [instanceId]: origin }` | 对端路由映射，值是本机拨向对端的 origin（如 `http://127.0.0.1:13080`），激活时自动为每个对端建立持久 WebSocket 链 | `{}` |
| `delivery` | `followup` / `steer` / `inject` | 入站消息未带 `delivery` 时的默认投递模式 | `'followup'` |
| `allowResume` | 布尔 | 是否允许发送方用 `resume` 唤醒本机的离线 session；接收方用此一票否决 | `true` |

> 鉴权用的共享密钥不在此配置，而是取自 DSH credentials 的 `DSH_INTERCONNECT_TOKEN`；运行时也可通过 `ctx.interconnect.subscribe(instanceId, origin)` / `ctx.interconnect.unsubscribe(instanceId)` 动态增删对端。

## 常见问题

**Q: 普通用户能用这个插件做什么?**

A: 主要给"想跨机器协同多个 DSH 实例"的用户用。例如让一台机的 agent 主动给另一台机的某个 session 派活、汇总不同实例上的运行结果，或在两个实例之间同步会话生命周期事件。需要两端都装这个插件并配置同一个共享密钥；单机用户用不到。

**Q: 装好之后还要做什么才能互通?**

A: 在两端的 DSH 凭据存储里配置同一个 `DSH_INTERCONNECT_TOKEN`（缺失时服务端会 fail-closed 返回 403），然后在本端 `config.peers` 写入对端 `instanceId` 与可达 `origin`。激活时插件会为每个对端自动建持久 WebSocket 链，无需手动调 link。

**Q: 发送消息时为什么有时不成功?reason 字段是干嘛的?**

A: 因为不同失败需要不同应对，插件用 `reason` 区分：`session-not-live` 是对端 session 没在跑（换目标或带 `resume`）；`unreachable` 是传输或鉴权失败（可重试）；`resume-refused` / `resume-failed` 与唤醒相关；`session-owned-by-subagent` 表示该 session 归 subagent 路由（要从父 agent 走）；`no-sender-known` 是 reply 找不到发送方记录。

**Q: resume 选项安全吗?为什么默认关闭?**

A: 唤醒会触发对端一次完整的 agent 回合（`wakeDriver` → `kick` → `turn` → `llm.stream`），即一次计费的模型调用，且会带着该 session 的完整工具集。所以默认关闭：发送方要显式带 `resume`，接收方还能用 `allowResume: false` 一票否决，避免在没人盯着的会话里被远程触发计费调用。

**Q: delivery 三个模式有什么区别?**

A: `followup` 把消息排队成独立一轮，等接收方当前那轮结束；`steer` 在运行轮里最近的 step 边界切进去，紧急消息等不及整轮；`inject` 只把消息写入上下文但不唤醒 idle 的 agent，可能一直读不到。发送方可按消息覆盖接收方配置默认。

**Q: 为什么有些 session 在 `interconnect_list` 里看不到?**

A: 列表只包含当前有正在运行 agent 的 live session（这是 `send` 能到达的集合）；subagent 拥有的 session 也会被排除——这类 session 的投递权归它的父 agent，从这里直接投会和父 agent 抢。判据直接复用 Host 的 `hasApiRemoteSubagentOwner`，本插件不重复实现。

**Q: 升级到 0.9 后旧调用方式还能用吗?**

A: 不能。0.9 是破坏性大版本：删除全部 HTTP 端点，`send`/`reply`/`ping`/`list` 全走 `/interconnect/link` 持久链；寻址从 `baseUrl` 改为 `instanceId`；`peers` 改为 `{ instanceId: origin }` 映射；`sender` 去掉 `baseUrl`，变为无地址身份；到未配置或未联通的对端直接返回 `unreachable`，没有 HTTP 回退。

## 上手难度
进阶 — 单实例安装一行命令即可，但要真正跨实例互通还要在两端配共享密钥 + 写对端 `peers` 映射，并理解 WebSocket 持久链路、`resume` 唤醒的副作用、delivery 三种模式以及对端版本与部署形态（subagent owner、headless 无 api-proxy）对行为的影响。

## 已知问题与限制
- 0.9.0 是破坏性大版本：HTTP 端点全删，寻址改用 `instanceId`，`peers` 改为映射，旧调用方式全部失效（CHANGELOG.md:11-21）
- 对端运行 0.9 之前版本时不带 `sender`，本端对该 session 调用 `interconnect_reply` 会返回 `no-sender-known`（src/interconnect/index.ts:284-288）
- 磁盘格式过旧的 session 无法唤醒（返回 `resume-failed`），这是 Host 上游 `resume()` 路径的限制，本插件不绕过（README.md:166-168）
- 没有 Host `agent` lookup 的部署（headless / 无 api-proxy profile）调用 `resume` 时会降级为 `session-not-live`，不报错（src/interconnect/index.ts:556-563）
- subagent 拥有的 session 不可直接 `send`，会返回 `session-owned-by-subagent`；判据直接复用 Host 的 `hasApiRemoteSubagentOwner`，不重复实现（src/interconnect/index.ts:599-601）
- 共享密钥必须两端完全一致；未配置时 `interconnect` 服务端 fail-closed 返回 403，没有任何自动协商或回退（src/interconnect/index.ts:692-695）
- 入站事件的 `ctx.on` 监听器抛错会被吞掉并记 warn，避免一处 listener 异常让 socket 的 message 回调挂掉；本地 listener 异常可能不直接显现（src/interconnect/index.ts:752-758）
- 出站 `send`/`reply` 等待结果受 `requestTimeoutMs`（默认 10s，上限 60s）约束，超时直接返回 `unreachable`，对端是否实际收到无法保证（src/interconnect/index.ts:365-371）

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-interconnect](https://deepseek-plugin.org/plugins/Chinesezjc/dsh-interconnect)
Wiki generated by AI (model: `MiniMax-M3`)
