Vibe Island — DSH Plugin for DeepSeek Harness
← Plugins V @dsh-external/dsh-vibe-island
Vibe Island DeepSeek Harness Agent notch Dynamic Island: compatible with Mac/Win, with a global Dynamic Island status bar that displays Agent thoughts, tool execution, and sub-agent status in real time
Target profile The plugin will be installed here. Keep web if you are unsure.
web (default) headless Custom…
npx -y @deepseek-ai/dsh plugin --profile web add github:myYangyunfan/dsh-deepisland#b7389a3c87499e9e2a224bcce09aa1af74875187
Copy README Compatibility Versions Compatibility and provenance Vibe Island is published as @dsh-external/dsh-vibe-island and currently resolves to version 1.3.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.
DSH compatibility *
Runtime surfaces web
Release source github
Registry updated 10/14/2026 Versions Show 1 more version Collapse versions README DeepSeek Harness 刘海灵动岛 (VibeIsland)
在 macOS 屏幕顶端的物理刘海 显示 DeepSeek 智能体的实时工作状态:
思考中 / 执行工具 / 等你审批 / 几个子代理在并行。点一下就能跳到对应的对话。
🧠 思考中 · 正在读 SessionJump.swift 12s
⚡ 执行中 · edit NotchMetrics.swift 8s
❓ 等待你确认 · 工具调用待审批 3s
📦 新用户:这样装
详细说明在 swift/INSTALL.md (含不装插件的纯手动路线)。
给维护者:.github/workflows/release.yml 改不动
GitHub 的 Git API 对 .github/workflows/** 写入要求 token 带 workflow scope ,
否则一律返回 404 (刻意不给 403,看起来像「路径不存在」,很容易误判成限流)。
用 gist / read:org / repo 三个 scope 的 token 能把仓库其余 92 个文件全推上去,
唯独这个 workflow 不行。要改它:
gh auth refresh -h github.com -s workflow
⚠️ 顺带记下一个反直觉的事实:GitHub 的这个检查只作用于 tree entry 的 path 。
把文件拆成分层子树(先建 workflows 子树,其 path 只有 release.yml,
再逐层向上用 path: workflows、.github 引用回去),
tree/commit 全部 201 成功 —— 卡在最后一步 PATCH /git/refs/heads/main ,
因为最终 ref 指向的 commit 改变了受保护路径。
所以分层构造绕不开权限,只能拿到 workflow scope。
1. 在 DSH 插件页输入地址安装
打开 DSH Desktop → 插件管理 → 输入:
git+https://github.com/myYangyunfan/dsh-deepisland.git
⚠️ 要完整地址,不能只写仓库名。
写成 myYangyunfan/dsh-deepisland 或 github.com/myYangyunfan/dsh-deepisland
会被当成 npm 包名 去 registry 找 —— 那里没有这个包,必然失败。
下面这几种都能识别,但建议照抄上面那条 :
写法 能不能装 git+https://github.com/myYangyunfan/dsh-deepisland.git✅ 推荐 https://github.com/myYangyunfan/dsh-deepisland.git✅ git+https://github.com/myYangyunfan/dsh-deepisland(无 .git)✅ myYangyunfan/dsh-deepisland❌ 当成 npm 包 github.com/myYangyunfan/dsh-deepisland❌ 当成 npm 包
2. 装完后点「立即启用」(多数情况下不用做 )
装完插件,DSH 会弹一个对话框,上面有个「立即启用」按钮 —— 点它,就完成了。
┌─ 装 @dsh-external/dsh-vibe-island ─────────┐
│ │
│ ✔ 已安装 │
│ │
│ [ 立即启用 ] ← 点这个 │
│ │
│ [ 关闭 ] │
└────────────────────────────────────────────┘
点了「立即启用」就直接跳到第 3 步。 没点、或者点了之后功能没反应,
才需要下面的补登记。
需要补登记时怎么做(两条路,选一条) (可以展开看)
A. 已经有 app(老用户升级、误删重装、手工装过)
点菜单栏图标→ 「⚠️ 插件未启用 · 点此修复…」 。
它只在真需要时出现,会解释状况、让你先退出 DSH(否则退出时会把改动盖掉)、
改前自动备份、写完自证、最后提示重启。做完那一项自动消失。
B. 从仓库下载 zip
解压后双击 scripts/setup.command。macOS 会用终端打开并自动完成登记,
跑完按任意键关窗。它会自己认出 DSH 在跑哪个 profile,不用你填任何东西。
为什么会有这个坑? (可以展开看)
插件管理器的「安装」只做了一半—— 它跑 pnpm add,只写 dependencies,
不碰 dsh.profile.bundles 。而宿主按 bundles 顺序叠加载树,
所以装完不点启用,插件压根不会被加载,而且宿主一点提示都没有
(插件管理器里照样显示「已安装」)。
追到源码,确切位置是这个:
// 插件管理器服务端
if (options?.enabled !== false)
await this.selectBundle(name, true); // ← 真正写 bundles 的那一步
界面的调用是 installBundle(spec, { enabled: false, ... }),
enabled: false 让它跳过了那一步 。
⚠️ 曾在这里写着「官方 CLI 不传这个参数,所以会自动登记(实测装完 bundles 就写好了)」。
该结论已被推翻 (2026-10-10,Windows 11 + DSH 0.2.0-rc.2 实测):
CLI 走同一条 reconcile(),而它依赖「目录已落地」——
pnpm add 没成功时它会抛 cannot resolve profile bundle 而不是补登记。
详见下方「确切原因」一节。
✅ 但现在(peerDependencies 修好之后)实测通了
2026-10-10 在 Windows 11 + DSH 0.2.0-rc.2 上真跑的结果:
dsh plugin --profile web add https://github.com/myYangyunfan/dsh-deepisland.git
→ Progress: resolved 4, reused 3, downloaded 1, added 4, done
deps: {"@dsh-external/dsh-vibe-island":"github:myYangyunfan/dsh-deepisland"}
bundles: 含 @dsh-external/dsh-vibe-island ✅ 自动登记了
import: 成功,35 ms,apply/inject 都在
即:一行命令装完,bundles 自动登记,插件能被加载。 不需要手动跑
register-bundle.mjs —— 但它仍在包里,作为 CLI 失败时的兜底。
⚠️ URL 必须写 https://github.com/...,不要写 git+https://... :
带 git+ 前缀会被 pnpm 转成 git+ssh://git@github.com/...,
走 SSH 通常连不上(实测 exit 128)。
⚠️ 两条附注 (都不是本插件的问题,但会让人以为没装好):
nodeLinker 布局影响最终链接 。profile 若是 isolated 布局(默认),
pnpm 会把包放进 node_modules/.pnpm/... 再建顶层 symlink;
本机 desktop profile 用的是 hoisted(平铺),而 web 是 isolated。
isolated 下偶发出现「实体在 .pnpm 里、顶层 symlink 没建」,
于是宿主 resolveBundleDir 报 cannot resolve profile bundle,
但 pnpm 自己显示 added 4, done。重跑 dsh plugin --profile X install
通常能补上;仍不行就删 pnpm-lock.yaml 强制重解析。
workspace:^ 要求 profile 里有 pnpm workspace 配置 。
没有 pnpm-workspace.yaml 时,pnpm 仍能装上(实测 peerDependencies
原样写进 lockfile),但链接链会断在最后一步 —— 症状同上。
之所以之前跑不通,是 peerDependencies 全写 *:pnpm 会跟着 npm 的
标签走,而这些 包的 停在远古版本( 的
latest = ,DSH 实际用 ),那个远古版的依赖树引用了
→ 整棵解析树 404。
网络不通 / 下载失败时怎么办? (可以展开看)
插件会自己读 HTTPS_PROXY / HTTP_PROXY (Node 的 node:https 默认不读,
只有 curl 会),直连失败时自动改走代理隧道。NO_PROXY 会被尊重。
如果还是失败,诊断日志会写清是直连失败 还是代理失败 :
~/Library/Application Support/DSHNotch/plugin-diagnose.log
每次启动 DSH 都会重写这个文件。想让插件直接跳过下载、自己装 app:
cd <仓库> && swift/build.sh install
装好后插件检测到 /Applications/DSHNotch.app 存在就跳过下载。
3. 重启 DeepSeek Harness
退出应用再打开 (不是关窗口 —— 关窗口 DSH 还活着,插件不会重新启动)。
为什么必须重启:那个 macOS app 是插件自己装 的 ——
只有插件被加载、跑起来,才会去下载安装 app。
不重启,插件根本没运行过,app 也就没装。
重启后你会看到日志里这几行(说明正在装):
[dsh-vibe-island] install: present — /Applications/DSHNotch.app
几秒后屏幕顶端的刘海就出现了。
确认装好了
curl -s http://127.0.0.1:47311/health
看到 "ok":true 和 "keys" 里有 notchEnabled 就对了。详细排查见
swift/INSTALL.md 。
🖥️ 怎么用
操作 效果 鼠标移到刘海 面板展开,显示各对话的实时状态 点某一行对话 直接切到 DSH 里的那个会话 (DSH 在后台也会被拉到前台)点面板其余区域 钉住 / 取消钉住 DSH 设置 → 🏝️ 灵动岛 开关刘海、开关一步跳转、开关空闲收起 app 菜单栏图标 同样能开关刘海
设置改动下一拍就生效 (app 每 250ms 查一次配置),不用重启任何东西。
⚠️ 一键直达依赖上面那个插件。不装插件 app 也能用(手动装一次即可),
但点对话会退回「置前 + 复制标题 + ⌘K 粘贴」。
🪟 Windows 现状
Windows 端的状态栏本体已经补齐 (windows/,Electron 无边框置顶小窗 + 托盘),
并在 Windows 11 真机上跑通过。之前的「Windows 没有状态栏」是接手时的状态,现已不成立。
latest
@deepseek-ai/*
latest
dsh-settings
0.0.1-rc.1
0.2.0-rc.2
从未发布到公共 registry 的 dsh-compact
⚠️ 写版本号是另一个坑 :宿主 evaluatePluginCompatibility 会拿
semver.satisfies(runtimeVersion, range) 判 peer,不满足就整个 bundle 被跳过
且无提示 (装上了但插件永不加载,比装不上更难查)。
正解是 workspace:^ —— 宿主把它当 runtimeVersion 本身比对(恒满足),
pnpm 也知道这是工作区内的包而不去 registry 解析。两边都不吃亏。
界面上本来就有「立即启用」按钮,它调的正是 setBundleEnabled(name, true) ——
所以这不是缺功能,是按钮容易被忽略 。大多数人装完直接关了对话框。
用 dsh --profile X --dump-config 对比过两种状态(同一份 package.json、
同一个 node_modules,只改 bundles ):
dependenciesbundles插件树里 有 无 不出现 有 有 出现(# == @dsh-external/dsh-vibe-island)
dependencies 只负责把文件装到 node_modules,对加载毫无影响 。
setup.command 优先调官方 CLI 的完整 cycle(remove → add,
因为宿主 CLI 内部的 bundles 同步只处理本次新增 的依赖),
跑完自己核对结果,失败才回退到手改 manifest。
屏幕顶端的状态栏本体 ✅ DSHNotch.app(NSPanel,物理刘海) ✅ DSHNotch.exe(Electron 无边框置顶小窗,屏顶居中) 托盘入口 ➖(屏顶常驻,不需要) ✅ 显示/隐藏、开机自启、桥状态、打开配置目录、退出 插件设置面板 ✅ ✅(纯 DOM,跨平台) 点岛跳会话(本机桥) ✅ ✅(node:http 回环,跨平台;DSH 侧的真实切换待端到端验证) 缺 app 自动安装 ✅ ✅(下载 zip → 校 SHA256 → 装到 %LOCALAPPDATA%\Programs\DSHNotch)
为什么是 Electron 而不是原生 Win32 Windows 上没有 NSPanel 的等价物。手写原生要处理 WS_EX_LAYERED /
WS_EX_TRANSPARENT / SetWindowPos(HWND_TOPMOST) / 点击穿透 / DPI 缩放 /
托盘图标与右键菜单,每处都有坑。Electron 几十行就能拿到同样的效果,
而且 DSH 自己就是 Electron ,不会出现「两个 GUI 框架抢焦点」。
顺带解决一个硬门槛:会话文件是 zstd 压缩的,解码要靠 Node 内置的
zlib.zstdDecompressSync —— Electron 33(Node 20)没有这个函数,
所以 Windows 端锁 Electron ≥ 42 (现用 44.7.0 / Node 24.21.0)。
代价是体积:安装包 111 MB、zip 153 MB(macOS 版 1.2 MB)。
与 macOS 版共享什么 同一份配置协议 client-config.json(Windows 在 %APPDATA%\DSHNotch\)、
同一个本机桥 127.0.0.1:47311、同一个数据源
~/.dsh/sessions/**/session.v4.jsonl.zstd。
所以设置面板两边通用,配置跨机器同步也是自然的。
限制(都在 windows/README.md 里写全了)
置前 DSH 降级为「复制标题 + 提示 Ctrl+K」:dsh://open 在 Windows 上
是否注册过尚未实测 ,且 Windows 没有 CGEvent 那种按键注入能力
状态判定 靠投影侧信号 + 「文件刚被写过」这个启发式 —— 真实事件流里
type 只有 session 一种,没有 tool/call 之类(174 份真实投影实测)
自动安装的下载环节没真跑过 已闭环 :v1.2.0 起 Release 带齐双平台资产,
下载链路在真机上用 scripts/verify-install-chain.mjs 走通过
(解析 latest → 取 SHA256SUMS → 下 zip → 核哈希,四步全过)。
CI 侧另有 verify-release 闸门 job:每次发布后重新下载全部资产核摘要,
缺任何一个平台的行都会把发布打红
测试套件本身也有平台坑,已修 :判平台相关的断言以前要么写死 macOS 路径
(/usr/bin/ps、/usr/bin/curl、/usr/bin/ditto、/usr/bin/file),
要么依赖 process.platform 而在 Windows 上走了另一条分支。后果是
node test/run.mjs installdl 在 Windows 上直接崩 (spawnSync ... EBUSY),
而 T1/T2 会拿 macOS 的 .app 夹具去跑 Windows 安装路径、报出一堆假红。
现在改成:命令走 PATH 解析(找不到就如实报告,不崩)、二进制架构自己读文件头
(Mach-O / PE)、installdl 的 T1–T4 全部显式钉 platform:'darwin' +
nodeArch。Windows 上的沙箱仍会拒起 curl.exe(EBUSY) ,
这一条会诚实地红成「本机验不了下载链路」,而不是崩。
✨ 它能看到什么
💭 思考中 —— 正在分析什么(取最近的分析要点)
⚡ 执行中 —— 正在跑哪个工具、动哪个文件
❓ 等待你确认 —— 工具调用待审批,会脉冲提示
👥 子代理并行 —— 同时几个子代理在跑(子代理实现为独立会话,从会话谱系读)
多会话同时跟随 —— DSH 能并行开好几个会话,折叠态显示最该关注的那个 + 徽标,
展开态每个对话各占一行,按「谁最需要你介入」排序(最多 6 个)
判定逻辑全部在 swift/Sources/DSHNotch/Core/ 里,事件流增量扫描、
有游标缓存,64000 条事件时每轮开销 0.0004ms。
🖥️ 状态栏本体:两个平台的两个 app 平台 形态 位置 macOS DSHNotch.app(Swift / NSPanel)屏幕顶端的物理刘海 Windows DSHNotch.exe(Electron 无边框置顶小窗 + 托盘)屏幕顶端居中
插件(lib/)不再往 DSH 窗口里画东西了。它保留三件事,缺一不可:
能力 由谁提供 设置面板(开关物理刘海、跳转、空闲收起) 插件客户端(lib/client.js) 点岛上的对话 → 打开对应会话 插件服务端(lib/index.js)的本机桥 缺 app 时自动下载安装 插件服务端(lib/index.js)
也就是说:插件是刘海的"控制面 + 通道",刘海本身是 app。
不装插件也能用(手动装 app 即可),但会失去「一步跳转」与「自动安装」。
🗑️ 为什么删掉窗口内那个岛
设置面板控制的就是物理刘海 DSH 设置面板 ──POST /config──▶ 插件服务端(Node)──▶ client-config.json
│
DSHNotch.app(Swift)◀── 读同一份 ◀┘
面板开关 键 效果 启用刘海灵动岛 notchEnabled物理刘海显示 / 隐藏 点对话直接跳转到该会话 jumpEnabled关掉则退回「置前 + ⌘K 粘贴」 空闲时自动收起 hideWhenIdle同步到 app
两个方向都能改 —— app 的菜单栏也有「显示/隐藏刘海灵动岛」,写同一份文件,
不会出现「面板说开着、菜单说关着」。
app 每 250ms 检查一次配置(靠文件 mtime + 大小 判变化,没变就返回缓存值),
所以设置点完下一拍就生效 ,不用重启任何东西。
不打开 DSH 也能开关(手动 / 排查用)
# 显示 / 隐藏物理刘海
curl -s -X POST -H 'content-type: application/json' \
-d '{"notchEnabled":false}' http://127.0.0.1:47311/config
# 查当前配置
curl -s http://127.0.0.1:47311/config
配置落在 ~/Library/Application Support/DSHNotch/client-config.json,
可以直接看。写接口有白名单 (只接受已知键与类型)—— 这是本机回环上的
HTTP 端点,同机任何进程都能调,不校验就等于开了个任意写的口子;
被拒的键会在响应里如实回报,不会静默丢弃。
DSH Notch 怎么用(系统级那个) 操作 结果 鼠标移到刘海上(停留约 0.2 秒) 展开成面板(440 宽;高 176,多会话时按行数长到 235) 鼠标移开(超过 0.35 秒) 自动收起 在岛上点一下(折叠态,或展开态的非对话区) 钉住常驻展开/再点一下取消 展开态点某一行对话(单会话时点中间详情块) 直接跳到 DSH 里的那个对话 (装了插件时;否则降级为置前 + 复制标题)。DSH 在后台也会被拉到前台状态变橙(等待人工确认) 自动展开提醒 菜单栏波形图标 使用说明 / 钉住 / 悬停展开开关 / 空闲自动收起 / 开机自启 / 授予辅助功能权限(自动定位对话) / 定位会话文件 / 导出预览图 / 退出
同时跟随最多 6 个对话。 DSH 并行开多个会话时(本机实测同一分钟有两个在写文件),
折叠态显示最该关注的那个 + 会话数徽标 ⚏ 2/3,展开态每个对话各占一行,
按「等人工确认 > 执行工具 > 思考 > 已完成 > 待命」排序——任一会话等人确认都会自动展开。
🔗 点一下对话就跳过去:插件桥 点展开态里的某一行,DSH 会直接切到那个会话 ——不用搜、不用粘贴、不用按回车,
也不需要辅助功能权限 。
为什么以前做不到 DSH 对外只注册了一条深链 dsh://open,作用仅是把主窗口拉到前台:
app.on("open-url", (event, url) => {
event.preventDefault();
if (url === "dsh://open" || url === "dsh://open/") focusPrimaryWindow();
});
没有「打开第 N 个对话」的对外接口。所以上一版只能绕:置前 DSH + 把标题写进剪贴板,
让你 ⌘K 再 ⌘V,有辅助功能权限时替你按完 ⌘K 和粘贴。
桥是怎么搭起来的 关键发现:插件的渲染进程能打开会话,外部 app 不能。
宿主 asar 里 ctx.uiWorkspace.openSession(target) 有 9 处真实调用,官方注释写着
「target 可以是已知的 Session id」。于是让插件的服务端半边在回环上起一个小 HTTP 服务,
两端各接一半:
点击 island row
│
▼
┌─────────────────────────┐ ┌──────────────────────────────────┐
│ DSHNotch.app (Swift) │ POST │ DSH 插件服务端 lib/index.js │
│ SessionJump.postToBridge│ ──────► │ 127.0.0.1:47311 /jump │
└─────────────────────────┘ │ 队列(上限 32,45s 过期) │
│ └──────────────────────────────────┘
│ 读 bridge.json 拿端口 ▲ 轮询 GET /next(500ms)
│ │
│ ┌──────────────────────────────────┐
└────────────────────────── │ DSH 插件客户端 lib/client.js │
│ ctx.get("uiWorkspace") │
│ .openSession(sessionId) │
└──────────────────────────────────┘
会话 id 从会话文件里取({"type":"session","id":"session-<uuid>"},与目录名一致),
正是 openSession 要的形态。
为什么渲染进程能连本机 HTTP
主窗口没有 CSP。 asar 里 10 处 Content-Security-Policy 分属 API 文件服务
(sandbox; default-src 'none')、HTML 预览消毒、更新/欢迎/强制更新弹窗,
没有一条管到 dsh-app://app 主界面。
Electron 启动参数含 --disable-features=PrivateNetworkAccessChecks, LocalNetworkAccessChecks,本机网络访问的额外预检被关掉了。
协议注册为 --cors-schemes=dsh-app,跨源走 CORS,所以桥的响应都带
Access-Control-Allow-Origin: *。
DSH 在后台也能置前 点击时先发 dsh://open 置前,再投桥切会话 。顺序是有讲究的:
openSession 只换视图、不管窗口焦点 —— 宿主文档原话是
"synchronously replaces the owned mainView reference" 。
DSH 在后台时若只投桥,会话确实切了,但窗口还在后台,用户看不见,等于没点。
dsh://open 是唯一能把窗口捞到前台的入口(它内部调 focusPrimaryWindow())。
反过来说,置前放在前面也更自然:窗口先到前台,用户看到的已经是一个切好的会话,
而不是「切完了再被拉过来」。
为什么设置里的开关以前「点不动」 如果你曾经看到「启用灵动岛状态栏」点了没反应,那不是 UI 坏了,是写入通道断了 。
设置卡片原本只有一条写配置的路:scope.update(...)。而 scope 来自宿主的
configForms 服务,它由 @deepseek-ai/dsh-client-ui-settings 提供 ——
用户可以在自己 profile 的 cordis.patch.yml 里把它关掉 :
- id: ui-settings
name: "@deepseek-ai/dsh-client-ui-settings"
config:
enabled: false
那个 bundle 带首次引导流程,很多人会关掉(本机 desktop profile 就是)。
一旦关掉,链条是这样断的:
bindConfigScope 拿不到 configForms → 返回 null
updateField 写的是 if (scope && …) scope.update(…) → 静默什么都不做
checkbox 是受控组件(checked: config.enabled),状态没变
→ 勾号不弹、岛不消失,也没有任何报错
通道 生效范围 说明 宿主 configForms 当前 profile 权威源,能用就用 桥上的 /config 跨重启、DSH 升级也不丢 服务端落盘到 client-config.json localStorage本次窗口 桥不在时的兜底
并且每次改动都会在面板上显示「已保存」或失败原因 —— 不再静默失败。
读配置时本地值是基线、宿主值覆盖它,所以宿主服务缺席时用户上次的选择依然生效。
落盘走的是白名单 (只接受已知键与类型)+ 原子替换 ,因为这是本机回环上的
HTTP 端点,同机任何进程都能调 —— 不校验就等于开了个任意写的口子。
curl -s http://127.0.0.1:47311/config
cat ~/Library/Application\ Support/DSHNotch/client-config.json
排查时踩的一个坑:日志目录里若只有旧文件,别据此断言「插件没激活」。
要看桥的 pid 有没有变 —— 改了插件代码但没重启宿主时,桥还是老进程,
新加的端点会返回 not found。这是「代码对但没生效」的常见误判,
我自己就差点据此得出错误结论。
点击热区的分工 点哪里 折叠态 展开态 对话详情块 / 某一行的对话 钉住 跳到该对话 面板其余区域(标题、待办、统计、模型行) 钉住 钉住 / 取消钉住
单会话面板没有做成「整块可点」 —— 那会抢掉外层「点一下 = 钉住/取消」的手势:
SwiftUI 里子视图手势优先于父视图,一旦整块接管,展开态就再也钉不住了
(本项目真踩过,--self-test 当场报「展开态下点击 → 仍应钉住」失败)。
折叠态本来没有对话可跳,点一下必须是钉住;两者要共存,
「跳转」就必须有明确的热区。
多会话时底部统计行也能点 —— 它显示的就是主会话(排序第一)的指标,不歧义。
桥坏了会怎样 降级,不影响灵动岛。 桥只在 127.0.0.1 上、只服务同一个用户、只传 sessionId。
用户没装插件、DSH 没开、插件版本旧 —— 全部退回「置前 + 复制标题 +(有权限则)⌘K ⌘V」,
和在装桥之前完全一样。/jump 与 /next 的响应都回显插件名,
免得 47311 附近有别的服务时被误认。
想关掉:设置里 bridgeEnabled: false。
🔴 两条不能碰的纪律
uiWorkspace 绝不能写进 exports.inject。 Cordis 会为缺失的服务无限等待,
插件永久停在 pending (waiting for service: uiWorkspace),整个灵动岛直接消失。
本项目早前把不存在的 settingsScope 写进 inject 就踩过这个坑
(证据在 ~/Library/Logs/DeepSeek Harness/crash-*-web-boot.log)。
现在一律用 ctx.get("uiWorkspace") 运行时探测,取不到就安静跳过这一跳。
/next 取队列前必须先 prune。 少这一步,一条十分钟前的点击会在你下次
打开 DSH 时把你劫持到某个会话。队列项 45s 过期。
📥 只装插件就够了(但要登记一次)
一、自动把 macOS app 装好
插件服务端检查 /Applications/DSHNotch.app
(判据是主程序和 自带的 zstdlite 都在——只看 .app 目录是不够的,
早期就出过「包在但读不到任何会话」的事故)
缺了就从本仓库 Release 下载 DSHNotch-<版本>-<架构>.zip
(直连优先,gh-proxy.com 镜像兜底)
校验 SHA256 ,对不上就中止安装
ditto 解压(保可执行位与代码签名,unzip 会丢)、清 quarantine
装进 /Applications(不可写则退 ~/Applications)并启动
请知情:它会下载并运行一个外部程序
只从本仓库的 Release 下载,且必须 SHA256 对得上 才会执行
绝不覆盖已安装的版本。 已装 → 一个字节都不动;装了但残缺 → 报 broken 并
让你自己处理,绝不悄悄替换
只装缺失的,不做升级。升级仍按 swift/INSTALL.md 手动来
每一步都打到 DSH 日志(搜 [dsh-vibe-island])
关掉:设置里 autoInstallApp: false,然后按 swift/INSTALL.md 手动装。
二、起本机跳转桥
🧪 DSH Notch 怎么用(系统级那个) 操作 结果 鼠标移到刘海上(停留约 0.2 秒) 展开成面板(440 宽;高 176,多会话时按行数长到 235) 鼠标移开(超过 0.35 秒) 自动收起 在岛上点一下(折叠态,或展开态的非对话区) 钉住常驻展开/再点一下取消 展开态点某一行对话(单会话时点中间正文块) 跳到 DSH 里的那个对话:DSH 置前 + 该会话标题进剪贴板 状态变橙(等待人工确认) 自动展开提醒 菜单栏波形图标 使用说明 / 钉住 / 悬停展开开关 / 空闲自动收起 / 开机自启 / 授予辅助功能权限(自动定位对话) / 定位会话文件 / 导出预览图 / 退出
同时跟随最多 6 个对话。 DSH 并行开多个会话时(本机实测同一分钟有两个在写文件),
折叠态显示最该关注的那个 + 会话数徽标 ⚏ 2/3,展开态每个对话各占一行,
按「等人工确认 > 执行工具 > 思考 > 已完成 > 待命」排序——任一会话等人确认都会自动展开。
安装(别人要装到自己机器上,看 swift/INSTALL.md ——
那里写了「下载现成包」和「从源码编译」两条路,含 Gatekeeper 放行;
装了插件的话 app 会自动装好 ,一般不用手动走):
cd swift
./build.sh install # 编译 + 打包 + 装到 /Applications 并启动
./build.sh package # 产出可分发 zip(.build/dist/,附 SHA256SUMS.txt)
./build.sh all-tests # 交互 / 投影 / 多会话 / 跳转 四套自检
./build.sh preview # 离屏渲染形状预览图(校验形状读起来像不像刘海延伸)
📁 插件工程结构 deepisland/
├── dsh.plugin.json # 插件核心元数据声明(id, version, main)
├── package.json # 客户端注入与宿主共享模块声明
├── cordis.patch.yml # DSH Cordis 插件栈自动挂载
├── lib/
│ ├── index.js # 服务端 Cordis 插件:配置注册 + 本机跳转桥 + app 自动安装
│ └── client.js # 客户端核心 Bundle(UI、CSS、事件扫描、桥客户端、设置项)
├── test/ # 零依赖测试套件(Node 断言 + 真实浏览器渲染验证)
└── README.md # 插件使用与安装文档
测试 node test/run.mjs all # 325 项 Node 断言:注册链路、容错、事件扫描、子代理、跳转桥两端、配置持久化
node test/run.mjs installdl # 15 项:T1–T4 离线(15 项),T5 要联网真下载 Release 验 SHA256(约 30s,不进 all)
node test/run.mjs cfg # 配置读写的离线语义(默认值 / 坏文件 / 缓存失效 / 白名单)
套件 断言 覆盖 contract57 宿主契约(配置命名空间、inject 纪律等) parse / apply / subagent / mount124 事件流解析、挂载、子代理聚合 bridge60 服务端桥 :起停、CORS、队列语义、TTL、端口顺延、app 完整性判定bridgeclient32 客户端桥 :端到端 POST /jump → 轮询 → openSession、降级、停用纪律installwin39 Windows 自动安装决策 :判据、启动纪律、残包不覆盖、架构守卫、预演、包结构定位installdl15 自动安装 :不覆盖已装、残缺包不替换、平台/架构守卫、真下载 + 校验 + 篡改检测(需联网)
跨语言那一段(Swift URLSession → Node 桥)由 ./swift/.build/DSHNotch --self-test-jump
在桥跑着时验证,会打印「投递调用正常返回(通=true)」。
两套测试均不依赖任何 npm 包。Node 套件通过桩 window.__ModuleLoader__ / document 拉起插件 factory 并灌入真实事件流;渲染套件用 CDP 驱动本机 Edge/Chrome,加载从 lib/client.js 抽取的真实 CSS,断言实际生效的计算样式。
桥的两套测试用 node:http 而不是 fetch 发请求:作者本机的执行沙箱允许 listen 回环,
但拦截 fetch 到 127.0.0.1。这只影响测试环境 ——桥的客户端在 DSH 渲染进程里用的是浏览器 fetch。
🚀 安装与导入方式(Direct Import) 本插件遵循 DeepSeek Harness 标准插件规范,可通过以下两种方式直接导入:
方式一:通过 DSH 插件管理器安装(推荐) DSH Desktop 内置插件管理器(@deepseek-ai/dsh-client-ui-plugin-manager,随 0.2.0-rc.2 提供),
安装入口是输入「包名或地址」 (pnpm 接受的写法都行),不是选择压缩包:
git+https://github.com/myYangyunfan/dsh-deepisland.git
装完还有一步登记(见下面的「还差一步」),然后重启 DSH。实测的三条限制:
一次只能装一个 —— 对话框一次跑一条 pnpm 命令,第二个 spec 要等前一个完成
没有版本选择器 —— 不列注册表版本、不提供升级;profile 装的插件升级=卸载后重装
不透明失败 —— 出错的行只显示失败,具体原因在 Host 日志里
装到哪个 profile 要看 DSH 实际在跑哪个:插件宿主进程的启动参数里第 4 段就是
profile 路径(ps -ax | grep dsh-desktop-host)。本机是 desktop。
🔴 还差一步:把插件登记进 dsh.profile.bundles 插件管理器的「安装」只做了一半。 光装完不生效 —— 这是个真坑,
而且宿主一点提示都没有 :插件管理器里照样显示「已安装」,功能毫无反应。
Bundles are npm packages whose manifest declares "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }; the tree is composed by applying each bundle's patch
lists in dsh.profile.bundles order over an empty entry list, then the
profile's own patches.
pnpm add 只写 dependencies,不碰 dsh.profile.bundles 。这俩是独立字段
(initProfile 里 dependencies: {} 和 bundles: [...bundles] 各自初始化,
无任何自动合并)。所以装完插件,它在 dependencies 里躺着,却不在 bundles 里 ——
树按 bundles 顺序叠,它压根不会被加载。
确切原因:界面传了 enabled: false 真正写 bundles 的是 selectBundle(name, true),而插件管理器的服务端实现是:
return this.configure(async () => {
if (options?.enabled !== false)
await this.selectBundle(name, true); // ← 这一步被跳过了
...
});
界面调用时传的是 installBundle(spec, { enabled: false, ... }),
于是跳过了写 bundles 。
⚠️ 本段曾写着「官方 CLI 不传 enabled,所以 dsh plugin add 会自动登记」。
那是错的,2026-10-10 在 Windows 11 + DSH 0.2.0-rc.2 上实测推翻。
真实现(读 dsh-plugin-manager/lib/types/operations.js 的 reconcile()):
CLI 装完同样 要写 bundles,它对每个 dependency 调 bundleManifest(),
而 bundleManifest → resolveBundleDir() 在目录不存在时直接 throw 。
于是一条 pnpm add 没成功的安装(dependencies 已写、目录没落地)会让
reconcile 抛 cannot resolve profile bundle,命令以失败告终,
留下「deps 有 / bundles 无 / 目录无」的半吊状态。
更要紧的是 bundleManifest 也是读取 profile 时的路径 ——
这个半吊状态有可能让下一次启动 DSH 直接抛异常,而不只是「插件没生效」。
所以:跑 register-bundle.mjs 不是可选的保险,是必需的收尾。
实测还发现另一件更要紧的事:这台机器上两种安装方式都装不进去 ——
pnpm add <本地路径> 与 pnpm add <git 地址> 均失败,
后者报 [ERR_PNPM_FETCH_404] @deepseek-ai/dsh-compact: Not Found
(该包不在公共 npm registry)。详见下方「安装为什么可能失败」。
而且界面上本来就有「立即启用」按钮 (装完的对话框里)——
它调的就是 setBundleEnabled(name, true),等于补登记。
大多数人装完直接关了对话框,所以没点上。
如果你装的时候看到了「立即启用」,点它就够了,第二步不用做 。
第二步是给没看见那个按钮的人准备的等价替代。
还有个细节值得记(它也解释了为什么直接 add 没用):
for (const name of dependencies) {
if (beforeDeps.has(name)) continue; // 只处理「本次新增」的依赖
…
bundles.push(name);
}
同步只覆盖「本次新增」的依赖。 对一个「已在 dependencies、但不在 bundles」的插件
直接 add,pnpm 会答 Already up to date,同步那一步根本不执行。
所以 setup.command 走的是官方完整 cycle:remove → add。
顺带更正另一处:宿主确实有 reportSkippedBundles 会往 stderr 打印跳过原因(实测有输出),
但它只列**「在 bundles 里、却加载失败」的条目。本插件是 压根不在 bundles 里**,
连「被跳过」都算不上 → 不在跳过列表、不影响别的 bundle、一点提示都没有 。
怎么补这一步 双击一个文件就行 (脚本会自己找 profile、自己备份、出错会解释):
从 GitHub 下载 zip 解压后双击它,或从仓库里双击。跑完重启 DSH Desktop 即可。
或者在终端里跑(等价,二选一)
# 你手上没有仓库也行 —— 脚本随插件装好了,路径是现成的:
node "$HOME/.dsh/profiles/desktop/node_modules/@dsh-external/dsh-vibe-island/scripts/register-bundle.mjs"
# 只想看状态不改:
node "$HOME/.dsh/profiles/desktop/node_modules/@dsh-external/dsh-vibe-island/scripts/register-bundle.mjs" --check
# 摘掉(依赖保留)
node "$HOME/.dsh/profiles/desktop/node_modules/@dsh-external/dsh-vibe-island/scripts/register-bundle.mjs" --remove
已有仓库的话也可以 node scripts/register-bundle.mjs。
profile 名会从 DSH 宿主进程的命令行里自动读出来,不用手填。
试过让 pnpm 自动登记,行不通(实测记录) 免得后人再试一遍。pnpm 确实会为 git 依赖跑 prepare 脚本 (实测会执行),
看起来像是能自动登记的正路,但有两个各自致命的坑:
默认被供应链安全挡住 。pnpm 11 要求先把它加进 allowBuilds 才跑:
ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED: … is not in the "allowBuilds" allowlist
而白名单要写精确到 commit hash 的 spec
("@作者/包@git+…#<40位hash>": true)—— 意味着每装一个版本、每升级一次,
用户都得手动改一次 pnpm-workspace.yaml。比双击一个文件更麻烦。
就算放行了也会被覆盖 。pnpm 的顺序是
先跑 git 依赖的 prepare → 然后才写 profile 的 package.json 。
实测 prepare 脚本自报「成功登记」,但它写进 manifest 的那一项
被 pnpm 随后的写入盖掉了,bundles 里根本没有。
两条叠起来这条路彻底没戏(但官方 CLI 那条是通的 ,见上 ——
它不依赖 prepare,而是在 pnpm 跑完之后自己写 manifest)。
宿主侧没有别的钩子:
normalizeShippedProfile 只在 profile 仍是出厂默认组合时重置 bundles(与装新插件无关);
dsh --profile desktop … 被 Electron 独占,CLI 进不去这个 profile。
它也是不能由插件自己搞定 的:挂载发生在读 manifest 之前 ,
bundle 没进列表 → 它的 apply() 根本不会被调用 → 也就没有机会去注册桥、
去装 app。鸡生蛋,只能由 profile 配置解开。
顺带说明:宿主里 dsh.plugin.json 根本不被读 (asar 里出现 0 次),
那是给人看的说明文件;engines 也不参与跳过判断。
bundle 唯一会被跳过的原因是 peerDependencies 里名字以
@deepseek-ai/dsh- 开头的包不满足 semver —— 本插件这些都写 *,
任意宿主版本都通过,不会成为跳过原因。
改完必须重启 DSH Desktop 才生效。 之后确认:
node "$HOME/.dsh/profiles/desktop/node_modules/@dsh-external/dsh-vibe-island/scripts/register-bundle.mjs" --check
# 三项应全绿
cat ~/Library/Application\ Support/DSHNotch/bridge.json # 桥起来了会出现
装完就有的东西 能力 由谁提供 物理刘海灵动岛本身 DSHNotch.app(原生 NSPanel,swift/ )设置面板(开关刘海 / 跳转 / 空闲收起) 插件客户端(lib/client.js) 点对话直接跳转 + 自动装 macOS app 插件服务端(lib/index.js)
⚙️ 配置说明 在 DeepSeek Harness 设置中,可自定义以下属性:
配置项 键名 默认值 说明 刘海开关 notchEnabledtrue物理刘海是否显示 (设置面板的主开关,也是这里的主角)一步跳转 jumpEnabledtrue点岛上的对话直接切到该会话。关掉则退回「置前 + 复制标题 + ⌘K 粘贴」 空闲收起 hideWhenIdlefalse空闲一段时间后自动收起刘海 显示位置 placementnotchnotch(顶端吸附) / floating(居中悬浮) / top-right(右上角)平台风格 platformModeautoauto(系统自适应) / macos(黑晶刘海) / windows(亚克力毛玻璃)呼吸光晕 glowEffecttrue是否在 Agent 思考与执行时开启流光光晕动效 悬停展开 expandOnHovertrue鼠标悬停在胶囊上时自动展开 HUD 控制台 缩放比例 scale1.0灵动岛尺寸缩放(范围 0.8 ~ 1.3) 跳转桥 bridgeEnabledtrue是否起本机回环桥(一步跳转的通道)。桥挂了只影响跳转,刘海照常显示 桥端口 bridgePort47311本机回环端口。被占用时自动向后顺延,实际端口写进 bridge.json 自动装 app autoInstallApptruemacOS 上缺 DSHNotch.app 时自动下载校验并安装。只装缺失的,不做升级 安装目录 appInstallDir/Applications不可写时自动退到 ~/Applications 子代理计数 showSubagentCounttrue有并行子代理时在岛体右侧显示微章计数
配置写在 profile 的 cordis.patch.yml 里,形如:
- id: vibe-island
name: '@dsh-external/dsh-vibe-island'
config:
bridgeEnabled: true
autoInstallApp: false # 不想让插件自动装 app 就关掉
🛡️ 架构与性能保障
零外部运行时依赖 :直接复用宿主环境已注入的 React 18 与微内核总线,体积仅数十 KB。
只追加扫描缓存 :按会话 id 保存事件流扫描游标(容量上限 64,超出自动回收),只增量处理最新发生的事件。解析开销与历史长度无关——实测 64000 条事件时每轮仅 0.0004ms,长会话下轮询占用主线程不到 0.1%。
子代理零额外数据通道 :内核把子代理实现为独立会话,插件只读客户端已有的会话谱系快照,不新增任何后端请求。
沙箱隔离样式 :所有 CSS 类名带有专用命名空间(vibe-island-*),不会对 DSH 主题样式产生冲突或污染。
插件加载永不被打崩 :服务端 apply() 里注册配置、起桥、装 app 三件事各自独立 try/catch,
全部失败也只记一行日志。配置服务缺失、register 抛重复注册、运行环境没有 node:http
都有对应用例(见 test-bridge.mjs T8)。
桥的暴露面极小 :只监听 127.0.0.1(不绑 0.0.0.0)、只服务同一用户、
只传 sessionId 与标题、队列上限 32 条且 45s 过期、响应回显插件名以防误认。
下载即校验 :自动安装只在 SHA256 与 Release 发布的 SHA256SUMS.txt 一致时才执行,
且绝不覆盖已安装的版本。TLS 证书校验保持开启——这是「校验过再装」的前提,
不要为了跑通而关掉。