远程监控和指导 DSH 智能体——一个 Cordis 插件,支持 macOS 和 Windows,外加一个 Swift 原生 iOS/iPad 应用。
36.7/100
综合分
6
GitHub 星标
1,278
npm 周下载
—/5
用户评分
数据截至 2026/10/28 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-mizarnpm 源 · dsh-mizar 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · market
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 7 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-mizar @ 0.8.14
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/29 09:42:44
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 45 处,展示前 43 条)
- 发起网络请求lib/client.js:1822
const response = await fetch("/api/mizar.status", { headers: { accept: "application/json" } }); - 发起网络请求lib/client.js:1914
const response = await fetch(path, { - 读取本地文件lib/bridge/sessions.js:1539
const stream = createReadStream(path); - 读取本地文件lib/bridge/sessions.js:1615
bytes = await readFile(attachment.path); - 发起网络请求lib/push/transport.js:59
const response = await fetch(url, { - 读取本地文件lib/auth/store.js:218
return await readFile(path, 'utf8'); - 写入本地文件lib/auth/store.js:250
await rename(temp, path); - 删除本地文件lib/auth/store.js:253
await unlink(temp).catch(() => { }); - 写入本地文件lib/auth/store.js:291
await mkdir(dir, { recursive: true, mode: 0o700 }); - 写入本地文件lib/auth/store.js:309
await rename(join(dir, 'state.json'), quarantine); - 写入本地文件lib/auth/store.js:408
await appendFile(this.#auditPath, `${JSON.stringify(event)}\n`, { encoding: 'utf8', mode: 0o600 }); - 发起网络请求lib/relay/client.js:545
const response = await fetch(`${this.#localOrigin}${String(frame.path)}`, { - 写入本地文件lib/relay/identity.js:49
await mkdir(stateDir, { recursive: true }); - 写入本地文件lib/relay/identity.js:51
await writeFile(path, `${JSON.stringify(stored, null, 2)}\n`, { mode: 0o600 }); - 写入本地文件lib/relay/identity.js:56
await chmod(path, 0o600).catch(() => { }); - 读取本地文件lib/relay/identity.js:74
text = await readFile(path, 'utf8'); - 读取本地文件lib/relay/selection.js:84
raw = await readFile(overridePath(stateDir), 'utf8'); - 写入本地文件lib/relay/selection.js:123
await mkdir(stateDir, { recursive: true, mode: 0o700 }); - 写入本地文件lib/relay/selection.js:126
await writeFile(temporary, `${JSON.stringify({ url: canonical }, null, 2)}\n`, { mode: 0o600 }); - 写入本地文件lib/relay/selection.js:127
await rename(temporary, target); - 读取本地文件lib/bridge/files.js:176
const root = await realpath(cwd).catch(() => null); - 读取本地文件lib/bridge/files.js:184
const absolute = await realpath(candidate).catch(() => null); - 读取本地文件lib/bridge/files.js:196
const facts = await stat(absolute).catch(() => null); - 写入本地文件lib/bridge/uploads.js:480
await mkdir(dir, { recursive: true, mode: 0o700 }); - 写入本地文件lib/bridge/uploads.js:481
await writeFile(join(dir, 'payload'), new Uint8Array(0), { mode: 0o600 }); - 删除本地文件递归lib/bridge/uploads.js:641
void rm(upload.dir, { recursive: true, force: true }).catch(() => { }); - 删除本地文件递归lib/bridge/uploads.js:734
await rm(upload.dir, { recursive: true, force: true }).catch(() => { }); - 删除本地文件递归lib/bridge/uploads.js:776
await rm(attachment.dir, { recursive: true, force: true }).catch(() => { }); - 写入本地文件lib/bridge/uploads.js:824
await mkdir(dir, { recursive: true, mode: 0o700 }); - 写入本地文件lib/bridge/uploads.js:825
await copyFile(join(attachment.dir, 'payload'), destination); - 删除本地文件递归lib/bridge/uploads.js:847
void rm(upload.dir, { recursive: true, force: true }).catch(() => { }); - 删除本地文件递归lib/bridge/uploads.js:873
await rm(upload.dir, { recursive: true, force: true }).catch(() => { }); - 删除本地文件递归lib/bridge/uploads.js:882
await rm(attachment.dir, { recursive: true, force: true }).catch(() => { }); - 读取本地文件lib/bridge/uploads.js:889
entries = await readdir(this.#root); - 读取本地文件lib/bridge/uploads.js:902
const payload = await stat(join(dir, 'payload')); - 写入本地文件lib/bridge/private-directory.js:67
await mkdir(path, { recursive: true }); - 写入本地文件lib/bridge/private-directory.js:69
await chmod(path, 0o700); - 执行外部命令来自配置/变量lib/bridge/private-directory.js:76
const child = spawn(executable, ['-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(WINDOWS_ACL, 'utf16le').toString('base64') - 发起网络请求lib/transport/p2p/portmap.js:74
const mapping = await this.request(internalPort, internalPort, lifetimeSeconds); - 发起网络请求lib/transport/p2p/portmap.js:81
const mapping = await this.request(this.internalPort, this.mapping.externalPort, this.lifetimeSeconds); - 发起网络请求lib/transport/p2p/portmap.js:89
await this.request(this.internalPort, 0, 0).catch(() => undefined); - 发起网络请求lib/transport/p2p/portmap.js:239
const response = await fetch(this.device.controlURL, { - 发起网络请求lib/transport/p2p/portmap.js:310
const response = await fetch(location);
扫描于 2026/11/3(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-api-gateway@deepseek-ai/dsh-api-session-controller@deepseek-ai/dsh-client-connection@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-commands@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-permission-presets@deepseek-ai/dsh-session@deepseek-ai/dsh-session-projection@deepseek-ai/dsh-session-stats@deepseek-ai/dsh-session-title给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
最近更新
在 GitHub 查看全部 →v0.5.02026-10-15
· v0.5.0 — 消息递不进来的真正原因
· 我的预算是按「字符数」算的,不是「字节数」
const size = JSON.stringify(record)?.length ?? 0 // ❌ UTF-16 码元
一个中文字符是 1 个 UTF-16 码元,但 UTF-8 是 3 字节。
所以"512KB 预算"实际发出的是 1.5–2MB——而你所有对话都是中文,所以测试时永远正常、使用时永远超限。
现在用 Buffer.byteLength(..., 'utf8'),预算降到 256KB。
实测:十轮长中文 → 86KB。而你的日志里是 2.0MB、2.9MB、5.1MB。
· 加了全局出站护栏
每个方法的预算只能覆盖被测量过的那一个方法——下一个超限的响应来自别处,这就是教训。
现在监听器发出的任何帧都检查大小,超了改用 RESPONSE_TOO_LARGE 错误码回过去,而不是发出去掐断连接。
· 另外两个
· 同步小窗不出现:同一个视图上挂了两个 .sheet,SwiftUI 只会显示一个——命名弹窗和进度小窗冲突了。命名弹窗已移到详情侧。
· 掉帧:对话用的是 VStack(全量渲染),几百条消息时每条都在布局。改成 LazyVStack。
· 升级
插件 0.5.0 + App 0.5.1 (8) —— 两个都要。
334 插件测试 + 112 MizarKit 测试。
v0.4.52026-10-15
· v0.4.5 — 修好「消息递不进来」
· 真正的原因(来自设备日志)
input message size 2222614 exceeds maximum message size 1048576
nw_read_request_report [C1] Receive failed with error "Message too long"
iOS 的 WebSocket 拒绝任何超过 1MB 的帧,而且是直接掐断连接。
session.messages 的响应有 2.1MB —— 所以它不是"少显示一些",是整条连接死了。
这一段日志解释了全部症状:
| 现象 | 原因 |
|---|---|
| 模型回复不显示 | 响应根本没到达 |
| 下拉刷新历史全没 | 同上,而且旧内容被空数组覆盖 |
| 断开、无法重连 | 传输层已经死了 |
· 修法
给每页加 512KB 字节预算,从最旧的记录开始裁。
之前两次我都在猜"哪些记录大"——这次是保证:无论记录里有什么,都不可能超过限制。
实测:12 轮对话现在返回 104 条记录 / 51KB,maxMessages 40 或 200 都一样。
· 顺带修好的
· 模型回复的读取位置:真实日志里 assistant/message 的文本在 data.message.content[],比用户消息的 data.content[] 深一层——这就是回复一直不显示的另一个原因
· 下拉刷新不再清空历史:失败时保留已有内容
· 移除了键盘上方那个浮动的「完成」胶囊(iOS 26 上它渲染错位,挡住了发送键)
· 升级
插件 0.4.5:GUI → 侧栏「插件」→ 卸载 → 添加插件 → ShirazuNagisa/DSH-Mizar
App 0.4.4 (6):装新的 IPA
两边都要更新。
329 插件测试 + 108 MizarKit 测试 + iOS UI 11/12。
v0.4.22026-10-15
· v0.4.2 — 修好对话显示
· 修了什么
消息消失(我上一版引入的回归):把每页请求从 40 条提到 200 条之后,响应大到发不回来,
而读取失败时 App 返回空数组——于是整个对话变空白,而不是少显示一些。
体积不是消息本身。 实测:一条 11 字的消息会附带 483 字的运行时上下文和
5921 字的技能目录,都以普通 user/message 事件发出。每轮约 6KB,而用户可能在用蜂窝网络。
现在插件在发出前就把它们丢掉,消息窗口保持 200 条。同一个会话现在返回 32 条记录、
33.7KB、0 条注入。
模型回复不显示:DSH 的 SessionHistoryRecord 有两种助手事件——
assistant/message(已提交的回复)和 assistant/attempt(流式尝试)。
解析器只认识后者,所以模型答得再好手机上也是空白。
桌面端发消息看不到:过滤用的是白名单(只保留 source.kind == "user"),
这会把一切在 Mac 自己的 DSH 窗口里打的字都隐藏掉。改成黑名单——
只按名字屏蔽那两个实测注入,其余全部显示。
· 升级
插件:GUI → 侧栏「插件」→ DSH-Mizar → 卸载 → 添加插件 → ShirazuNagisa/DSH-Mizar
(现在是 0.4.2)
App:装新的 0.4.1 (4) IPA
两边都要更新。插件负责不发垃圾数据,App 负责正确解析。
· 测试
328 插件 + 69 中继 + 102 MizarKit + iOS UI 11/12。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成DSH-Mizar 插件
用手机看 DSH Agent 干活,也能给它发消息、回答审批。
这个仓库是电脑侧插件(DSH 的一个 Cordis 插件,跑在 DSH 所在的那台机器上)。另外三个仓库:
| DSH-Mizar-iOS | 手机 App(iOS / iPadOS) |
| DSH-Mizar-Android | 手机 App(Android) |
| DSH-Mizar-Relay | 中继服务器,让手机从任何网络连上你的电脑 |
一、装插件
推荐用 npm 装:插件已发布到 npm,包名 dsh-mizar,与当前 DSH 运行时(0.2.1-rc.2)对齐。
装完都要重启 DSH —— 插件只在启动时加载,不重启等于没装。
推荐:图形界面(不碰终端)
1. 打开 DSH,左侧栏点 插件(Plugins)
2. 安装输入框里填 dsh-mizar,点安装
3. 确认它弹出来的包名 / 版本 / 描述
4. 重启 DSH
推荐:命令行
dsh plugin --profile desktop add dsh-mizar # 跟随 npm 最新版
dsh plugin --profile desktop add dsh-mizar@0.8.4 # 锁定某个版本
两种装法都会把依赖写成 dsh-mizar@^x.y.z。
展开完整文档(README 剩余 1 节)
DSH-Mizar 插件
用手机看 DSH Agent 干活,也能给它发消息、回答审批。
这个仓库是电脑侧插件(DSH 的一个 Cordis 插件,跑在 DSH 所在的那台机器上)。另外三个仓库:
| DSH-Mizar-iOS | 手机 App(iOS / iPadOS) |
| DSH-Mizar-Android | 手机 App(Android) |
| DSH-Mizar-Relay | 中继服务器,让手机从任何网络连上你的电脑 |
一、装插件
推荐用 npm 装:插件已发布到 npm,包名 dsh-mizar,与当前 DSH 运行时(0.2.1-rc.2)对齐。
装完都要重启 DSH —— 插件只在启动时加载,不重启等于没装。
推荐:图形界面(不碰终端)
1. 打开 DSH,左侧栏点 插件(Plugins)
2. 安装输入框里填 dsh-mizar,点安装
3. 确认它弹出来的包名 / 版本 / 描述
4. 重启 DSH
推荐:命令行
dsh plugin --profile desktop add dsh-mizar # 跟随 npm 最新版
dsh plugin --profile desktop add dsh-mizar@0.8.4 # 锁定某个版本
两种装法都会把依赖写成 dsh-mizar@^x.y.z。升级只要再跑一次 add dsh-mizar@latest。
重启后侧边栏会出现 DSH-Mizar 面板。
为什么是 npm,而不是 GitHub 直装
| | npm(推荐) | GitHub 直装 |
| 版本 | 有版本号,可锁 @0.8.4 | 跟随 main 最新提交,不可锁 |
| 可追溯 | npm view dsh-mizar versions、npm outdated | 只有 commit hash |
| 完整性 | registry sha512 校验 | 无包完整性校验 |
| 前提 | 只要能访问 registry | 需要 git + pnpm 的 git 支持 |
| 适合 | 所有人 | 只适合尝鲜未发布的提交 |
备选:GitHub / 本地 tarball
尝鲜:装 main 上还没发版的提交
dsh plugin --profile desktop add ShirazuNagisa/DSH-Mizar
离线 / 内网:在有网的机器上 npm pack,再把 .tgz 传过去
npm pack dsh-mizar
dsh plugin --profile desktop add /path/to/dsh-mizar-0.8.4.tgz
装不到最新版 / 镜像同步延迟
插件发布在 npm 官方源(registry.npmjs.org)。国内镜像(如 registry.npmmirror.com)偶尔滞后,表现是 pnpm 抓到一个旧的或占位版本。两种情况:
- 加 --registry 直连官方源:
dsh plugin --profile desktop add dsh-mizar@0.8.4 --registry=https://registry.npmjs.org
- 或者去 npmmirror 同步页 点一次「同步」,等镜像更新。
GUI 的安装框走的是本机默认 registry;镜像没同步时,用上面的 CLI 命令更稳。
Windows:覆盖安装/升级报 ERR_PNPM_ENOENT ... dsh-mizar_tmp_
Windows 上覆盖已有旧版时(profile 里已经有 dsh-mizar),pnpm 的 hoisted 安装会先把新包解到 node_modules\dsh-mizar_tmp_<pid>_<n>,再替换旧目录;这一步在 Windows 上会撞上文件锁 / 杀毒软件扫描的竞态,报:
[ERR_PNPM_ENOENT] [importPackage C:\Users\<you>\.dsh\profiles\desktop\node_modules\dsh-mizar]
ENOENT: no such file or directory, scandir '...\node_modules\dsh-mizar_tmp_<pid>_<n>\node_modules'
这是 pnpm 在 Windows + nodeLinker: hoisted 下替换包目录的已知竞态,不是 npm 包损坏: #6961 是同一串报错(讨论里在 11.27+ 的新版 Windows 上没能复现);其他 DSH 插件也遇到过并靠重试解决(dsh-TUI#225)。
同一份 0.8.9 tarball 在 macOS 上,用 DSH 自带的 pnpm 11.28.5 和同样的 hoisted 配置, 全新安装和 0.8.8 → 0.8.9 覆盖升级都能装好,registry 的 sha512 也与包内容一致。
DSH 0.2.1-rc.2 的插件管理器在 pnpm 失败后会回滚 package.json 和 pnpm-lock.yaml; node_modules 不会被回滚(DSH 自己的已知限制里也写了失败安装可能留下半成品文件),所以重试可能继续撞上同一个半成品目录。按顺序处理:
1. 原样重试一次。这是竞态,第二次通常就成功:
dsh plugin --profile desktop add dsh-mizar@0.8.9
2. 仍报同一个 _tmp_ 错误:先完全退出 DSH(任务栏托盘也要退出),在 PowerShell 里清掉半成品目录再装。目标目录不存在时 pnpm 走的是不会触发该竞态的直装路径: $p = "$env:USERPROFILE\.dsh\profiles\desktop"powershell Remove-Item -Recurse -Force "$p\node_modules\dsh-mizar" -ErrorAction SilentlyContinue Get-ChildItem "$p\node_modules" -Directory -Filter "dsh-mizar_tmp_" | Remove-Item -Recurse -Force
dsh plugin --profile desktop add dsh-mizar@0.8.9
3. 确认版本,然后重启 DSH(插件只在启动时加载): powershell (Get-Content "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-mizar\package.json" | ConvertFrom-Json).version
输出 0.8.9 才是装对了
图形界面安装同理:先重试一次;还失败就先退出 DSH、按上面清目录,再从界面装。
三步之后仍不行时,把 %USERPROFILE%\.dsh\profiles\desktop\.plugin-manager\logs 里最新的日志留下来——里面有 pnpm 的完整输出,可以直接看出是下载、校验还是链接哪一步失败。
根治在 DSH 的插件管理器给它做一次自动重试(或者升级内置 pnpm),插件包本身没有可改的地方:报错发生在 pnpm 把包搬进 profile 的那一步,插件还没被解包。
版本必须与 DSH 对齐
DSH 会精确比对插件声明的 @deepseek-ai/dsh- 与运行时版本,不匹配时安装会被拒绝:
Plugin dsh-mizar@0.8.4 is incompatible with dsh 0.2.1-rc.2
- 正常做法:升级到匹配的插件版本(DSH 每个 rc 发布后,插件跟一个 +0.0.1);
- 临时跑起来(有风险,只在确认没有 API 变更时用): sh
dsh plugin --profile desktop allow-version dsh-mizar@0.8.4 --dsh-version 0.2.1-rc.2 --accept-risk
二、配对
1. 电脑上打开 DSH,点侧边栏的 DSH-Mizar
2. 面板里会出现二维码
3. 手机装好 App 后扫码
没有别的步骤。 不用填 token,不用填地址,不用改文件。
三、插件做什么
- 把会话列表、对话正文、用量、审批和提问转给手机
- 手机可以发消息、打断正在跑的 turn、回答审批和提问、切换模型与权限
- 付费账户可以在会话里发送照片或文件;图片作为 DSH 图片内容,文件作为 DSH 文件内容或可读路径句柄
- 推送(审批 / 提问 / 跑完)经中继转给 Apple
- 手机和电脑在同一个局域网时优先直连(局域网通信),连不上再走中继;中继开着时监听器也会绑局域网地址
监听地址:中继开着也绑局域网
监听器默认绑这台电脑的局域网 IPv4 地址,中继开着的时候也一样(另外总会绑 127.0.0.1,中继靠它把手机请求转回本机)。0.9.4 起「局域网通信」是产品承诺的功能:手机和电脑在同一个局域网时直连,不占中继带宽,也是手机端解锁付费功能的条件。如果中继一开就把监听器缩回 loopback,手机永远找不到可直连的地址,这个功能等于不存在。
监听器有设备令牌鉴权和配对频率限制。把它绑到局域网上,只是让同网段的手机能直连,不会把 DSH 自己的 Web 接口暴露出去(那是 DSH 的安全边界,插件不碰)。
想退回「只监听本机」的老姿势(比如这台电脑接在不可信的网络上,只用中继): yaml
~/.dsh/profiles/<profile>/cordis.patch.yml
- id: mizar
config:
relay:
preferLoopback: true
把 preferLoopback 换成 bind.mode: loopback 效果相同,两个写一个就够。代价是没有局域网通信:手机即使和电脑在同一个 Wi-Fi 也走中继,局域网解锁(附件、全量细节)不生效,其余功能不变。
中继隧道自愈:断线会自己重连
中继方向有两个只有长期运行才会暴露的故障,0.8.6 起都做了兜底:
- 半开隧道。 电脑换网络(Wi-Fi 切换、VPN 断掉、休眠唤醒)时,本地 socket 可能既收不到中继的 close、也不再有任何流量,而系统仍报 ESTABLISHED。中继那边过了 90 秒没收到 pong 就把房间摘掉;插件这边却一直显示「已连接」,于是再也不重拨 —— 手机无论直连还是中继都连不上,只能重启 DSH。现在插件自己有静默看门狗:超过 relay.watchdogMs(默认 120 秒,即中继 30 秒一次 ping 的四个周期)没收到中继任何一帧,就强制丢弃这条 socket 并重拨。可用 relay.watchdogMs: 0 关掉。
- 端口漂移。 DSH 可能重复挂载/重载插件;以前每次挂载都绑一个新监听,端口从 19390 一路漂到 19391、19392,手机配对里保存的直连地址就永远失效了。现在一个进程只保留一个挂载:新挂载要等旧挂载完全释放监听之后才绑端口;网络变化导致监听地址改变时,隧道也会立刻用新地址重拨,并把监听端口的变化同步给隧道(否则隧道健康、手机请求却全部 502)。
- 0.8.7 修了 0.8.6 单实例的漏洞。 0.8.6 的注册表是模块级的,而 DSH 会在同一进程里多次加载插件模块,每份副本各有一张空注册表——实测一个进程因此挂了 8 个监听(19390–19397)。
0.8.7 把注册表放到 globalThis(Symbol.for('dsh-mizar.mount-registry')),所有模块副本共用同一个槽位,一个进程只剩一个监听。
日志里对应 relay: the tunnel has been silent for …s — redialling it、 relay: the network changed — redialling the tunnel 和 relay: the listener moved to port …。
付费账户的高带宽中继(0.8.8)
付费账户的隧道现在会跟着订阅走:App 知道账户是否付费,插件不知道,所以由 App 调 relay.set 请求电脑把隧道移到账号中继(dshmizar-1.shirazu-nagisa.com);免费账户仍走 dshmizar.shirazu-nagisa.com,登录/登出或订阅到期时会自动切回。
- 隧道令牌不变 ⇒ 房间不变,已配对关系不受影响,只是断开重拨。
- 选择持久化在插件状态目录(relay-override.json,600),重启后继续生效。
- 插件只接受两个公共中继,自建 relay.url 时该能力不出现;旧宿主回 NOT_IMPLEMENTED,旧客户端不会调用,行为完全不变。
- 切换前插件广播 relay.changed,其它已配对设备会更新保存的中继地址;离线的手机下次连接时会用同一房间尝试另一个公共中继自愈。
- 连接面板里的「高带宽服务器 / 免费版服务器」现在按实际所在中继显示,不再只看账户等级。
协议细节(relay.set、host.info.relay、relay.changed)见 docs/protocol-v1.md。
完成回复的用量、分支与账户余额(0.8.9)
手机端在「已完成」的回复下多了一行工具栏(复制 / 分支 / token 用量 / 本地时间),并新增了账户余额页。数据都由插件按 DSH 自己的语义给出,缺数据就是 unknown,绝不估算或补零:
- 整轮用量 mizarTokenUsage 挂在 turn/end.data 上。只折叠原始连续 turn/start → turn/end 窗口,用官方 deriveTurnTokenUsage 的纯函数;构建时把它打进 lib/bridge/turn-usage-fold.js,安装后的插件不依赖宿主的 token-meter 运行时。页边界不完整或缺少生命周期时省略该字段,客户端显示不可用。
- 能否分支 mizarBranchUnavailable:这一轮总结之后还有工具活动、非文本回答、重试或错误时为 true;窗口不完整时省略(unknown),不伪称安全。旧插件没有这个字段,客户端保守禁用并提示刷新/升级。
- 按轮分支 session.fork:请求带 atSeq(所选轮次真实的 inclusive turn/end.seq),插件先验证该边界存在、窗口完整、总结之后没有非法尾部,再调用 DSH 的 session/fork。验证失败直接报错,不会退化成「用最新轮」。
- 官方账户余额 account.balance:只有 host.info.plugin.capabilities 含 deepseek.balance.v1 时客户端才调用。插件只走宿主原生账户端点,不读 API-key REST、不传输凭据或账户身份;金额是原样十进制字符串,unsupported / signed-out / unavailable 与真实的 0 严格区分,失败绝不显示为零。充值入口只会打开固定的官方 https://platform.deepseek.com/top_up,插件不发起任何充值请求。
- 子代理 activity:session.activity 增加 parentSessionId,子会话按父会话的地址读日志,与 session.messages 的寻址方式一致。
协议细节(account.balance、mizarTokenUsage/mizarBranchUnavailable、session.fork)见 docs/protocol-v1.md。
对话只传总结
一页历史里只保留这些:你的提问、每一轮的 turn/start 和 turn/end、每个 step/start(只用来数「几步」),以及每一轮里模型最后那条有正文的消息。
逐步叙述、工具调用与结果、流式过程,以及还没结束的那一轮的中间回复,一律不发。手机上那两行状态 —— 「已完成,用时 xx」和「深度求索中,用时 xx」—— 都不能展开,下面直接就是这一轮的总结。全量历史经中继是服务器上最大的一块流量,这是为了把它压下来。
分页切点取自 session/follow 首帧的 cursor(DSH 自己对 throughSeq 的定义)。用 session/projections 的 asOfSeq 会滞后于日志头,刚发出的那一轮就不在页里 —— 手机上表现为「AI 明明在干活,却没有正在工作的提示」。
四、通知
三种情况手机会收到推送,插件通过中继发给 Apple:
- Agent 卡在权限审批上,等你点头
- Agent 卡在提问上,等你回答
- 某个会话跑完了(跑挂/中断了会说挂掉,不会谎报完成)
一轮结束不会立刻推。 DSH 里一个指令常常连着跑好几个 turn,每个 turn 都推一次等于一个指令三条通知 —— 所以插件会等这个会话安静 15 秒才发。正在看着 App 的时候什么都不发。
锁屏上的 Live Activity 会跟着结束。 iPhone 把正在跑的会话画在锁屏 / 灵动岛上计时;App 被挂起时它自己没机会收尾。所以 App 会把这次活动的推送令牌随注册交给插件,插件存下来、转给中继;会话跑完、且手机上还看得到的会话一个都不在跑时,那条「跑完了」的通知会让中继顺手结束 Live Activity —— 任务结束后计时不会一直涨。
推送要 Apple 的签名密钥,密钥只能放在服务器上(放在插件里就等于公开)。用官方中继什么都不用做;自建中继需要配 apns,见 Relay 仓库的部署文档第 10 节。
四·五、发布(维护者)
发布走 trusted publishing(OIDC):GitHub Actions 用一次性的 OIDC 身份向 npm 换发布凭证,没有长期令牌,也没有验证码要输。发布就是推一个版本 tag。
一次性配置(npmjs.com,用账号的安全密钥验证):
1. 打开 dsh-mizar 包页面 → Settings → Publishing access → Trusted publisher → GitHub Actions;
2. 填:
- Organization or user:ShirazuNagisa
- Repository:DSH-Mizar
- 工作流文件名:publish.yml
- 环境名称:留空
- 允许的操作:勾上 npm publish(直接发布;只勾暂存的话,每次发布还要人工批准);
3. 保存。
之后发布: sh
cd dsh
npm version patch # 或手改 package.json 的 version
git push # 版本号先上 main
git tag vX.Y.Z && git push origin vX.Y.Z
工作流(.github/workflows/publish.yml)会:校验 tag 与 package.json 版本一致 →
npm ci → typecheck → 全部测试 → build → npm publish --provenance。任何一步失败都不会发布。
为什么不用手工 npm publish:这个账号的第二因子是安全密钥(WebAuthn),npm CLI 不支持 WebAuthn,Enter OTP: 永远过不去;trusted publishing 是 npm 对 CI 发布的官方方案,还顺带生成来源证明(provenance)。
手工 npm publish 对这个账号已经不可用:第二因子是安全密钥,npm CLI 没有 WebAuthn 挑战,Enter OTP: 永远过不去。需要手工发布时,先给账号加一个验证器 App(TOTP),或者继续用 tag 工作流。
五、开发 sh
npm install # 依赖(npm cache 建议指到本地:npm_config_cache=$PWD/.devcache/npm)
npm test # 全部测试
npm run typecheck
npm run build # tsc + 浏览器半边;lib/ 是提交进仓库的,必须与全新构建一致
lib/ 故意提交进仓库:dsh plugin add 装的是 git 检出,没有构建步骤,缺了 lib/ 等于插件装不上。所以改完 src/ 一定要 npm run build 再提交。
跑一个真的开发实例(用仓库内的 .devhome,不碰你 ~/.dsh 的正式配置): sh
node scripts/dev.mjs install # 构建并装进 mizar-dev profile
node scripts/dev.mjs boot # 起一个真 DSH;日志 .devstate/dev.log
node scripts/dev.mjs stop
对着它跑一次真实链路的冒烟测试(会打印配对码): sh
MIZAR_CODE=XXXX-XXXX node scripts/smoke-listener.mjs
开发实例不要连中继。 .devhome 是从正式配置里拷出来的,会拿到同一个中继房间号,两边一抢就会把正式那条隧道踢掉 —— .devhome/profiles/mizar-dev/cordis.patch.yml 里已经写了 relay: enabled: false。
归档:archive/full-message-transport/ 是 0.7.2 之前那套「全量传输」的实现,不参与编译*,留着以便以后要用(见该目录的 README)。
常见问题
手机连不上。
面板顶部会写原因。同一个局域网内会优先直连;不在同一个网络时走中继,只要电脑和手机都能上网就行。
面板里没有二维码。
重启 DSH。装完不重启,插件不会加载。
想改成局域网直连 / 为什么没走局域网。
默认就会优先走局域网:App 每次刷新先扫一遍本机网络,能直连就直连,连不上才走中继;二维码和 host.info 里两条路都带着。
如果之前为了「只监听本机」手动配过 bind.mode: loopback 或 relay.preferLoopback: true,把这两项删掉(或改成 bind.mode: lan / preferLoopback: false),重启 DSH 后监听器就会重新绑局域网地址。
插件版本对不对。
DSH 启动日志里有 [mizar] dsh-mizar <版本号> · state: … · devices: N live 这一行。
收不到通知。
按顺序查:App 里「设置 → 通知」有没有允许;插件日志里有没有 push: registered …
(说明手机把推送令牌交上来了);中继上 curl localhost:8787/healthz 的 pushDevices 是不是大于 0。三条都对还是没有,看中继日志里 Apple 回的是什么 —— BadDeviceToken 基本就是环境选错了。
协议、架构决策、自建中继、研究记录:docs/
许可
本项目以 GNU General Public License v3.0(GPLv3,SPDX:GPL-3.0-only)发布。
许可证全文见 LICENSE。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
ShirazuNagisa/DSH-Mizar
远程监控和指导 DSH 智能体——一个 Cordis 插件,支持 macOS 和 Windows,外加一个 Swift 原生 iOS/iPad 应用。
相关插件 · 市场 / 管理
插件精选列表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
这是你的仓库?认领条目