在多台设备间加密同步会话与任务,可远程接管或迁移
29.6/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/11/2 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add zengpu987-ctrl/dsh-deep-connect该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
是什么
dsh 原生插件 · market
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 6 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 52 处,展示前 52 条)
- 写入本地文件paper/measure.mjs:23
mkdirSync(DATA, { recursive: true }); - 删除本地文件递归paper/measure.mjs:260
rmSync(dir, { recursive: true, force: true }); - 读取本地文件paper/measure.mjs:360
for (const name of readdirSync(dir)) { - 读取本地文件paper/measure.mjs:362
const st = statSync(full); - 读取本地文件paper/measure.mjs:367
files.push({ path: full.slice(ROOT.length + 1), bytes: st.size, lines: readFileSync(full, 'utf8').split('\n').length }); - 写入本地文件paper/measure.mjs:407
writeFileSync(join(DATA, 'metrics.json'), `${JSON.stringify(METRICS, null, 2)}\n`); - 写入本地文件paper/measure.mjs:419
writeFileSync(join(DATA, 'metrics.md'), `${md.join('\n')}\n`); - 写入本地文件paper/render.mjs:6
mkdirSync(`${ROOT}/build`, { recursive: true }); - 发起网络请求plugin/client.js:801
response = await fetch(API_URL, { - 写入剪贴板plugin/client.js:1435
await navigator.clipboard.writeText(text); - 写入本地文件plugin/host/net.js:119
const out = createWriteStream(destination); - 删除本地文件plugin/host/net.js:135
await rm(destination, { force: true }).catch(() => undefined); - 发起网络请求plugin/host/relay.js:543
const response = await fetch(new URL('/v1/send', this.relayUrl).toString(), { - 发起网络请求plugin/host/relay.js:569
const response = await fetch(new URL('/v1/recv', this.relayUrl).toString(), { - 读取本地文件plugin/host/station.js:111
await access(candidate); - 发起网络请求plugin/host/station.js:125
const response = await fetch(`${url}/healthz`); - 读取本地文件plugin/host/station.js:152
record = JSON.parse(await readFile(path, 'utf8')); - 发起网络请求plugin/host/station.js:159
const response = await fetch(`${url}/healthz`, { signal: AbortSignal.timeout(3_000) }); - 删除本地文件plugin/host/station.js:166
await rm(path, { force: true }).catch(() => undefined); - 执行外部命令来自配置/变量plugin/host/station.js:214
const child = spawn(binary, ['tunnel', '--url', targetUrl, '--no-autoupdate'], { stdio: ['ignore', 'pipe', 'pipe'] }); - 执行外部命令来自配置/变量plugin/host/station.js:295
this.relay = spawn(process.execPath, [ - 发起网络请求plugin/host/station.js:338
const response = await fetch(`http://127.0.0.1:${this.port}/healthz`, { signal: AbortSignal.timeout(3_000) }); - 执行外部命令来自配置/变量plugin/host/station.js:421
const child = spawn(binary, ['tunnel', '--url', `http://127.0.0.1:${port}`, '--no-autoupdate'], { - 写入本地文件plugin/host/station.js:464
await mkdir(join(this.root, 'station'), { recursive: true }); - 写入本地文件plugin/host/station.js:465
await writeFile(path, JSON.stringify({ - 删除本地文件plugin/host/station.js:495
await rm(join(this.root, 'station', STATION_FILE), { force: true }).catch(() => undefined); - 读取本地文件plugin/host/store.js:37
const text = await readFile(path, 'utf8'); - 写入本地文件plugin/host/store.js:47
await mkdir(dirname(path), { recursive: true }); - 写入本地文件plugin/host/store.js:49
await writeFile(tmp, JSON.stringify(value, null, 2), { mode: 0o600 }); - 写入本地文件plugin/host/store.js:50
await rename(tmp, path); - 写入本地文件plugin/host/store.js:99
await mkdir(this.root, { recursive: true, mode: 0o700 }); - 读取本地文件plugin/host/store.js:259
await access(this.#historyPath(deviceId, sessionId)); - 删除本地文件递归plugin/host/store.js:269
await rm(join(this.remoteDir, encodeURIComponent(deviceId)), { recursive: true, force: true }); - 删除本地文件plugin/host/store.js:271
await rm(join(this.indexDir, `${encodeURIComponent(deviceId)}.json`), { force: true }); - 请求外部主机api.github.complugin/index.js:987
const response = await fetch(`https://api.github.com/repos/${UPDATE_REPO}/tags?per_page=50`, { - 发起网络请求plugin/index.js:987
const response = await fetch(`https://api.github.com/repos/${UPDATE_REPO}/tags?per_page=50`, { - 删除本地文件递归plugin/index.js:1050
await rm(work, { recursive: true, force: true }); - 写入本地文件plugin/index.js:1051
await mkdir(work, { recursive: true }); - 删除本地文件plugin/index.js:1066
await rm(archive, { force: true }).catch(() => undefined); - 读取本地文件plugin/index.js:1069
if (!(await stat(archive).then(() => true).catch(() => false))) { - 写入本地文件plugin/index.js:1073
await writeFile(archive, bytes); - 执行外部命令固定命令plugin/index.js:1079
const child = spawn('tar', ['-xzf', archive, '-C', work], { stdio: 'ignore' }); - 读取本地文件plugin/index.js:1083
const entries = await readdir(work, { withFileTypes: true }); - 删除本地文件递归plugin/index.js:1091
await rm(work, { recursive: true, force: true }).catch(() => undefined); - 发起网络请求relay/cloudflare/worker.js:44
async fetch(request, env) { - 发起网络请求relay/cloudflare/worker.js:56
return env.RELAY_OBJECT.get(id).fetch(request); - 发起网络请求relay/cloudflare/worker.js:211
async fetch(request) { - 写入本地文件relay/relay.cjs:593
fs.mkdirSync(path.dirname(statePath), { recursive: true }); - 写入本地文件relay/relay.cjs:594
fs.writeFileSync(tmp, JSON.stringify(dump)); - 写入本地文件relay/relay.cjs:595
fs.renameSync(tmp, statePath); - 读取本地文件relay/relay.cjs:599
if (statePath === '' || !fs.existsSync(statePath)) return; - 读取本地文件relay/relay.cjs:601
const dump = JSON.parse(fs.readFileSync(statePath, 'utf8'));
扫描于 2026/11/3(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库
涉及外部主机:api.github.com
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
README
由 DeepSeek 最新模型翻译生成Deep Connect Pro
多台 DSH 通过一键生成的中继站互相看得见(v3 Pro 起不再依赖账号或手填地址):
- 会话索引互见 —— 每台机器把本地会话的索引(标题、时间、消息数、最近几句提问、任务计数)加密广播给同账号的其它设备。
- 完整对话可读 —— 对端一出现,索引里的每条会话正文都会被自动拉下来(v2),点开即读、离线可看。
- 任务状态一致 —— todo / goal / 团队任务本身就是会话日志里的事件,跟着会话一起同步,索引行上直接显示「待办 N / 完成 N / 目标 / 团队 N」。
- 可接管 —— 在 B 机给 A 机开着的会话发消息。真正干活的是 A 机(模型、工具、文件都在 A 上), B 机看着结果。一条会话永远只有一台机器在写,所以只追加的事件日志不会分叉。
- 可继承 —— 也可以把 A 的会话连同完整事件日志搬成 B 上的一条新会话,用 B 的模型和工具接着干;A 上那条原样保留。
- 中继只见密文 —— 密钥由两台机器各自从同一个账号本地推导,从不上传。
展开完整文档(README 剩余 1 节)
Deep Connect Pro
多台 DSH 通过一键生成的中继站互相看得见(v3 Pro 起不再依赖账号或手填地址):
- 会话索引互见 —— 每台机器把本地会话的索引(标题、时间、消息数、最近几句提问、任务计数)加密广播给同账号的其它设备。
- 完整对话可读 —— 对端一出现,索引里的每条会话正文都会被自动拉下来(v2),点开即读、离线可看。
- 任务状态一致 —— todo / goal / 团队任务本身就是会话日志里的事件,跟着会话一起同步,索引行上直接显示「待办 N / 完成 N / 目标 / 团队 N」。
- 可接管 —— 在 B 机给 A 机开着的会话发消息。真正干活的是 A 机(模型、工具、文件都在 A 上), B 机看着结果。一条会话永远只有一台机器在写,所以只追加的事件日志不会分叉。
- 可继承 —— 也可以把 A 的会话连同完整事件日志搬成 B 上的一条新会话,用 B 的模型和工具接着干;A 上那条原样保留。
- 中继只见密文 —— 密钥由两台机器各自从同一个账号本地推导,从不上传。
A 机 (DSH) B 机 (DSH) ┌──────────────┐ ┌──────────────┐ │ deep-connect │◀── 同源 /api ── 浏览器 │ deep-connect │◀── 同源 /api ── 浏览器 │ Host 半 │ │ Host 半 │ └──────┬───────┘ └──────┬───────┘ │ WSS(密文) │ WSS(密文) └──────────────► dsh-connect-relay ◄────────────┘ 云端,零依赖单文件只见 channel 哈希与不透明密文
浏览器永远只连本机:面板读写本地 Host 的同源路由,本地 Host 再走中继去另一台机器。
所以浏览器侧不需要新认证、不跨源、不接触 channel。
快速开始
1. 在每台机器上装插件(一行命令)
已经装好本仓库的机器上不需要这一步;新机器直接运行。
首选(jsDelivr CDN,国内实测可达):
curl -fsSL https://cdn.jsdelivr.net/gh/zengpu987-ctrl/dsh-deep-connect@v4.3.0/install.sh | bash
备选(任选其一,都是同一个脚本):
同上,但不带版本号(会取 main 上的最新安装器)
curl -fsSL https://cdn.jsdelivr.net/gh/zengpu987-ctrl/dsh-deep-connect@main/install.sh | bash
GitHub 镜像
curl -fsSL https://gh-proxy.com/https://raw.githubusercontent.com/zengpu987-ctrl/dsh-deep-connect/main/install.sh | bash
GitHub 直连(国内时通时断,带重试)
curl -fsSL --retry 5 --retry-all-errors https://raw.githubusercontent.com/zengpu987-ctrl/dsh-deep-connect/main/install.sh | bash
安装器只要求 curl + tar +(node 或 python3 之一,用来改 profile 的 JSON)。
macOS 自带 python3,因此没装 Node 的机器也能直接跑。
脚本内部的仓库下载也按实测顺序做了多路回退(codeload → ghproxy.net → gh-proxy.com → github.com)。
脚本会:优先从镜像下载仓库到 ~/.dsh/plugins/dsh-deep-connect → 链接进 DSH 的 desktop profile → 写依赖、bundle 列表和 Loader 配置行。
改任何文件前都会备份,重复运行是幂等的。装完完全退出并重开 Harness。
每个 Release 还附带一个预编译包 dsh-deep-connect.tgz(由 .github/workflows/release-tarball.yml 打包,文件名不带版本号)。插件市场用它做免构建安装:
dsh plugin --profile web add "https://github.com/zengpu987-ctrl/dsh-deep-connect/releases/latest/download/dsh-deep-connect.tgz"
新版本发 Release 时该资产会被同名覆盖,所以这条链接永远指向最新版,市场条目不需要跟着改。
想连中继地址一起写进去(这样面板里不用再填):
注意:变量要放在 bash 前面(管道左侧只作用于 curl),或者先 export。
curl -fsSL https://cdn.jsdelivr.net/gh/zengpu987-ctrl/dsh-deep-connect@v4.3.0/install.sh \
| RELAY_URL=http://172.20.10.12:8790 bash
等价写法:export RELAY_URL=http://172.20.10.12:8790; curl ... | bash。
非默认 profile(如 web)加 PROFILE=web;DSH 装在别处加 DSH_HOME=/path/to/.dsh。
2. 一键生成中继站(v3 Pro)
装好并重启 Harness 后,左侧 Deep Connect Pro 面板里只有两件事:
1. 在任意一台设备上点 「一键生成中继站」。本机会起一个中继进程;机器上有 cloudflared 时会顺手开一条 Cloudflare 快速隧道,于是得到一个 公网可达的邀请链接;没有 cloudflared 就退化成局域网链接(面板会明说)。
2. 在另一台设备的「加入别人的中继站」里粘贴这条链接,点加入。两台设备就在同一个中继站里了。
链接形如 https://xxxx.trycloudflare.com#<邀请令牌>。令牌在 URL 的 # 后面——浏览器和 WebSocket 都不会把它发给服务器,中继只看到频道哈希和密文。
链接就是邀请函:谁拿到链接谁就能加入,请通过可信渠道发送。
想自己托管一个常开中继(VPS / 局域网服务器)仍然可以,见 relay/DEPLOY.md;在面板里改填那条地址即可,协议与中继站完全一样。
v4.3.0:星球主题
Deep Connect 面板换成深空蓝紫的星球主题:
- 星空底:两层视差星点(径向渐变,不依赖图片/画布),缓慢闪烁。
- 星云与行星:右上大行星带环、左下小行星、右下迷你行星,各自按不同周期漂浮;所有颜色仍取自 --dsw- 主题 token,浅色/深色跟随应用。
- 不挡操作:整层 pointer-events:none、aria-hidden="true",设备栏、会话、按钮完全可点。
- 减弱动态:prefers-reduced-motion 下所有漂浮与闪烁停止。
配套的 DSH 开机动画是独立插件,见工作区 dsh-cosmos-launch(安装到配置档后生效):启动时星点从中心爆开、星云成形、行星落位,约 4.2 秒后自动进入界面;右下角可「跳过」,设置里可关闭或立即重播,减弱动态时缩短为不到 1 秒。
v4.2.0:互联更稳、加入后不再掉出、界面更灵动
稳定性(这轮主要工作):
- 中继站看护(watchdog):主持人每 20 秒检查本机中继;进程崩了/被杀了,就在同一端口、用同一个邀请令牌原地重启。
隧道指向的端口没变,所以邀请链接对其它设备仍然有效,不需要重新加入。日志里会看到「同一端口重启(邀请链接不变)」。
- 加入方永不自动退出:成员关系只由「退出中继站」清除。链路断了会一直重试;如果断开超过 90 秒,Host 会主动 nudge() 一次,跳过 60 秒的指数退避,立刻重连。
- 成员持久化:加入方的地址/令牌/角色写进配置,插件更新、DSH 重启自动重连;主持人重启时先尝试接续 detach 进程,接不上就自动重建(v4.0.4 起)。
- 顺带修了 e2e 里手机下发命令的偶发失败:测试在切换记忆共享后会等 A 重新连上再下发,不再靠运气;这也验证了「切换设置引起的短暂断线会自动恢复」。
界面(灵动版):
- 流动色场:两层模糊光斑在背景里缓慢漂移(20s 往返),像极光一样铺在磨砂玻璃下面。
- 入场节奏:会话行、设备项、任务卡、阅读器气泡逐条上浮淡入(35ms 递进)。
- 呼吸在线灯:已连接的绿点带柔和的呼吸光环。
- 弹性控件:页签、开关用回弹曲线;按钮按下缩到 97%;设备项悬停右移 2px。
- 输入聚焦光晕:编辑器聚焦时边框与光晕一起亮起。
- 全部动画在 prefers-reduced-motion 下关闭;颜色仍只来自主题 token。
测试:relay 40、station 20(新增看护重启:杀进程后同端口同令牌恢复)、e2e 116、client 112。
v4.1.0:磨砂玻璃界面 + 会话搜索 + 全功能核查
界面重做(全部走主题 token,无硬编码颜色):
- 磨砂玻璃材质:设备栏、会话行、任务卡、阅读器气泡、设置卡、输入框、命令卡统一 backdrop-filter: blur() saturate() 叠在分层渐变底色上,带内高光与柔和投影;不支持模糊的环境自动退回不透明底色。
- 圆润圆角:设备栏 24px、卡片 20px、输入框/按钮/标签 14–999px;按钮改胶囊形并加了抬起动效;滚动条改成细圆角。
- 层次与动效:顶部栏半透明悬浮、设备项选中用品牌色渐变、对话/轨迹页签是胶囊分段控件; prefers-reduced-motion 下全部过渡关闭。
- 会话搜索:主区新增搜索框,按标题、提示词、任务正文、会话 id 过滤(本地即时,不发网络请求);设备栏每个设备多了「待办数量」小徽章,一眼看出哪台还有活。
- 配色自适应:浅色/深色都沿用 DSH 的 --dsw- 变量,切换主题不用改插件。
功能核查(本版全绿):relay 40、station 17、e2e 116、client 112。上一轮修好的投递、插话、发布命令(v4.0.5)与 v4.0.4 的实时/调用行/自动重建都在回归覆盖内。
v4.0.5:跨机指令真的能落地——接力在本机跑、插话被服从、命令带工作区与模型强度
v4.0.4 及以前,从另一台机器发过来的任何输入都进不了对端。根因是一行签名:
async prompt(request, signal) { signal.throwIfAborted(); /* … */ }
Host 的 Remote 方法由传输层补第二个参数 AbortSignal,而插件是进程内直调:
sessionController.prompt(request) 少传了 signal,于是在 throwIfAborted 处必抛
TypeError: Cannot read properties of undefined (reading 'throwIfAborted'),被 servePrompt / serveTask 的 catch 吞成 accepted:false 回给发送方。
插话、发布命令、手机下发共用这条路径,因此全部失效——而 e2e 一直全绿,是因为测试桩的 prompt 不看第二个参数(4.0.5 起桩会抛同样的 TypeError,回归跑得出来)。
同版把三件事按使用方的语义补齐:
- 接力到本机 = 在本机把任务跑一遍。 继承完事件日志后立刻向新本机会话投递一条「接力到本机,请把它完整跑一遍」的指令(附原任务正文),由本机的模型和工具真正执行;阅读器里的「继承到本机」仍是纯复制。回执带 ran / task / runError。
- 插话必须被听到。 仍走 steer,但对端回执会带回实际生效的投递方式:只有真的进了运行中的那一轮,才显示「已插话」;否则显示「已排队」,不再把排队冒充成插话。
- 发布命令带工作区与模型强度。 对端的工作区列表与默认目录随索引一起广播;命令卡多了「对端工作区 / 模型强度 / 思考强度」三个下拉(默认取本机当前模型)。对端用 create({ workspaceId }) 落进指定工作区,再 selectModel(…) 装上本机选的模型强度,最后才投递任务;模型不可路由会明确失败,不静默降级。
兼容性:线协议仍是 v3,新增字段是附加的。4.0.4 的对端会忽略 workspaces / model / workspaceId,命令仍按它自己的默认目录与默认模型执行;但 4.0.4 作为接收方依旧拒收一切输入(根因在接收方),所以两端都要升到 4.0.5。对端偏旧时,失败信息会直接点明「对端插件低于 4.0.5」。
验证:relay-smoke 40 项、client-check 109 项、deep-connect-e2e 116 项,全过。
v4.0.4:五项承诺的核实与补齐
逐条对照 4.0.x 的实现与测试,这一版补了三处真实缺口:
| 承诺 | 现状 | 证据 |
| 点检查更新直接装 | ✅ | 面板按钮 → update-install;下载 codeload(4.6 MB 实测)→ tar 解包 → 覆盖 plugin/;面板提示重启。客户端测试断言按钮发的是 update-install 而不是命令文本。 |
| 对话/轨迹分开 | ✅ | 阅读器两页签;tool/call 现在单独成行并标注「调用」(此前会落进普通 turn 的兜底标签)。 |
| 实时看进程 + 插话 | ✅ | 远程阅读器每 2.5 秒强制拉一次新副本(fresh:true 跳过缓存比较,此前受索引 15 秒节流影响并不实时);插话走 steer,测试覆盖。 |
| 微信扫码管理 | ✅ | 令牌手机页(设备/任务/下发命令)+可选隧道+设置里的二维码;e2e 覆盖手机页 200、错令牌 404、手机下发命令在对方执行;QR src 断言覆盖。 |
| 默认留在中继站 | ✅(有边界) | 加入方持久化并自动重连;托管方用 detach 进程 + station.json 接续;本轮新增:接续失败时自动重建中继站,不再需要手点。边界:整机重启会杀掉 detach 进程,Cloudflare 快速隧道域名随之改变,托管方需要把新链接再发一次;要彻底免维护,需具名隧道或 VPS 中继。退出中继站 可永久退出。 |
v4.0.3:v4.0.2 以下版本「窗口封死」
两条路一起封:
- 面板封死。 运行的插件版本若低于 MIN_VERSION(4.0.2),客户端只渲染一屏「此版本已封停」+升级命令,设备栏、会话、设置全部不出现;Host 端也只放行 list / update-check / update-install,其它动作一律 badRequest。
- 中继站封死。 中继新增 --require-version <semver>:中继站默认以 --require-version 4.0.2 启动,握手和 HTTP 信箱都会检查 ver 参数(x-dsh-connect-version 头同样接受)。没有上报版本或低于下限的客户端直接吃 426 version-refused,邮件箱路径也拒绝。
客户端现在每次连接都会带 ver=<PLUGIN_VERSION>(WebSocket 查询参数与 HTTP 头各一份)。
版本号收敛到 plugin/host/version.js 一处:PLUGIN_VERSION 与 MIN_VERSION 同源,避免面板 / 中继 / 更新检查三处漂移。
手工用 relay/install.sh 起的中继不强制版本(那是自建部署的自由);只有面板生成的中继站会带上版本门槛。
v4.0.2:更新检查不再被单个 CDN 的缓存骗到
4.0.1 的链路是「第一个成功即采用」。实测发现 jsDelivr 的包版本列表会滞后几分钟:刚发 v4.0.1 后它仍返回 v4.0.0,先采用它就会让用户看不到新版本。
4.0.2 改成所有来源都问一遍、取最高版本:jsDelivr 包 API、GitHub tags、 raw.githubusercontent 的 plugin/package.json、curl、fetch 各自独立尝试,任何一个成功都计入候选,最后按 semver 取最大值;transport 字段记录本次哪些来源成功。
全部失败仍是 10 分钟后重试,成功后 6 小时再查。
实测(本机代理环境):transport=jsdelivr+github+raw+curl+fetch,latest=4.0.1。
v4.0.1:修复自动检查更新
原来的检查更新用 fetch 直连 GitHub,而 DSH 宿主进程常带 NODE_USE_ENV_PROXY=1: Node 会把 fetch 交给代理,代理到 api.github.com 的 TLS 经常被重置(ECONNRESET),于是「检查更新」一直失败;GitHub 匿名 API 每小时 60 次的限流也会让 403 长期挂着。
4.0.1 把检查改成多传输链(net.js):
1. jsDelivr 包 API(CDN,无匿名限流,列出同一批 tag)——首选;
2. node:https 直连 GitHub tags(绕过 fetch 的代理逻辑);
3. curl(机器确实需要代理时兜底);
4. fetch(最后)。
另外三处修正:
- 失败不再压 6 小时:检查失败后 10 分钟重试;成功才推到 6 小时后。
- 每 30 分钟巡检一次:ctx.interval 周期性调用,面板轮询自然看到新版本。
- 下载也走同一套:httpsDownload 直连 codeload,失败依次退 ghproxy / gh-proxy,最后 curl;全部失败才报错,并把每条路线的错误一起显示。
实测(本机,GitHub API 已被限流 403):检查经 jsDelivr 返回 latest=4.0.0,transport=jsdelivr; codeload 直连下载 4.6 MB 成功。
v4.0.0:一键自更新、对话/轨迹分栏、实时跟随与插话、微信扫码、默认留在中继站
- 检查更新直接装。 「检查并更新」不再显示一条 curl:Host 下载对应 tag 的仓库包,用 tar 解到临时目录,再把 plugin/(以及 relay/)覆盖到正在运行的位置;面板提示「已更新到 vX,重启 Harness 后生效」。失败会明说原因,不会静默。
- 对话与轨迹分栏。 阅读器顶部两个页签:对话(用户/助手的正文)与 轨迹(工具调用、工具结果、系统/开发者事件)。tool/call 现在单独成一条轨迹,带工具名和参数,不再只藏在助手正文里。
- 实时跟随 + 插话。 打开发往对端的会话时每 2.5 秒刷新一次,顶部显示「实时」;输入框的「插话」走 steer,直接进入对端正跑着的那一轮,「发送」则排队下一轮。
- 微信扫码手机管理。 设置里开启「手机管理」后,本机启动一个带令牌的手机页面(设备列表、任务清单、给设备下发命令),有 cloudflared 时额外开一条隧道并显示二维码;微信扫一扫即可打开。令牌在路径里,错令牌一律 404。
- 默认留在中继站,可退出。 加入过的中继站写入配置,重启/更新后自动重连;托管端的中继与隧道是 detach 进程并落盘 station.json,重启后自动接续(adoptStation)。
想离开点「退出中继站」,退出后下次启动不再自动加入。只有整机重启(断电/重启系统)会杀掉 detached 进程,那时托管端需要重新生成一次链接——这是 Cloudflare 快速隧道的固有限制,不是配置丢失。
协议未变(仍是 v3 线协议),4.0.0 与 3.x 可以滚动升级、混跑。
v3.0.2:任务面板、任务内容与接力、记忆共享开关
- 左侧设备栏。 面板左边是设备列表(本机 + 每台对端,带在线灯、会话数、记忆共享状态),点某台设备就只看它的任务与会话;默认是「全部设备」总览。设置仍在右上角,管互联配置。
- 任务内容而不只是轨迹。 索引里现在带任务正文(待办条目、团队任务、目标文案,每会话最多 12 条),设备详情直接列出任务清单;阅读器顶部也显示这条会话的任务内容,正文照旧是完整对话。
- 任务接力。 每条对端任务/会话旁有「接力到本机」:把完整事件日志复制成本机一条新会话(同一套继承机制),随后自动切到本机设备、可以继续干;原会话留在对端。
- 记忆共享开关。 设备栏上的开关控制本机是否把对话正文共享给中继站:关掉后只广播标题与任务计数,对端拉正文/继承都会被明确拒绝(memory-sharing-off),开关状态随索引广播。
- 更新不断联。 中继站的中继与隧道改为 detach 启动并落盘 station.json:插件升级/重启后新进程直接接续同一个地址与邀请令牌(adoptStation),已加入的设备不用重新加入。
- 图标真的换了。 侧边栏图标以前是 client.js 里内联的地球+链接,不是 icon.svg;现在两处都是「一台电脑—锁—另一台电脑」。
注意:v3.0.2 未改协议(仍是 v3),所以 3.0.0 / 3.0.1 / 3.0.2 之间可以滚动升级、混跑。
v3.0.0 Pro(发布补丁 v3.0.1):一键中继站、旧版失效、自动检查更新
- 中继站取代旧互联方式。 不再需要在两台机器上填同一个中继地址、也不再靠账号或口令推出共享密钥:一台设备生成中继站,另一台粘贴链接加入。邀请令牌就是密钥输入。
- 3.0.0 Pro 以下全部失效。 协议升到 v3:频道/密钥的域分隔符、AAD 里的版本号都变了, v2 客户端连帧都解不开;中继站还要求升级请求带 proto=3,旧客户端直接吃 426 Upgrade Required。
旧版不是"被劝退",而是在协议层被拒绝。
- 插件内自动检查更新。 Host 每 6 小时(面板首开也会查一次)向 GitHub 要最新 release,面板顶部出现「有新版本 vX」时,设置里会给出可直接复制的升级命令。
- 图标改成两台电脑相连(plugin/icon.svg)。
v2.0.0:出现彼此设备后立即全文同步,并且可继承
v2 把「点开才拉」变成「一出现就同步」:
- 自动全文同步:只要对端出现在设备列表里,本机就把它索引里的每一条会话正文 排队拉下来(autoSyncHistory,默认开),存进 ~/.dsh/deep-connect/remote/<设备>/。
列表行的 hasCopy 就是「这条正文已经在本机了」;离线也能照常阅读。
一条只按 (eventCount, updatedAt) 变化过的会话才会重拉,稳定语料不产生流量。
- 打开即读:阅读器先读本地副本,只有副本过期才发一次网络请求。
- 继承到本机:阅读对端会话时,右下角的 「继承到本机」 会把那条会话的 原始事件日志(不是摘要)经中继加密搬过来,用本机的 sessionPersistence 落成一条新的本地会话,然后就能用本机的模型和工具接着干。原会话仍留在对端,两边各自追加,不会分叉。
- 接管仍在:输入框发送 / 插话仍然是把消息交给拥有会话的那台机器执行(sessionController.prompt),适合「让那台机器继续跑」。
「继承」写的是真会话:新会话出现在普通会话列表里,可 resume、可改名、可继续。
单条上限 12 MB(中继单帧 16 MB),超了会明确拒绝而不是悄悄截断。
本机没挂载 sessionPersistence 的部署会直接说明无法继承。
发版检查:node tools/station-smoke.mjs 真起一个中继站(relay 子进程 + cloudflared 隧道)并验证令牌、proto=3 门禁与邀请链接往返;node tools/deep-connect-e2e.mjs 覆盖了自动全文同步、缓存优先读取、继承落地等路径(116 项),relay-smoke(40 项)与 client-check(112 项)不变。
面板里能看到什么
| 区域 | 内容 |
| 顶部 | 本机名、连接状态灯、中继地址、密钥指纹、重新连接 / 设置 |
| 设置 | 中继地址、访问令牌(可选)、共享口令(可选)、设备名、自动同步索引、保存 |
| 设备 | 每台对端:名字、设备 id、在线徽标、最后在线时间、会话数 |
| 会话 | 「本机」与每台对端一组:标题、相对时间、会话 id、事件数、cwd、任务标签、最后一句提问预览 |
| 阅读 | 点开任一条:完整正文(先读本机副本),工具调用可折叠、报错的工具红色显示;底部可继续对话,对端会话右下角有「继承到本机」 |
继续一条对话意味着什么
在 B 机点开 A 机的会话,输入文字按发送:
1. B 机的 Host 把这句话加密后交给中继;
2. A 机的 Host 收到并解密,通过 DSH 自己的 ctx.sessionController.prompt(...) 投喂给本地 Agent —— 和你在 A 机键盘上打字走的是同一条路;
3. 真正执行的是 A 机,工具调用落在 A 机的磁盘和权限里;
4. B 机刷新后看到新内容。
对端离线时输入框会禁用,并说明原因;不会静默失败,也不会假装成功。
数据放在哪
~/.dsh/deep-connect/
config.json 中继地址、令牌、口令、开关 identity.json 本机 deviceId 与设备名 peers.json 对方设备名单 index/<deviceId>.json 对方的会话索引 remote/<deviceId>/<id>.json 对方会话的本地副本(只读,自动同步而来,不注册进本机会话列表) outbox.json 中继断开时暂存待发信封
写入一律「临时文件 + 原子重命名」。对端会话副本不注册进会话存储,所以既不会混进你的会话列表,也不会被误 resume;只有你主动点「继承到本机」时,才会通过 sessionPersistence.create/append/flush 写出一条新的本机会话,那条和普通会话一样出现在列表里、可以 resume。
实现要点
它不自己解析会话文件。 磁盘上的 session.vN.jsonl.zstd 是多个独立 zstd 帧首尾相接(实测一个 199,882 字节的文件里 149 帧、解出 646,403 字节),带校验和与崩溃尾巴恢复。
插件一律走 ctx.sessionQuery(没有则退回 ctx.sessionPersistence),把逐帧、校验、历史格式迁移都交给唯一正确的那个实现。
索引很轻。 一次 list() 拿全部 header,再用 stat() 的变更令牌判断哪条会话真的变了,只重读变化的那条,折叠出标题、事件数、最近三句提问与任务计数。所以一个稳定的语料库几乎不花代价。
请求/响应先验证链路。 中继重启后,旧 TCP 连接不会自己报错,socket 仍显示可写,写进去的帧静默消失。所以发请求前先做一次 ping/pong 往返——完成了往返才算链路活着;心跳发现对端不回 pong 时强制拆掉 socket(close() 在死连接上永远等不到握手完成,重连路径就不会触发)。
中继是敌意但有用的。 它会丢、会重、会乱序、会扣着不发;但它改不了内容(AAD 绑定收发双方),也读不出来。
已验证 / 未验证
| 部分 | 状态 |
| 中继 relay/relay.cjs | ✅ 端到端验证,40 项检查(真实 WebSocket 帧、掩码、分片、长度三种编码、离线信箱与重连排空、半开连接离场、上限与淘汰、长轮询、频道隔离、令牌门、重启后落盘恢复) |
| Host 半 + 客户端半 + 中继的联通 | ✅ 端到端验证,116 项检查:两台真实插件实例各自持有会话语料,经真实中继互通(密钥一致、索引过线、任务折叠、完整历史、设备一出现就自动全文同步、缓存优先读取、继承成本机会话且事件逐条重排、无法伪造未抓取的会话、接管投喂、陌生账号进不来、篡改/换收件人被拒、设置往返) |
| 浏览器半渲染 | ✅ 112 项离线检查(把 client.js 当文本 eval,用桩 React 渲染面板,断言真机载荷下的输出)+ 真机 GUI 无头 Chrome 打开面板逐项核对 |
| deep-connect/relay/cloudflare | ⚠️ 未运行过(本机 workers.dev 被污染且没有账号)。按官方 API 写,首次 wrangler deploy 才是真测试 |
| deep-connect/relay/deno | ⚠️ 未运行过。同上,协议与 relay.cjs 一致 |
跑测试:
node tools/relay-smoke.mjs # 中继(40 项)
node tools/station-smoke.mjs # 一键中继站:真起 relay + cloudflared + 接续 + 看护(20 项)
node tools/deep-connect-e2e.mjs # 两台机器 + 中继(116 项)
node tools/client-check.mjs # 浏览器半(112 项)
node ../tools/browser/check-connect.mjs # 真机 GUI:面板真的渲染出来(需 GUI 在 19387 运行)
浏览器半重写(2026-10-15)
症状:点左侧「Deep Connect」主区域空白。控制台:
Error: Minified React error #31 … args[]=object with keys {title} slot entry crashed in 'main'
根因:元素层是 span(className, ...children),但调用点写的是
span('dc_factValue', value, { title })——{title} 被当成 children 交给 React。
真实 React 拒绝「对象作为子节点」并抛 error #31;槽位对抛错的 entry 的处理是退出该 entry,于是 main 槽位里这个面板整个消失,只剩空主区域。
离线的 client-check 用桩 React,不做子节点校验,所以 70/70 通过却掩盖了真机崩溃——这类差异只有真机(或严格桩)能发现。
重写要点:
1. 尾参数 props bag 是一等公民:element(tag, className, ...rest) 若最后一个参数是朴素对象,就把它当 props({ title } 变成属性),之前的参数才是 children。
2. children 过白名单:renderable() 丢掉 React 渲染不了的对象并 console.warn——即便将来再有人写错,也不会把对象交给 React,error #31 这一类从结构上不可能再现。
3. 注册永不抛出:apply() 包 try/catch,失败也占住 main 槽位;面板渲染失败走 React error boundary(PanelBoundary)——只有 boundary 能接住 React 渲染期抛出的异常,普通 try 包住 render 调用接不住(这正是上一版「已经 try 过了却还是空白」的原因)。
4. 离线桩没有类组件,PanelBoundary 只在有 React.Component 时构造,其余情况由 PanelShell 兜底。
验证:client-check 80/80(重写后新增 10 项:props bag 进 props 不进 children、className 合并、 bag 之前的 children 全部保留、child 位上的对象被丢弃、渲染失败被兜住并显示原因); relay-smoke、deep-connect-e2e 全过;真机 GUI 无头 Chrome 实测面板
([data-dc="panel"] 1160×900,标题、本机/中继/指纹、本机 34 会话分组、任务 chips 全部就位,
控制台不再出现 slot entry crashed in 'main')。
Host 半与中继未重写:Host 的 POST /api/deep-connect/api {action:"list"} 实测返回完整视图,中继与端到端各有真实 WebSocket 测试且全过。它们不在这条故障链上,重写只会引入新风险。
手动安装(不想跑脚本)
1. 把本目录链接进配置档:
ln -s /绝对路径/dsh-deep-connect/plugin \
~/.dsh/profiles/desktop/node_modules/@local/dsh-deep-connect
2. 在 ~/.dsh/profiles/desktop/package.json 里加依赖 "@local/dsh-deep-connect": "link:/绝对路径/dsh-deep-connect/plugin",并把 @local/dsh-deep-connect 加进 dsh.profile.bundles。
3. 在 ~/.dsh/profiles/desktop/cordis.patch.yml 末尾追加:
- insert:
- id: deep-connect
name: '@local/dsh-deep-connect'
config:
root: !!js dshHomePath('deep-connect')
relayUrl: !!js process.env.DSH_DEEP_CONNECT_RELAY ?? ''
secret: !!js process.env.DSH_DEEP_CONNECT_SECRET ?? ''
indexIntervalMs: 15000
4. 重启 Harness。
仓库结构
| 路径 | 作用 |
| plugin/ | 插件本体:index.js(Host 半)、client.js(浏览器半)、host/(加密、存储、会话读取、中继客户端、继承写入 inherit.js)、cordis.patch.yml、package.json |
| relay/ | 中继端:relay.cjs(零依赖单文件,已验证)、install.sh、DEPLOY.md、cloudflare/、deno/(云端版,未运行验证) |
| install.sh / uninstall.sh | 装/卸。支持本地 checkout 与 curl \| bash 远程安装,幂等并备份 |
| tools/ | 三套测试:relay-smoke.mjs、deep-connect-e2e.mjs、client-check.mjs |
| DESIGN.md | 设计依据:为什么是这个形状、中继契约、密码学、数据流、磁盘布局 |
跑测试(只需要 Node ≥ 22,无依赖):
node tools/relay-smoke.mjs
node tools/station-smoke.mjs
node tools/deep-connect-e2e.mjs
node tools/client-check.mjs
卸载
./uninstall.sh # 移除配置行与链接,保留 ~/.dsh/deep-connect 数据 ./uninstall.sh --purge # 连数据、中继状态和下载的源码一起删
远程安装(curl | bash)后本仓库位于 ~/.dsh/plugins/dsh-deep-connect,卸载脚本也可以在仓库里跑 ./uninstall.sh。
许可证
MIT。
论文
本仓库附带一篇 Nature 格式的技术论文(英文 18 页,另有中文版),记录设计、协议、密码学、威胁模型、实测数据与故障目录:
- 英文:paper/DeepConnect-Nature-EN.pdf
- 中文:paper/DeepConnect-Nature-ZH.pdf
- 源文件:paper/(src/ 为中英文正文与元数据,measure.mjs 采集数据,make_figures.py 出图,47 篇参考文献)
复现:
node paper/measure.mjs # 采集真实测量数据 -> paper/data/metrics.json
python3 paper/make_figures.py # 生成中英文图表 -> paper/assets/
python3 paper/build.py # 生成 HTML -> paper/build/
node paper/render.mjs # 渲染 PDF
论文中的每个数字都来自 paper/data/metrics.json,构建脚本在占位符缺失时会失败,因此正文与图表不会漂移。
与既有方案的关系
写这个插件之前先查过生态,有两类现成东西值得说明:
- dsh-session-sync(Apache-2.0) 已经能做跨设备会话同步,走的是 git 镜像 + 三方合并。
它适合「事后对齐」,但需要一个两台机器都够得着的 git remote,没有在线状态、没有实时,也看不到对方此刻在不开着哪条会话。Deep Connect 走的是常驻中继,要的是「现在就能看见、点开就能读、还能把消息递过去」。
- @dshfly/remote-connector、onebox-dsh-bridge(MIT) 已经把「PC 主动外连的中继 + 端到端加密 + 扫码配对」这套架构在手机遥控电脑上做成了产品,也解决过 DSH 的浏览器鉴权围栏。
Deep Connect 和它们的差别是目标:这里要的是同账号两台电脑之间的会话/任务对等同步,而不是单机遥控。
已知边界
- 没有前向保密:密钥由账号 id(或你的口令)派生,靠的是「只有同账号推导得出」。
要做到「中继被攻破也解不开历史」,需要一次带外配对生成设备密钥,属于二期。
- 中继能看到元数据:在线设备数、设备名、信封大小与时间。这些是路由必需的。
- 不做冲突合并:一条会话只有一台机器写,所以不存在需要合并的分叉。真正的双向协同编辑不在范围内。
- 附件不同步:对话里的图片/文件引用会出现在正文里,但字节不搬运。
- 接管是「远程投喂」而非「搬迁」:会话留在原机器上执行。在一台机器上迁移整条会话需要独占租约,属于二期。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
zengpu987-ctrl/dsh-deep-connect
在多台设备间加密同步会话与任务,可远程接管或迁移
相关插件 · 市场 / 管理
插件精选列表awesome-dsh-plugin/awesome-dsh-plugin
DeepSeek Harness(dsh)插件精选列表
★ 22,225未汉化DSH
73.6
新词雷达zhuyansen/new-term-radar
new-term-radar新词雷达
★ 2,049已汉化MCP
72.8
插件生态导航0xsline/awesome-deepseek-harness
安装 贡献指南 DeepSeek Docs 公开插件目录 Issues
★ 1,188已汉化DSH
72.6
技能注册中心iflytek/skillhub
企业级开源智能体技能注册中心——发布、发现并管理组织内可复用的技能包。
★ 5,370已汉化DSH
71.9
插件市场目录imsai-sh/awesome-deepseek-harness-plugins
面向 DeepSeek Harnessdsh生态的社区插件目录,共收录 13941 个插件含 PR 收录与…
★ 355已汉化DSH
71.8
插件精选清单Anil-matcha/awesome-dsh-plugin
DeepSeek Harnessdsh精选指南——DeepSeek…
★ 1,013已汉化DSH
71.7
这是你的仓库?认领条目