🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

移动端增强套件 hikarioyama/Smart-DSH

DeepSeek Harnessv0.0.3
hikarioyama34 天前有提交已提供中文文档扫描:含敏感能力

Smart-DSH — 移动端 UI、通知,以及 Esc 停止,为 DeepSeek Harness 打造

45.9/100
综合分
32
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/2 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查未通过:仓库 package.json 标记 private,未发布到 npm,需从源码安装;安装清单取自 dsh-btw/package.json。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add hikarioyama/Smart-DSH
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · ui
装得上吗
本站已真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 34 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

仓库 package.json 标记 private,未发布到 npm,需从源码安装;安装清单取自 dsh-btw/package.json

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-btw @ 0.0.3
✓Node 引擎要求 ^22.19.0 || >=24.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 06:33:50

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 52 处,展示前 44 条)
  • 发起网络请求
    dsh-btw/lib/index.js:274
    async fetch(request) {
  • 读取本地文件
    dsh-btw/lib/index.js:322
    text = await readFile(this.filename(sessionId), "utf8");
  • 写入本地文件
    dsh-btw/lib/index.js:344
    await mkdir(dir, {
  • 读取本地文件
    dsh-btw/lib/index.js:359
    if (createHash("sha256").update(await readFile(path)).digest("hex") !== sha256) throw Error("Damaged audit context; refusing reuse");
  • 写入本地文件
    dsh-btw/lib/index.js:386
    await mkdir(this.root, {
  • 发起网络请求
    dsh-notify-push/lib/client.js:78
    const { publicKey } = await fetch(VAPID_URL).then((response) => {
  • 发起网络请求
    dsh-notify-push/lib/client.js:87
    const response = await fetch(SUBSCRIBE_URL, {
  • 发起网络请求
    dsh-notify-push/lib/client.js:104
    await fetch(UNSUBSCRIBE_URL, {
  • 写入本地文件
    dsh-notify-push/lib/index.js:14
    mkdirSync(dirname(path), { recursive: true });
  • 写入本地文件
    dsh-notify-push/lib/index.js:18
    writeFileSync(fd, JSON.stringify(value, null, 2) + "\n", "utf8");
  • 写入本地文件
    dsh-notify-push/lib/index.js:23
    renameSync(tmp, path);
  • 读取本地文件
    dsh-notify-push/lib/index.js:27
    if (!existsSync(path)) return fallback;
  • 读取本地文件
    dsh-notify-push/lib/index.js:29
    return JSON.parse(readFileSync(path, "utf8"));
  • 读取本地文件
    dsh-notify-push/lib/index.js:160
    const swBody = readFileSync(join(PACKAGE_ROOT, "sw", "sw.js"), "utf8");
  • 发起网络请求
    dsh-tasks/lib/client.js:284
    const response = await fetch(`/api/tasks/view?session=${encodeURIComponent(sessionId)}`, { credentials: "same-origin" });
  • 发起网络请求
    dsh-tasks/lib/client.js:300
    const response = await fetch(`/api/tasks/view?session=${encodeURIComponent(sessionId)}`, { credentials: "same-origin" });
  • 发起网络请求
    dsh-tasks/lib/client.js:311
    const response = await fetch(`/api/tasks/sub-model?${params}`, { method: "POST", credentials: "same-origin" });
  • 读取本地文件
    dsh-tasks/lib/index.js:641
    return readFileSync(join(roleDir(), `${role}.md`), "utf8");
  • 写入本地文件
    dsh-tasks/lib/index.js:649
    mkdirSync(dir, { recursive: true, mode: 0o700 });
  • 写入本地文件
    dsh-tasks/lib/index.js:650
    chmodSync(dir, 0o700);
  • 读取本地文件
    dsh-tasks/lib/index.js:652
    if (!existsSync(file)) writeFileSync(file, text, { mode: 0o600 });
  • 写入本地文件
    dsh-tasks/lib/index.js:652
    if (!existsSync(file)) writeFileSync(file, text, { mode: 0o600 });
  • 写入本地文件
    dsh-tasks/lib/index.js:653
    chmodSync(file, 0o600);
  • 写入本地文件
    dsh-tasks/lib/index.js:657
    mkdirSync(home, { recursive: true, mode: 0o700 });
  • 写入本地文件
    dsh-tasks/lib/index.js:658
    chmodSync(home, 0o700);
  • 读取本地文件
    dsh-tasks/lib/index.js:670
    if (!existsSync(file)) return [];
  • 读取本地文件
    dsh-tasks/lib/index.js:672
    for (const line of readFileSync(file, "utf8").split("\n")) {
  • 读取本地文件
    dsh-tasks/lib/index.js:682
    if (!existsSync(file)) return { enabled: false, subModel: null };
  • 读取本地文件
    dsh-tasks/lib/index.js:684
    return readSessionState(JSON.parse(readFileSync(file, "utf8")));
  • 读取本地文件
    opencode-go/apply.mjs:39
    if (!existsSync(link)) continue;
  • 执行外部命令来自配置/变量
    opencode-go/apply.mjs:55
    const gen = spawnSync(process.execPath, [join(HERE, "gen.mjs"), ...(noDeepseek ? ["--no-deepseek"] : []), "--out", genOut], {
  • 读取本地文件
    opencode-go/apply.mjs:79
    const raw = readFileSync(genOut, "utf8");
  • 读取本地文件
    opencode-go/apply.mjs:102
    const settingsDoc = YAML.parseDocument(readFileSync(SETTINGS, "utf8"));
  • 写入本地文件
    opencode-go/apply.mjs:126
    mkdirSync(backupDir, { recursive: true });
  • 写入本地文件
    opencode-go/apply.mjs:127
    copyFileSync(SETTINGS, join(backupDir, "settings.yaml"));
  • 写入本地文件
    opencode-go/apply.mjs:128
    chmodSync(join(backupDir, "settings.yaml"), 0o600);
  • 写入本地文件
    opencode-go/apply.mjs:129
    writeFileSync(join(backupDir, "restore.sh"), `#!/bin/sh
  • 写入本地文件
    opencode-go/apply.mjs:133
    chmodSync(join(backupDir, "restore.sh"), 0o700);
  • 写入本地文件
    opencode-go/apply.mjs:149
    writeFileSync(tmp, settingsDoc.toString());
  • 读取本地文件
    opencode-go/gen.mjs:43
    if (!existsSync(link)) continue;
  • 写入本地文件
    opencode-go/gen.mjs:220
    writeFileSync(OUT, lines.join("\n") + "\n");
  • 读取本地文件
    opencode-go/set-default-model.cjs:15
    const doc = yaml.parseDocument(fs.readFileSync(file, 'utf8'));
  • 写入本地文件
    opencode-go/set-default-model.cjs:17
    fs.writeFileSync(file, doc.toString());
  • 发起网络请求
    patches/dsh-client-hmr-0.1.2-rc.1/client.js:63
    void locks.request("dsh:client-hmr:/plugins/events:v1", { signal: abort.signal }, async () => {
扫描于 2026/11/2(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 移动端友好界面
  • WebPush通知

非官方 DeepSeek Harness 插件:移动端友好界面、Web Push 通知,外加 Linux Tailscale 配置指南

README

由 DeepSeek 最新模型翻译生成
Smart-DSH — 移动端 UI、通知,以及 Esc 停止,为 DeepSeek Harness 打造 非官方 DSH 插件包与 Linux 安装指南,不是 DSH 的分支。 保留上游 DSH 的安装,再加上 Smart-DSH,即可获得: - 移动端 UI: 全宽聊天与输入框,logo 打开会话列表,去掉 logo 提示浮层与点击高亮。 - 通知: 提问和顶层回合结束的通知,走 Web Push。 - Esc 停止: 按 Escape 取消正在运行的回合,可在 Settings → General 里开关。 - 远程访问指南: 用仅限 tailnet 的 Tailscale Serve HTTPS 连接手机。 - 子代理任务委派: 每个角色只配一个专属动词(worker / explorer / hacker / reviewer),一个同级 hub 带持久日志,外加一个子代理会话标签页。 - OpenCode Go 模型目录: 让 llm-pi-ai 的 OpenCode Go 路由始终跟得上订阅实际提供的每个模型(脚本 + 指南)。 - 多标签页可靠性: 一个可选、带防护的 shared-HMR 变通方案,防止上游开发者更新连接耗尽 Firefox 的 HTTP/1.1 连接槽位。 这是非官方社区扩展,与 DeepSeek 无隶属关系。
展开完整文档(README 剩余 1 节)
Smart-DSH — 移动端 UI、通知,以及 Esc 停止,为 DeepSeek Harness 打造

非官方 DSH 插件包与 Linux 安装指南,不是 DSH 的分支。
保留上游 DSH 的安装,再加上 Smart-DSH,即可获得:

- 移动端 UI: 全宽聊天与输入框,logo 打开会话列表,去掉 logo 提示浮层与点击高亮。
- 通知: 提问和顶层回合结束的通知,走 Web Push。
- Esc 停止: 按 Escape 取消正在运行的回合,可在 Settings → General 里开关。
- 远程访问指南: 用仅限 tailnet 的 Tailscale Serve HTTPS 连接手机。
- 子代理任务委派: 每个角色只配一个专属动词(worker / explorer / hacker / reviewer),一个同级 hub 带持久日志,外加一个子代理会话标签页。
- OpenCode Go 模型目录: 让 llm-pi-ai 的 OpenCode Go 路由始终跟得上订阅实际提供的每个模型(脚本 + 指南)。
- 多标签页可靠性: 一个可选、带防护的 shared-HMR 变通方案,防止上游开发者更新连接耗尽 Firefox 的 HTTP/1.1 连接槽位。

这是非官方社区扩展,与 DeepSeek 无隶属关系。
兼容性针对 DSH 0.1.2-rc.1 测试;移动端样式使用版本相关的选择器。回合结束通知描述的是智能体回合终止,并不代表独立验证了每个请求的任务都已成功。

一个自包含的 DeepSeek Harness (DSH) 插件包(dsh-notify-push),外加配套远程访问基础设施(Tailscale Serve + 手机)的安装说明。当智能体调用 ask_user_question 时,你的手机会收到一条 Web Push 通知,里面带着问题文本 —— 即使没有浏览器连着也能收到。回合结束时,你会收到一条完成通知,标题反映结束原因(作業完了 / token 上限截断 / 阻塞 / 错误 / 中止);子代理回合不发通知 —— 只有顶层会话才发。点一下通知即可聚焦到应用。

第二个无依赖的插件包(dsh-esc-stop)补上了输入框停止按钮已有的键盘手势:按 Escape 取消当前正在运行的回合。菜单、对话框、队列消息编辑器、输入法组合状态各自保留自己的 Escape,整个手势在 Settings → General 里只有一行 ON/OFF —— 不用改文件,也不用重启就能切换。见 Esc-to-stop。

状态:在 Arch Linux 上可用的配置,2026-09-07 验证,真实投递到 Android Chrome 和桌面 Firefox。这些安装示例省略了主机相关的标识符。

要求

| 要求 | 原因 |
| DSH 0.1.2-rc.1 | 插件包依赖 webServer.register、connection.requestRejection 和 { prepend: true } 监听器选项 —— 如果你的 DSH 版本不同,请先确认这些是否存在(dsh --version) |
| Node >= 22.19(排除 23) | DSH 自身的要求 |
| pnpm | dsh plugin 只是 pnpm 的薄封装转发 |
| 任何基于 Chromium 的浏览器,或带 PushManager 的 Firefox | 已在 Android Chrome(FCM)和桌面 Firefox(Mozilla autopush)上验证 |
| 手机需要安全上下文 | 通过 Tailscale Serve 走 HTTPS(见下文)——纯 http://<ip>:3080 无法订阅推送 |
| Android:无需额外操作。iOS:添加到主屏幕 | iOS WebKit 只向已安装的 PWA(16.4+)投递推送 |

工作原理

ask_user_question (tool)
└─ host: ctx.on("user-questions/request", listener, { prepend: true }) ← outermost
├─ sendPushToAll() web-push → FCM / Mozilla autopush (fire-and-forget)
└─ return next() delegates to api-remotes forwarder → browser composer
(the waterfall is NEVER consumed)

turn completion (top-level sessions only)
└─ host: ctx.inject(["agents"]) → agentCtx.on("session/event", listener)
├─ filter: agents.roots().includes(agents.get(session.id)) ← subagent turns excluded
└─ sendPushToAll(buildTurnEndPayload(session.id, event.data.reason))
titles by reason.kind: completed / max-tokens / blocked / error / aborted

| 组件 | 文件 | 作用 |
| 宿主侧 | dsh-notify-push/lib/index.js | 前置的 user-questions/request waterfall 监听器 + session/event(turn/end)监听器(仅根会话)+ Web Push 扇出 + HTTP 路由(/api/push/ 经 connection.requestRejection 鉴权,/push/sw.js 为静态资源,带 Service-Worker-Allowed: /) |
| 客户端侧 | dsh-notify-push/lib/client.js | window.__ModuleLoader__.load({...}) 包装;页面存活期间使用本地 Notification;/notify popupSelect 命令,用于申请权限和订阅/退订推送 |
| Service worker | dsh-notify-push/sw/sw.js | push → showNotification(requireInteraction: true),notificationclick → 聚焦/打开窗口 |
| 状态 | $DSH_HOME/notify-push/(0600,不在本仓库中) | vapid.json(首次启动时生成,必须在重启后保留)+ subscriptions.json(收到 404/410 时自动清理) |

状态目录跟随 DSH_HOME(默认 ~/.dsh),所以这里没有任何东西硬编码到某个特定用户。

安装

0. Where to keep the bundle source — any persistent path works; ~/.dsh/profiles/web/bundles-src/ is just what this setup uses. Adjust BUNDLE_SRC freely.
BUNDLE_SRC="$HOME/.dsh/profiles/web/bundles-src/dsh-notify-push"
git clone https://github.com/hikarioyama/Smart-DSH.git /tmp/Smart-DSH
mkdir -p "$(dirname "$BUNDLE_SRC")"
cp -r /tmp/Smart-DSH/dsh-notify-push "$BUNDLE_SRC"

1. The bundle's own dependency ("web-push"). Order relative to step 2 does not matter,
but run this BEFORE the first restart.
cd "$BUNDLE_SRC" && pnpm add web-push@^3.6.7

2. Register into the web profile (adds the dependency as link: AND appends
dsh-notify-push to dsh.profile.bundles via its dsh.bundle.patch declaration)
cd ~/.dsh/profiles/web && dsh plugin --profile web add "$BUNDLE_SRC"

3. 只读校验组合结果(绝不碰正在运行的服务器)

dsh --profile web --dump-config | grep dsh-notify-push # expect: "- id: dsh-notify-push"

4. 校验依赖能否解析(在配置档目录下执行)

cd ~/.dsh/profiles/web && node --input-type=module -e "await import('web-push'); console.log('web-push resolvable')"

依赖说明:dsh 配置档在 pnpm-workspace.yaml 里使用 nodeLinker: hoisted,所以第 1 步的 pnpm add 会装进配置档的 node_modules,并从链接后的 bundle 解析。如果第 4 步报 web-push 无法解析,重跑第 1 步,然后在配置档目录里执行 pnpm install。

接着重启并启用: bash
systemctl --user restart dsh-web.service

In the DSH UI on each device: /notify → ON → grant the notification permission.

预期结果:首次启动时出现 ~/.dsh/notify-push/vapid.json + subscriptions.json;每台启用的设备对应一条订阅;向智能体提问并触发 ask_user_question,每台启用的设备都会收到通知。

新标签页显示「No sessions yet」

在 DSH 0.1.2-rc.1 上,上游 client-hmr 会为每个标签页开一条常驻的开发者更新连接。标签页开得够多,就会耗尽浏览器的 HTTP/1.1 槽位,导致新的会话列表请求和 WebSocket 握手发不出去。这与 dsh-notify-push 通知插件无关,也不代表会话被删了。

Smart-DSH 内置一个带版本与校验和保护的变通方案,让多个标签页共用一条 HMR 连接。在当前检出目录下执行: bash
node scripts/apply-shared-hmr.cjs --check # read-only; resolves the dsh on PATH
node scripts/apply-shared-hmr.cjs --apply # explicit write with a private backup
node --test scripts/test-shared-hmr.cjs scripts/test-apply-shared-hmr.cjs

它不会重启 DSH,也不会改动浏览器偏好或会话数据。版本未知或本地已有改动都会被拒绝。这是独立的可选安装步骤;只复制 dsh-notify-push 不会应用它。具体路径、回滚、浏览器要求、实时更新限制,以及 DSH 升级后如何复查,见变通方案指南。

Esc 停止(dsh-esc-stop)

智能体干活时按 Escape,当前这一轮就被取消——和输入框的 停止生成 按钮是同一种取消。默认开启,开关在 Settings → General → Esc で推論を停止;它的值就是用户设置文档里的宿主设置 esc-stop.enabled,所以跟着账号跨设备同步,切换不需要改文件或重启。

Escape 是共享按键,只要有更近的界面占用了它,监听器就拒绝响应:

| 拒绝情形 | 原因 |
| 按住 Ctrl/Meta/Alt/Shift,或按键重复 | 属于浏览器/系统快捷键,而且长按不能反复触发 |
| 输入法组词中 | 按键归输入法所有 |
| event.defaultPrevented | 已经消费掉 Escape 的处理器优先 |
| 目标是原生 input/textarea/select | 队列消息编辑器会在按 Escape 时自行关闭 |
| 有 [aria-modal="true"] 元素打开 | 设置面板打开期间由它接管 Escape |
| 有 [role="listbox"]/[role="menu"] 元素打开 | 命令菜单、popupSelect 或选择器接管 Escape |
| 开关处于 OFF、没有任务在跑、会话已被移除,或有子代理在台上 | 没有可停止的对象,这些状态下按钮本身也不存在 |

监听器在 document 上以捕获阶段运行,并延迟一个微任务再判断,所以 Escape 绝不会既关闭菜单又停止本轮:更近的处理程序是否消费了按键,或它刚关闭的浮层,都只读取一次——在按键完成路由之后。

接受的路径就是按钮自己的那条(sessions.scope(id).get("conversation").cancel()),所以失败呈现完全一致:消息落入会话的 promptError。Stop 只取消进行中的这一轮——排队的消息保留,并按 FIFO 顺序继续。

安装步骤与 dsh-notify-push 相同,只是换成 dsh-esc-stop,另外多一步注册(该 bundle 自身没有运行时依赖,所以额外的 pnpm add 步骤不适用)。dsh plugin 只是转发给 pnpm,而配置档是 pnpm 工作区根目录,所以依赖需要 -w;层列表直接编辑: bash
BUNDLE_SRC="$HOME/.dsh/profiles/web/bundles-src/dsh-esc-stop"
git clone https://github.com/hikarioyama/Smart-DSH.git /tmp/Smart-DSH
mkdir -p "$(dirname "$BUNDLE_SRC")" && cp -r /tmp/Smart-DSH/dsh-esc-stop "$BUNDLE_SRC"
cd ~/.dsh/profiles/web && dsh plugin --profile web add "$BUNDLE_SRC" -w
node -e 'const fs=require("fs"),p=process.env.HOME+"/.dsh/profiles/web/package.json",m=JSON.parse(fs.readFileSync(p,"utf8")),b=m.dsh.profile.bundles;if(!b.includes("dsh-esc-stop"))b.push("dsh-esc-stop");fs.writeFileSync(p,JSON.stringify(m,null,2)+"\n")'
dsh --profile web --dump-config | grep dsh-esc-stop # read-only composition check
systemctl --user restart dsh-web.service # never from the session being restarted

在重启之前,运行中的服务器保持启动时的组合:设置行和监听器只在重启之后出现。

细节、逐条防护的理由和限制:dsh-esc-stop/README.md。

子代理任务委派(dsh-tasks)

父智能体生成具名子代理,每个只配一个专属动词——worker(只编辑任务指定的路径,然后检查这些路径)、explorer(只测量,从不编辑)、hacker(攻击任务的前提;在 MiL 中恰好给出三个 hack,不做实验)、reviewer(对照任务评判指定的 diff,引用 loc:=path:line)。子代理永远看不到父对话:任务就是全部内容,worker 启动时若没有具体的具名路径,会被 harness 拒绝。

报告通过一个同级 hub 流转,形式是发给活着的……的一行 MiL 事实名单 id;散文式报告会被拒绝(report:=∅ ∵ ¬MiL),子代理会被提醒一次,要求重新陈述。它们会出现在 Web UI 的子代理会话标签页里,任务日志保存在 $DSH_HOME/tasks/ 下。子代理里的 ask_user_question 会被拒绝并记录日志,所以子代理永远不会卡在一个它看不到答案的问题上;子代理的轮次不会发通知。

安装(无运行时依赖——dsh-notify-push 需要的 pnpm add 步骤在这里不适用;-w 标志是因为该配置档是 pnpm workspace 根): bash
BUNDLE_SRC="$HOME/.dsh/profiles/web/bundles-src/dsh-tasks"
git clone https://github.com/hikarioyama/Smart-DSH.git /tmp/Smart-DSH
mkdir -p "$(dirname "$BUNDLE_SRC")" && cp -r /tmp/Smart-DSH/dsh-tasks "$BUNDLE_SRC"
cd ~/.dsh/profiles/web && dsh plugin --profile web add "$BUNDLE_SRC" -w
node -e 'const fs=require("fs"),p=process.env.HOME+"/.dsh/profiles/web/package.json",m=JSON.parse(fs.readFileSync(p,"utf8")),b=m.dsh.profile.bundles;if(!b.includes("dsh-tasks"))b.push("dsh-tasks");fs.writeFileSync(p,JSON.stringify(m,null,2)+"
")'
dsh --profile web --dump-config | grep dsh-tasks # read-only composition check
systemctl --user restart dsh-web.service # never from the session being restarted

单元测试套件(25 个测试:工具策略、MiL 报告器、两个 bundle 部分)通过配置档提升的 @deepseek-ai/ 解析 DSH 导入,所以在注册后从 $BUNDLE_SRC 运行——裸克隆会报 ERR_MODULE_NOT_FOUND。详情见 dsh-tasks/README.md。

OpenCode Go 模型目录(opencode-go/)

DSH 内置 OpenCode Go 服务商,模型列表冻结在目录里,于是订阅实际提供的新模型(以及目录已删除的模型)会逐渐对不上。这个目录是一套小工具,读取实时的 Go 模型列表,并重写 $DSH_HOME/settings.yaml 里的 llm-pi-ai.providers.opencode-go.:

| 文件 | 作用 |
| gen.mjs | 从内置的 pi-ai 目录表生成目录增量(内置目录路由 + 两条仅声明路由,用于目录中未知的 ID) |
| apply.mjs | 通过 YAML Document API 把增量拼接进 settings.yaml(保留注释),并生成 backups/ + restore.sh 快照 |
| verify.sh | 走真实链路探测每个已配置模型(流式 chat/completions,外加 /responses 路由),打印 OK/SKIP/NG 表格 |
| set-default-model.cjs | 测试辅助:在一次性 settings 副本里替换 agent-default-model |
bash
cd opencode-go
node gen.mjs # review the 29-model catalog (3 routes)
node apply.mjs --dry-run # diff only
node apply.mjs # write (live-watched by DSH — no restart)
sh verify.sh # per-model wire probe

以下三点已对照 2026-09-29 的实时端点验证:

- 隐私门控: DeepSeek 系列需要把 workspace Privacy 区域设为 Global,muse-spark- 还需要「trains on request data」同意。两者都在 OpenCode 控制台的 Privacy 页面(导航里没有链接——直接打开 …/console/<org_id>/settings/privacy)。

- 目录中未知的 ID(deepseek-v4.1-flash、mimo-v2.6-、space-bunny-free、gpt-6-luna、grok-4.7、……)无法从冻结的 pi-ai 目录继承,因此它们声明在单独的 opencode-go-completions / opencode-go-responses 路由上,这些路由自带 api/baseURL——主路由必须保持协议混合。
- 已弃用的 ID(kimi-k2.6、omen-alpha、minimax-m2.7)被排除;grok-4.7 以非推理模式运行,因为 xAI 拒绝 reasoning_effort: "none"。

详情:opencode-go/README.md。

配套基础设施(远程访问 + 手机)

最小可复现形式——作者配置里的 flock/guard/URL-file 管道是机器相关的,有意不放进本仓库: ini

~/.config/systemd/user/dsh-web.service (minimal working form)

Adjust the ExecStart path to your dsh install location (which dsh).

[Unit]
Description=DSH web server
After=network.target

[Service]
Environment="DSH_HOME=%h/.dsh"
ExecStart=%h/.local/bin/dsh web --host 127.0.0.1 --port 3080 \
--trusted-host <machine>.<tailnet>.ts.net --no-open
Restart=on-failure

[Install]
WantedBy=default.target
bash

Expose to the tailnet only (current tailscale CLI syntax; run once, persists):

tailscale serve --bg 3080

→ https://<machine>.<tailnet>.ts.net (proxying http://127.0.0.1:3080)

tailscale serve status # verify

配置过程中总结的注意事项:

- DSH 只绑定 loopback;Tailscale Serve 是唯一的外部暴露方式,所以未认证的 /push/sw.js 路由只有 tailnet 设备能访问。
- --trusted-host 的值必须和手机使用的主机名一致,否则浏览器信任围栏会拒绝连接。
- Web 应用已经自带 PWA manifest,所以从主屏安装后,通知点击聚焦路径也能正常工作。
- 如果你还跑了 guard 来防止重复启动 DSH,不要为了测试再启动第二个临时实例——见「在没有实时服务器的情况下测试」。
- tailscale serve 的语法各版本不同(当前命令行是 serve --bg 3080,旧版是 serve https / http://...)——查 tailscale serve --help。

在没有实时服务器的情况下测试

随包附带一套自包含的 node --test 测试(只写入临时 DSH_HOME,不碰真实状态): bash
cd dsh-notify-push && pnpm install && npm test

expect: pass 1 / fail 0

覆盖内容:

- 宿主侧用假的 ctx(ctx.on/effect/inject + webServer.register 收集器 + Readable.from 请求体):路由注册、/api/push/ 上的认证 401/403、订阅校验 + 持久化、waterfall 委托(next() 的值透传),以及 /push/sw.js 上的 Service-Worker-Allowed: / 头。
- 推送发送路径用真实的 ECDH P-256 订阅来验证:加密 + VAPID 签名成功,发送在 DNS 处失败(对无效端点报 ENOTFOUND),这证明整条链路一直到网络层都是通的。
- 客户端那一半没有自动化测试覆盖:手动验证时,用 window.__ModuleLoader__ shim 和桩 ctx 加载 lib/client.js(断言 user-questions/request 监听器把 next() 的返回值透传出去,并且 /notify 的 popupSelect 贡献项完成注册)。

dsh-esc-stop 自带测试套件,它通过同一个 ModuleLoader shim 覆盖了客户端那一半(决策守卫、监听器接线、销毁、设置行): bash
cd dsh-esc-stop && npm install && npm test

expect: tests 14 / pass 14 / fail 0

运维

- 开关:每台设备用 /notify 开/关。撤销浏览器权限 → 下次启动时自动重新订阅会失败,已存的开关会掉到 OFF。
- 重启检查清单:DSH 会话靠只追加日志存活;重启后确认 $DSH_HOME/notify-push/vapid.json 还在(换新密钥会让所有订阅变成孤儿——如果你是有意重置密钥,连 subscriptions.json 一起删掉)。
- 收不到通知时,按顺序排查:(1) vapid.json 熬过了重启,(2) 服务器日志里没有 notify-push 报错,(3) 站点的通知权限是「允许」,且 Chrome 的系统级通知已打开,(4) subscriptions.json 里还有条目。
- 这个 bundle 遵循 dsh.bundle.patch + dsh.client 三层插件模式;最小声明见 dsh-notify-push/cordis.patch.yml 和 package.json。

定制要点

- 语言:通知标题/正文前缀和 /notify 命令文案默认是日文(選択肢、(他 N 件)、通知: ON)。改 lib/index.js 里的 buildPayload,以及 lib/client.js 里的 questionSummary/statusLabel/命令标签。
- VAPID subject:configure() 里的 mailto:root@localhost 是占位符;有些推送服务会对它告警——换成你自己的联系地址。
- 浏览器支持:任何暴露 PushManager 的浏览器都能通过 /notify 的可用性检查;目前只验证过 Android Chrome 和桌面 Firefox。
- sw/sw.js 里的 requireInteraction: true 会让通知停留在屏幕上;想用一闪而过的横幅就把它调低。

KG 节点

镜像本仓库的知识图谱节点: ~/knowledge/nodes/agents/dsh-notify-push-bundle-pattern.md。

移动端布局(DSH 0.1.2-rc.1)

视口宽度不超过 767 CSS 像素时,折叠侧边栏只占 logo 那一角;聊天列使用整个视口宽度。点 DeepSeek logo 一步打开会话列表,而不是先经过中间的图标栏。问题卡片和计划卡片使用列宽(100%),不再用 680px 的内容下限,两侧留 2.5% 内边距,这样窄屏手机不会裁掉任何一边。桌面布局不变。
上游展开的侧边栏面板保持原有行为。CSS-module 选择器这些内容与版本相关:升级 DSH 之后要重新检查。没有改动任何通知逻辑。

针对现有 DSH 服务器做浏览器回归检查(不会启动或重启 DSH):
PLAYWRIGHT_MODULE=/path/to/playwright DSH_LOGIN_URL_FILE=/path/to/private-login-url.txt node scripts/test-mobile-layout.cjs。
使用隔离的浏览器上下文,验证 360/412/767/768/1280 CSS 像素宽度、开关往返切换和清理。登录 URL 文件必须保密,绝不提交。

Esc 停止回归检查

scripts/test-esc-stop.cjs 在运行中的 DSH 页面里驱动随包发布的 dsh-esc-stop 监听器(同样输入私密登录 URL,不重启 DSH): bash
PLAYWRIGHT_MODULE=/path/to/playwright \
DSH_LOGIN_URL_FILE=/path/to/private-login-url.txt \
node scripts/test-esc-stop.cjs

它会断言组合后的服务商 bundle(dsh-api-session-controller、dsh-client-ui-renderer、dsh-client-ui-settings)都已存在,然后针对桩会话派发真实的 KeyboardEvent——运行中的回合取消一次,而空闲、关闭开关、按键重复、修饰键组合、模态框打开、列表浮层打开、原生输入框获得焦点、按键已被消费、销毁以及具名拒绝原因这些情况都不取消。桩会话意味着不会取消任何真实回合;整个 bundle 也会加载进页面,以证明它能解析并注册到 __ModuleLoader__。登录 URL 文件必须保密,绝不提交。
预览分享卡片长什么样 ↓hikarioyama/Smart-DSH
分享 · DeepSeek Harness Hub
hikarioyama/Smart-DSH

Smart-DSH — 移动端 UI、通知,以及 Esc 停止,为 DeepSeek Harness 打造

这是你的仓库?认领条目

回到顶部