一个住在自己手机里的角色,为 DeepSeek Harnessdsh而生。她有自己的 Android…
31.5/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/11/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add Kevinwu901113/dsh-dot该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · platform
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 0 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 141 处,展示前 102 条)
- 发起网络请求lib/client.js:59
const res = await fetch("/dsh-dot/computer/status", { - 发起网络请求lib/client.js:91
return (await (await fetch(`${phoneLiveViewUrl(endpoint)}status`, { - 发起网络请求lib/client.js:111
return (await fetch(`${panelUrl()}api/state`, { - 读取本地文件src/computer/vm.js:57
try { return existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : {}; } catch { return {}; } - 读取本地文件src/computer/vm.js:57
try { return existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : {}; } catch { return {}; } - 删除本地文件src/computer/vm.js:66
try { if (statSync(file).size > 1_000_000) rmSync(file, { force: true }); } catch {} - 读取本地文件src/computer/vm.js:66
try { if (statSync(file).size > 1_000_000) rmSync(file, { force: true }); } catch {} - 写入本地文件src/computer/vm.js:68
try { appendFileSync(file, `${new Date().toISOString()} ${level} ${args.map((a) => (a instanceof Error ? a.message : String(a))).join(' ')}\n`); } cat - 读取本地文件src/computer/vm.js:82
try { return JSON.parse(readFileSync(stateFile(), 'utf8')); } catch { return {}; } - 写入本地文件src/computer/vm.js:85
try { mkdirSync(dshHome(), { recursive: true }); writeFileSync(stateFile(), JSON.stringify(state, null, 2)); } catch {} - 写入本地文件src/computer/vm.js:85
try { mkdirSync(dshHome(), { recursive: true }); writeFileSync(stateFile(), JSON.stringify(state, null, 2)); } catch {} - 读取本地文件src/computer/vm.js:99
try { return statSync(dir).isDirectory(); } catch { return false; } - 发起网络请求src/computer/vm.js:105
const req = http.get(url, { timeout: 3000, headers }, (res) => { res.resume(); done(res.statusCode ?? 0); }); - 写入本地文件src/computer/vm.js:127
mkdirSync(fallback, { recursive: true }); - 删除本地文件src/computer/vm.js:266
try { rmSync(computer.lockFile, { force: true }); } catch {} - 写入本地文件src/computer/vm.js:281
try { writeFileSync(computer.lockFile, String(process.pid)); } catch {} - 删除本地文件src/computer/vm.js:289
try { rmSync(computer.lockFile, { force: true }); } catch {} - 写入本地文件src/computer/vm.js:337
try { writeFileSync(computer.lockFile, String(process.pid)); } catch {} - 删除本地文件src/computer/vm.js:341
.finally(() => { try { rmSync(computer.lockFile, { force: true }); } catch {} }); - 读取本地文件src/computer/subprocess.js:41
try { return existsSync(this.vm.lockFile); } catch { return false; } - 读取本地文件src/dot/core.js:159
try { data = JSON.parse(readFileSync(file, 'utf8')); } catch (error) { void error; } - 写入本地文件src/dot/core.js:162
set: (k, v) => { data[k] = v; try { writeFileSync(file, JSON.stringify(data, null, 2)); } catch (error) { void error; } }, - 写入本地文件src/dot/core.js:219
mkdirSync(home, { recursive: true }); - 读取本地文件src/interlude/core.js:428
try { return JSON.parse(readFileSync(file, 'utf8')); } catch (error) { void error; return fallback; } - 读取本地文件src/interlude/core.js:434
try { text = readFileSync(file, 'utf8'); } catch (error) { void error; return []; } - 写入本地文件src/interlude/core.js:512
mkdirSync(dir, { recursive: true }); - 读取本地文件src/interlude/core.js:517
if (cfg.identityFile) { try { extra = readFileSync(cfg.identityFile, 'utf8'); } catch (error) { void error; } } - 写入本地文件src/interlude/core.js:528
const saveMeta = () => { try { writeFileSync(files.meta, JSON.stringify(meta, null, 2)); } catch (error) { void error; } }; - 写入本地文件src/interlude/core.js:529
const saveIntents = () => { try { writeFileSync(files.intents, JSON.stringify(intents, null, 2)); } catch (error) { void error; } }; - 写入本地文件src/interlude/core.js:575
try { appendFileSync(files.pending, `${line}\n`); } catch (error) { logger.warn(`pending write failed: ${error.message}`); } - 读取本地文件src/interlude/core.js:585
try { lines = readFileSync(files.web, 'utf8').split('\n').filter(Boolean); } catch (error) { void error; } - 写入本地文件src/interlude/core.js:605
appendFileSync(files.web, `${JSON.stringify({ at: describeNow(new Date()), ...observed })}\n`); - 发起网络请求src/interlude/core.js:646
const r = await web.fetch({ url: b.url }); - 读取本地文件src/interlude/core.js:697
try { lines = readFileSync(files.pending, 'utf8').split('\n').filter(Boolean); } catch (error) { void error; } - 写入本地文件src/interlude/core.js:755
try { appendFileSync(files.calls, `${JSON.stringify({ ts: new Date().toISOString(), phase: `${phase}:refresh`, usage: usageOf(r2.usage), reply: clip(r - 读取本地文件src/interlude/core.js:963
const script = (() => { let t = ''; try { t = readFileSync(files.script, 'utf8'); } catch (error) { void error; } return t.split(/^## /m).filter((x) = - 写入本地文件src/memory/memory.js:202
mkdirSync(dir, { recursive: true }); - 读取本地文件src/memory/memory.js:216
try { memory = normalizeMemory(JSON.parse(readFileSync(files.memory, 'utf8'))); } catch (error) { void error; memory = emptyMemory(); } - 写入本地文件src/memory/memory.js:221
writeFileSync(`${files.memory}.tmp`, JSON.stringify(memory, null, 2)); - 写入本地文件src/memory/memory.js:222
renameSync(`${files.memory}.tmp`, files.memory); - 写入本地文件src/memory/memory.js:223
writeFileSync(files.md, renderMarkdown()); - 读取本地文件src/memory/memory.js:241
try { lines = readFileSync(files.transcript, 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l)); } catch (error) { void error; } - 写入本地文件src/memory/memory.js:248
try { appendFileSync(files.transcript, `${JSON.stringify(line)}\n`); } catch (err) { logger.warn(`transcript write failed: ${err.message}`); } - 写入本地文件src/memory/memory.js:267
writeFileSync(files.transcript, `${[...lines].map((l) => JSON.stringify(l)).join('\n')}\n`); - 读取本地文件src/memory/memory.js:563
try { diary = readFileSync(files.diary, 'utf8').split(/^## /m).filter((x) => x.trim()).map((x) => { const [head, ...body] = x.split('\n'); return { da - 读取本地文件src/phone/phone.js:78
try { return existsSync(settingsFile()) ? JSON.parse(readFileSync(settingsFile(), 'utf8')) : {}; } catch { return {}; } - 读取本地文件src/phone/phone.js:78
try { return existsSync(settingsFile()) ? JSON.parse(readFileSync(settingsFile(), 'utf8')) : {}; } catch { return {}; } - 写入本地文件src/phone/phone.js:82
mkdirSync(dirname(settingsFile()), { recursive: true }); - 写入本地文件src/phone/phone.js:83
writeFileSync(settingsFile(), JSON.stringify(next, null, 2), { mode: 0o600 }); - 写入本地文件src/phone/phone.js:84
try { chmodSync(settingsFile(), 0o600); } catch {} - 删除本地文件src/phone/phone.js:165
try { if (statSync(file).size > 1_000_000) rmSync(file, { force: true }); } catch {} - 读取本地文件src/phone/phone.js:165
try { if (statSync(file).size > 1_000_000) rmSync(file, { force: true }); } catch {} - 写入本地文件src/phone/phone.js:166
const write = (level, args) => { try { appendFileSync(file, `${new Date().toISOString()} ${level} ${args.map((a) => (a instanceof Error ? a.message : - 执行外部命令来自配置/变量src/phone/phone.js:173
execFile(file, args, { maxBuffer: 64 * 1024 * 1024, ...opts }, (err, stdout, stderr) => (err ? reject(Object.assign(err, { stdout, stderr })) : resolv - 写入本地文件src/phone/phone.js:188
mkdirSync(dirname(file), { recursive: true }); - 读取本地文件src/phone/phone.js:190
const tick = setInterval(() => { try { onProgress?.(statSync(part).size, 0); } catch { } }, 5000); - 写入本地文件src/phone/phone.js:192
renameSync(part, file); - 读取本地文件src/phone/phone.js:294
for (const n of readdirSync(f.dir)) if (/^dsh-dot-poster(- +)? png$/.test(n) && join(f.dir, n) !== file) { try { rmSync(join(f.dir, n), { force: tru - 删除本地文件src/phone/phone.js:294
for (const n of readdirSync(f.dir)) if (/^dsh-dot-poster(- +)? png$/.test(n) && join(f.dir, n) !== file) { try { rmSync(join(f.dir, n), { force: tru - 读取本地文件src/phone/phone.js:335
const built = existsSync(ini) ? /^disk dataPartition size=( +)G$/m.exec(readFileSync(ini, 'utf8'))?.[1] : undefined; - 删除本地文件递归src/phone/phone.js:369
rmSync(tmp, { recursive: true, force: true }); - 删除本地文件递归src/phone/phone.js:375
rmSync(paths.downloads, { recursive: true, force: true }); - 读取本地文件src/qq/qq.js:63
export function readSettingsFile() { try { return existsSync(settingsFile()) ? JSON.parse(readFileSync(settingsFile(), 'utf8')) : {}; } catch { return - 读取本地文件src/qq/qq.js:63
export function readSettingsFile() { try { return existsSync(settingsFile()) ? JSON.parse(readFileSync(settingsFile(), 'utf8')) : {}; } catch { return - 写入本地文件src/qq/qq.js:65
function writeSettings(patch) { const next = { ...readSettingsFile(), ...patch }; mkdirSync(dirname(settingsFile()), { recursive: true }); writeFileSy - 写入本地文件src/qq/qq.js:65
function writeSettings(patch) { const next = { ...readSettingsFile(), ...patch }; mkdirSync(dirname(settingsFile()), { recursive: true }); writeFileSy - 写入本地文件src/qq/qq.js:65
function writeSettings(patch) { const next = { ...readSettingsFile(), ...patch }; mkdirSync(dirname(settingsFile()), { recursive: true }); writeFileSy - 读取本地文件src/qq/qq.js:374
try { if (statSync(file).size > 1_000_000) rmSync(file, { force: true }); } catch {} - 删除本地文件src/qq/qq.js:374
try { if (statSync(file).size > 1_000_000) rmSync(file, { force: true }); } catch {} - 写入本地文件src/qq/qq.js:375
const write = (level, args) => { try { appendFileSync(file, `${new Date().toISOString()} ${level} ${args.map((a) => (a instanceof Error ? a.message : - 发起网络请求src/qq/qq.js:836
if (got.isNew) stickers.fetch(got.id).then((ok) => { if (ok) audit({ kind: 'sticker-kept', id: got.id, from: who }); }, (err) => logger.debug(`sticker - 写入本地文件src/qq/qq.js:1067
mkdirSync(dirname(file), { recursive: true }); - 写入本地文件src/qq/qq.js:1068
writeFileSync(file, got.png); - 读取本地文件src/computer/backends/microsandbox.js:36
if (existsSync(bin)) return bin; - 读取本地文件src/computer/backends/microsandbox.js:114
for (const id of readdirSync(snapDir).filter((d) => d.startsWith('snap_'))) { - 读取本地文件src/computer/backends/microsandbox.js:116
const member = JSON.parse(readFileSync(join(snapDir, id, 'group-member.json'), 'utf8')).name; - 读取本地文件src/computer/backends/microsandbox.js:117
const created = JSON.parse(readFileSync(join(snapDir, id, 'snapshot.json'), 'utf8')).capture?.created_at || ''; - 写入本地文件src/computer/audit.js:24
try { if (statSync(file).size > MAX_BYTES) renameSync(file, `${file}.1`); } catch {} - 读取本地文件src/computer/audit.js:24
try { if (statSync(file).size > MAX_BYTES) renameSync(file, `${file}.1`); } catch {} - 写入本地文件src/computer/audit.js:26
appendFileSync(file, `${JSON.stringify({ ts: new Date().toISOString(), ...entry })}\n`); - 读取本地文件src/computer/key.js:30
if (existsSync(file)) { - 读取本地文件src/computer/key.js:31
const k = readFileSync(file, 'utf8').trim(); - 写入本地文件src/computer/key.js:37
mkdirSync(dirname(file), { recursive: true }); - 写入本地文件src/computer/key.js:38
writeFileSync(file, `${key}\n`, { mode: 0o600 }); - 写入本地文件src/computer/key.js:39
try { chmodSync(file, 0o600); } catch {} - 发起网络请求src/computer/live-view.js:103
const up = http.request({ host: gateway.host, port: gateway.port, method: req.method, path: `${rest}${url.search}`, headers: upstreamHeaders(req, key( - 发起网络请求src/computer/live-view.js:126
const up = http.request({ host: gateway.host, port: gateway.port, method: 'GET', path: '/websockify', headers }); - 执行外部命令固定命令src/computer/backends/proxy.js:10
const out = execFileSync('scutil', ['--proxy'], { encoding: 'utf8', timeout: 3000 }); - 执行外部命令固定命令src/computer/backends/proxy.js:16
const out = execFileSync('reg', ['query', key], { encoding: 'utf8', timeout: 3000 }); - 读取本地文件src/qq/stickers.js:17
try { list = JSON.parse(readFileSync(file, 'utf8')); } catch {} - 写入本地文件src/qq/stickers.js:18
const save = () => { try { mkdirSync(dir, { recursive: true }); writeFileSync(file, JSON.stringify(list)); } catch {} }; - 写入本地文件src/qq/stickers.js:18
const save = () => { try { mkdirSync(dir, { recursive: true }); writeFileSync(file, JSON.stringify(list)); } catch {} }; - 删除本地文件src/qq/stickers.js:22
while (list.length > MAX) { const old = list.shift(); try { rmSync(path(old.id), { force: true }); } catch {} } - 读取本地文件src/qq/stickers.js:31
read: (id) => { const s = stickers.get(id); if (!s?.saved) return null; try { return readFileSync(path(id)); } catch { return null; } }, - 发起网络请求src/qq/stickers.js:47
async fetch(id) { - 读取本地文件src/qq/stickers.js:50
if (s.saved && existsSync(path(id))) return true; - 发起网络请求src/qq/stickers.js:52
const res = await fetch(s.url, { signal: AbortSignal.timeout(30_000) }); - 写入本地文件src/qq/stickers.js:56
mkdirSync(dir, { recursive: true }); writeFileSync(path(id), buf); - 写入本地文件src/qq/stickers.js:56
mkdirSync(dir, { recursive: true }); writeFileSync(path(id), buf); - 写入本地文件src/qq/stickers.js:66
mkdirSync(dir, { recursive: true }); writeFileSync(path(id), buf); - 写入本地文件src/qq/stickers.js:66
mkdirSync(dir, { recursive: true }); writeFileSync(path(id), buf); - 读取本地文件src/computer/docker.js:24
return CANDIDATES.find((p) => existsSync(p)) ?? 'docker';
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-subprocess@deepseek-ai/dsh-subprocess-local@deepseek-ai/dsh-llm@deepseek-ai/dsh-mcp-client给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
README
由 DeepSeek 最新模型翻译生成dsh-dot
一个住在自己手机里的角色,为 DeepSeek Harness(dsh)而生。她有自己的 Android 手机(这个插件负责安装并运行它),有自己的账号,有自己的日子:在你和她对话的间隙,她继续生活——写下发生的事,去手机上看看她好奇的东西,有时真的有理由给你发消息。你是她 QQ / 微信 / 飞书……上的联系人之一;dsh-im 负责承载聊天。如果你还想让她干活——shell、脚本、桌面浏览器、定时任务——就给她配一台可选的电脑。
一个插件,六个组件(与其他 dsh 插件共存:见中文部分的「与其他插件共存」):
| 组件 | 是什么 | 依赖 |
| dsh-dot/phone | 她的手机:插件会下载一个 Android 模拟器(模拟器 + platform-tools + 一个系统镜像,约 2 GB,需你先接受 SDK 许可),启动、保活,并以 mcp__dot-phone__ 暴露出来(mobile-mcp 走 adb)。模拟器窗口就是实时画面;她的应用和登录状态都在 AVD 里,重启后依然保留。DSH_DOT_PHONE=off 可关闭。
展开完整文档(README 剩余 1 节)
dsh-dot
一个住在自己手机里的角色,为 DeepSeek Harness(dsh)而生。她有自己的 Android 手机(这个插件负责安装并运行它),有自己的账号,有自己的日子:在你和她对话的间隙,她继续生活——写下发生的事,去手机上看看她好奇的东西,有时真的有理由给你发消息。你是她 QQ / 微信 / 飞书……上的联系人之一;dsh-im 负责承载聊天。如果你还想让她干活——shell、脚本、桌面浏览器、定时任务——就给她配一台可选的电脑。
一个插件,六个组件(与其他 dsh 插件共存:见中文部分的「与其他插件共存」):
| 组件 | 是什么 | 依赖 |
| dsh-dot/phone | 她的手机:插件会下载一个 Android 模拟器(模拟器 + platform-tools + 一个系统镜像,约 2 GB,需你先接受 SDK 许可),启动、保活,并以 mcp__dot-phone__ 暴露出来(mobile-mcp 走 adb)。模拟器窗口就是实时画面;她的应用和登录状态都在 AVD 里,重启后依然保留。DSH_DOT_PHONE=off 可关闭。详情 | 硬件虚拟化(Apple silicon 或 Intel Mac、带 KVM 的 Linux、带 WHPX 的 Windows) |
| dsh-dot/interlude | 幕间,源自 HDS-Interlude:把 Canon(人设;HDSI 预设可用分享码导入)作为提示词的一部分;对话之间的生活——剧本、连续性、意图;去她的手机或浏览器里看看东西(只读,无需审批);可选主动联系。DSH_DOT_INTERLUDE=off 可关闭。详情 | — |
| dsh-dot/qq | 她电脑上的 QQ:NapCat 或 SnowLuma(OneBot 11,/qq backend)跑在电脑 VM 里,和她手机上 QQ 应用是同一个账号;消息一到就叫醒她,她用 qq_send 回复。DSH_DOT_QQ=off 可关闭。详情 | 电脑组件 |
| dsh-dot/memory | 她记得的东西:你、她自己的笔记、她遇到的人、发生过的事;对话超过 150k token 后,把对话交接给新会话。DSH_DOT_MEMORY=off 可关闭。详情 | — |
| dsh-dot/dot | 联系人层:定时结果 → IM,除非回复是 DOT_SILENT;审批和设备通知走 IM;工作规则作为提示词的一部分。DSH_DOT_CONTACT=off 可关闭 | IM 需要 dsh-im;dsh 内置的定时服务 |
| dsh-dot/computer(可选) | 内置 microVM(无需 Docker),带 shell、浏览器、桌面、实时画面、审批关卡和审计日志——干活的隔离边界。DSH_DOT_COMPUTER=off 可关闭。详情 | microsandbox(内置)或 Docker |
dsh plugin --profile desktop add dsh-dot-life @xmanrui/dsh-im # npm; or github:Kevinwu901113/dsh-dot for the source
then, in dsh: /phone accept (Android SDK license → the phone installs itself; /phone accept 32 for 32 GB of storage)
/interlude preset 6QA0-1PD6 (a persona from the HDSI hub; pick your own on typ0.space)
历史:dsh-computer(v0.1–0.5)和 dsh-interlude 原本是独立插件;从 dsh-dot v0.3/v0.4 起,它们作为组件并入这里,git 历史也一并合并。对用户来说,它们没有任何变化。
中文说明
dsh-dot 让 dsh 里住进一个在自己手机上过日子的角色。她有自己的安卓手机(插件自己装、自己开)、自己的账号、自己的作息:你们不聊天的时候她也在过日子——把发生的事写下来、拿起手机去看她好奇的东西、偶尔有个真实的理由找你说句话。你是她 QQ / 微信 / 飞书里的一个联系人(dsh-im 负责通道)。想让她也能干活——shell、脚本、桌面浏览器、定时任务——再给她那台可选的电脑。
一、组成:一个插件、六个组件
| 组件 | 是什么 | 依赖 |
| dsh-dot/phone 她的手机 | Android 模拟器。插件自己从 Google 仓库下载模拟器、platform-tools 和一个系统镜像(约 2 GB,先要你接受 SDK 许可),自己写 AVD(不需要 Java / Android Studio),开机、保活,以 mcp__dot-phone__ 暴露给模型(mobile-mcp 走 adb)。模拟器窗口就是实时画面;App 和登录态在 AVD 里,重启不丢。DSH_DOT_PHONE=off 关掉。详见 docs/phone.md | 硬件虚拟化:Apple silicon / Intel Mac,Linux 要 KVM,Windows 要 WHPX |
| dsh-dot/interlude 幕间 | 按 HDS-Interlude:Canon(人设,HDSI 分享码导入)作为系统提示的一段;两次对话之间的生活——剧本、连续性、打算;想看什么就拿手机(或电脑浏览器)去看,只读、无审批;可选的主动联系。DSH_DOT_INTERLUDE=off 关掉。详见 docs/interlude.md | — |
| dsh-dot/qq 她电脑上的 QQ | NapCat 或 SnowLuma(OneBot 11,/qq backend 切换)跑在她的电脑 VM 里,和手机上的 QQ 同一个账号;消息到的瞬间叫醒她,她用 qq_send 秒回;dot 的提醒和她的主动联系也走这里,官方机器人退为后备。DSH_DOT_QQ=off 关掉(电脑关了它也关)。详见 docs/qq.md | 电脑组件 |
| dsh-dot/memory 她记得的 | 关于你的画像和她自己的笔记(按 Hermes 的做法,有字数上限,她自己能改),她在各个 App 里遇到的人(每人一张卡:互动次数由代码根据真实事件记,印象和事由模型写;只在帖子里互动过的人只记次数和一句印象;关于别人的事先进待核实区),发生过的事(按 dsh-whale-inurqq:碎片带重要度、被说起几次、会老化)。她的对话到 15 万 token 就交班换一个新会话,远早于 dsh 自己在 80% 窗口时的压缩。DSH_DOT_MEMORY=off 关掉。详见 docs/memory.md | — |
| dsh-dot/dot 联系人层 | 定时任务的结果推到 IM(回复只是 DOT_SILENT 就不发)、审批和设备状况的提醒、工作规则作为系统提示的一段。DSH_DOT_CONTACT=off 关掉 | IM 用 dsh-im;定时用 dsh 内置的定时服务 |
| dsh-dot/computer(可选)她的电脑 | 自带 microVM(不需要 Docker),里面有 shell、浏览器、桌面;实时画面、审批闸门、审计日志。干活时的隔离边界。DSH_DOT_COMPUTER=off 关掉。详见 docs/computer.md | microsandbox(自带)或 Docker |
v0.2 之前「电脑」(dsh-computer)和「幕间」(dsh-interlude)是独立插件;v0.3 / v0.4 起并进来做组件,git 历史一起并了。对外没变:dshComputer、~/.dsh/dsh-computer、~/dot/interlude/、/dot /interlude。已经装了它们的,先 dsh plugin --profile desktop remove dsh-computer dsh-interlude 再装新版 dsh-dot。v0.9 起 npm 包名是 dsh-dot-life(dsh-dot 在 npm 上被占位包占着;v0.8.x 曾叫 @kevinwu901113/dsh-dot,已标记 deprecated):之前装过旧名的,先 dsh plugin --profile desktop remove @kevinwu901113/dsh-dot(或 dsh-dot)再装;~/dot、~/.dsh/phone、虚拟机都不用动。v0.9.2 起 MCP 工具名带了命名空间:mcp__vm__ → mcp__dot-vm__,mcp__phone__ → mcp__dot-phone__(见下文「与其他插件共存」);自己写过这些工具名的规则或提示要跟着改。
二、安装
需要 dsh ≥ 0.2.1。先退出 dsh 桌面版,再用一条命令装齐: bash
macOS
"/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" plugin --profile desktop add \
dsh-dot-life @xmanrui/dsh-im
open -a "DeepSeek Harness"
powershell
Windows(PowerShell;需要开启 Windows 虚拟机监控程序平台 WHPX,手机才能跑)
& "$env:LOCALAPPDATA\Programs\DeepSeek Harness\resources\runtime\cli\bin\dsh.cmd" plugin --profile desktop add
dsh-dot-life @xmanrui/dsh-im
Web 版:dsh plugin --profile web add dsh-dot-life。从源码装:把包名换成 github:Kevinwu901113/dsh-dot。
三、第一次设置
1. 手机:在 dsh 里发 /phone accept(默认给手机 16 GB 存储;要别的大小发 /phone accept 32 这样,手机建好后不能再改)——接受 Android SDK 许可后插件开始下载(约 2 GB,进度在 ~/.dsh/dsh-phone.log),装完自动开机,桌面上出现一个手机窗口。/phone 看状态。
2. 装 App、登录:在手机窗口里像用真手机一样操作:从官网下 APK 用 adb install(~/.dsh/phone/platform-tools/adb),或者让她自己装(mobile_install_app 会弹审批)。登录、验证码、人脸都由你在窗口里亲手过——她遇到会停下来叫你。用小号。 各平台的条款都不允许自动化,封了就封了。
3. 人设:/interlude preset <分享码> 从 typ0.space 导入一份 HDSI 预设,或者把 examples/canon.minase-style.json 放到 ~/dot/interlude/canon.json。
4. 她在手机上看什么、玩什么:不用配。常见社区 App(小黑盒、B 站、微博、小红书、知乎、抖音、酷安、贴吧、豆瓣)的站 → 包名表是内置的,只对她自己装了的 App 生效:有 App 的站她用手机去看(只读、无审批、每天有上限),幕间里想点赞 / 评论 / 关注就在那些 App 里做。别的 App 在 profile 的 cordis.patch.yml 里给 dot-interlude 加 phoneApps / playApps。
5. 她的 QQ:/qq install → /qq account <小号> → /qq start → /qq qr,在「手机」标签页里用她手机上的 QQ 扫一扫(码会出现在她的摄像头画面里)确认电脑登录(详见 docs/qq.md)。之后你在 QQ 里给她发消息,1–2 秒叫醒她,她用 qq_send 回。不想用协议端的,按 dsh-im 的说明接一个官方机器人也行。
6. 电脑(可选):默认装上并启动;不想要就在环境里设 DSH_DOT_COMPUTER=off。要公网实时画面按 docs/computer.md 用 tailscale serve。
四、她一天是怎么过的
- 你们聊天:dsh 自己跑对话,dsh-im 接 IM;她的人设(Canon)和工作规则是系统提示里的两段,不需要 AGENTS.md。
- 聊天安静下来 idleMinutes 后、或每 advanceEveryMinutes 一次(只在 activeHours 内)、或某个打算到了时间:幕间一轮——一次模型调用,她写下这段时间过的日子、更新连续性快照、记下打算,顺便说她想去看点什么(browserIntents)。
- 去看:有 App 的站用手机(phoneApps),登录过的网站用电脑浏览器(vmSites),其他先走 dsh 的 web 服务;被挡了就自己去看。实现是往她最近的会话里追加一条「[幕间观察]」消息,她用 mcp__dot-phone__ / mcp__dot-vm__browser_ 自己看,最后那段回复作为 webContext 进入之后的幕间。这一轮不算幕,也没有审批——只读工具直接放行,输入、安装、下单一类直接拒绝。
- 跑腿会话:拿起手机、App 通知、幕间的「去看」和「玩」都在她的另一个会话「dot · 跑腿」里跑,截图和屏幕元素不进你们的对话;跑腿有结果时,只在她的对话里留一句「[刚才拿起手机看了一眼] …」(不叫醒她,下一轮她就知道)。跑腿会话超过 6 万 token 自动换新。QQ / 微信的消息和你的话照常直接叫醒她的对话。
- 拿起手机:手机没响,她也会隔一阵拿起来看一眼(phoneCheckEveryMinutes,默认 30,最近没被叫醒过时):有没回的消息就回,App 里有新动态就看,没事就 DOT_SILENT 回桌面。/dot check 立刻来一次。
- 手机响了:任何 App 的新通知(QQ 消息、小黑盒的更新、以后她自己装的任何 App)都会叫醒她:[手机通知] QQ · 宽:在吗,她自己去 App 里回,或者不理。按 App 静音:/phone notify mute <包名>。
- 玩(默认开,DSH_DOT_PLAY=off 关):幕间里她想「给那篇点个赞 / 评论一句 / 关注他」,就变成一条 [想玩会儿手机],她用手机去做——只在她自己装了的 App 里。玩多少不是配额,是她自己的习惯(记忆 self 栏里她自己记下的);发帖 / 私信 / 花钱不在允许的动作里。守则是 Moltbook 给智能体定的:参与 > 创造,真有话说才发。
- 找你:speak: true 时,幕间里 contact 过四道闸(活跃时段、意愿值、每日上限、有投递目标)才通过 dsh-im 发出去。
- 干活(装了电脑时):你说「每天早上看看 X 有没有更新」,她建定时任务;到点在电脑里查,有新内容推到 IM,没有就 DOT_SILENT;危险操作弹审批卡,附实时画面链接。
五、文件和命令
| ~/dot/interlude/ | 人设 canon.json、剧本 script.md、连续性 state.json、打算 intents.json、看到的东西 web-context.jsonl、每轮原始回复和 token 用量 calls.jsonl |
| ~/dot/memory/ | memory.md(给你看的:关于你、她的笔记、认识的人、待核实、碎片)、diary.md(她每天 23:30 写的日记)、memory.json、真实发生过的对话 transcript.jsonl、交班记录 handoffs.jsonl、每次整理的原始回复 calls.jsonl |
| ~/.dsh/phone/ | 模拟器、platform-tools、系统镜像、AVD(avd/dot.avd/);~/.dsh/dsh-phone.log 日志、dsh-phone-audit.jsonl 审计、dsh-phone.json 设置 |
| ~/.dsh/dsh-computer | 电脑的日志、审计、状态 |
| /phone [accept\|restart\|screenshot] · /interlude [now\|advance\|seed\|preset \|canon] · /dot [test\|check] · /dot memory [now\|diary\|handoff] · /dot people [approve <id>\|drop <id>\|forget <名字>] | 各组件的状态和操作 |
| 右侧栏 dot 标签页(会话顶部 ◎ 按钮) | 一页看全:手机 / 电脑 QQ / 电脑 状态、她的对话交班进度、她此刻在做什么和打算、今天的轮数 / 玩 / 去看 / 叫醒 / token、记忆用量、日记、最近两段剧本。只读,3 秒刷新;页面由 dsh-dot/dot 挂在 dsh 自己的 web server 上(/dsh-dot/panel/),不开新端口 |
| 安全边界 | 手机实时画面与控制(/dsh-dot/phone/)和 dot 面板都挂在 dsh 的 web server 上,每个请求先过 dsh 的 connection 服务(dsh 登录态 + Host/Origin 围栏,不信任任何转发头)——插件不自带鉴权、不开自己的端口。电脑的实时桌面同样挂在 dsh 的 web server 上(/dsh-dot/computer/,含 VNC 的 WebSocket),由插件带密钥中继给虚拟机网关;网关端口(127.0.0.1:18080)只绑本机回环且要求密钥(SANDBOX_API_KEY,每台机器自动生成在 ~/.dsh/dsh-computer.key),只有插件内部持有。若要从外面看,请暴露 dsh 本身而不是这个端口 |
dot 的通知相关:notifyBatchMs(1 秒内的通知合成一次叫醒)、notifyHours(可选的静音时段,默认不设:任何时候来通知都叫醒她);她正在跑一轮时来的通知会攒到那轮结束,同一个对话的多条按顺序列出,一次叫醒。每个组件的配置项见各自的文档:phone、interlude、computer;dot 自己只有 home、scope、imBot / imTarget、autoUnblockDaily。
六、原则
- 不重复造轮子:手机用 Google 官方模拟器和 mobile-mcp,电脑用 microsandbox,IM 用 dsh-im,定时用 dsh 内置的,人设用 HDSI 社区的预设,幕间的提示词按 HDSI 原文。
- 她只能看,不能瞒着你动:幕间的「去看」是只读的;有后果的动作(装 App、输密码、付款、给没找过她的人发消息、把她设备上的文件发出去、碰钥匙和登录态文件)走审批,或者根本不给。
- 「你」只有一个来源:电脑端 QQ 里 /qq owner 指定的号。手机通知上的昵称、微信名谁都能改,所以它们永远不算你的指令。
- 可审计:她看到什么、为什么没出声、哪次工具被拒了,calls.jsonl 和两份审计日志里都有。
- 用小号。
七、与其他插件共存
dsh-dot 尽量不占公共名字,但有几处是全局的,装别的插件前先看一眼:
- 电脑组件开着时会改全局设置(cordis.patch.yml 顶部三段):本机 subprocess 服务商换成虚拟机里的那个(dot-subprocess),sandbox-policy 设成 danger-full-access(虚拟机才是隔离边界),权限预设整套改写成 computer / computer-auto。所以不要同时装别的沙箱、subprocess 或权限预设插件;要用它们,就 DSH_DOT_COMPUTER=off,这三处恢复 dsh 默认。
- MCP 工具名带命名空间:虚拟机的工具是 mcp__dot-vm__,手机是 mcp__dot-phone__(serverName 为 dot-vm / dot-phone;dsh 规范化工具名时 - 合法,原样保留)。别的插件挂一个叫 vm 或 phone 的 MCP 不会再撞名。前缀定义在 src/tool-prefixes.js,改名只改一处。
- 每个组件都能单独关,在启动 dsh 的环境里设:
| 组件 | 关掉 |
| computer(连带 qq) | DSH_DOT_COMPUTER=off |
| phone | DSH_DOT_PHONE=off |
| qq | DSH_DOT_QQ=off |
| memory | DSH_DOT_MEMORY=off |
| interlude | DSH_DOT_INTERLUDE=off |
| dot(联系人层) | DSH_DOT_CONTACT=off |
组件之间只通过 ctx.get(...)?. 找彼此,缺了哪个其余的照常跑(比如关了 dot,记忆不再自动交班;关了 memory,通知里不带人物卡)。
- 记忆共存模式:dot-memory 的 coexist(环境变量 DSH_DOT_MEMORY_COEXIST),默认 auto——发现另一个记忆服务(otherMemoryServices,默认 hermesMemory、autoMemory、dshMemory、memoryBank;不含过于通用的 memory,遇到别的名字自己加;cordis 没有公开的服务枚举,所以是名单)就打一行 info 日志,切到 people-only:人物卡、碎片、日记照旧,但关于他 / 她自己记下的两栏不再写进系统提示,整理时也不再改这两栏,免得两个插件一起「讲记忆」。full 强制全开,people-only 强制只留人物。
- 一个 QQ 号只能有一个应答者:她电脑上的 NapCat 已经用这个号收发消息,不要再在同一个号上跑 qq-bridge 或别的 OneBot 机器人,否则两边都会回。
- 上下文压缩插件:dsh-dot 注入会话的消息带固定的 source,压缩时请保留(或至少别把它们当成用户原话):
- { kind: 'dsh-dot', form: 'phone-notification' } 手机通知叫醒她
- { kind: 'dsh-dot', form: 'phone-check' } 定时看一眼手机
- { kind: 'dsh-dot', form: 'errand-note' } 跑腿会话做完的事回写到主对话
- { kind: 'dsh-interlude', form: 'observation' } 幕间观察
- { kind: 'dsh-interlude', form: 'play' } 幕间「玩」
- { kind: 'handoff', generation } 记忆交班时注入新会话的交接
- 来自她 QQ 上主人的消息是 { kind: 'user', rpcId: 'qq-…' },即普通用户消息。
- bash 也过 dsh-dot 的电脑闸门(dot-gate 的 shellTools 含 bash),别的插件再给 bash 加审批,同一条命令会弹两次审批。
八、路线
现状、分期和待定的决定见 docs/ROADMAP.md。
九、许可证
AGPL-3.0-only。幕间组件按原文使用 HDS-Interlude(AGPL-3.0)的提示词,所以整个包按 AGPL 发布;v0.3 之前以 MIT 发布的 computer / dot 代码,那些版本仍按 MIT 可用。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
Kevinwu901113/dsh-dot
一个住在自己手机里的角色,为 DeepSeek Harnessdsh而生。她有自己的 Android…
相关插件 · 平台集成
智能体工程课walkinglabs/learn-harness-engineering
一门项目制课程,教你搭建环境、管理状态、做验证、加控制机制,让 AI 编程智能体稳定干活。
★ 21,155已汉化DSH
71.7
视频翻译配音liuzhao1225/YouDub-webui
开源视频本地化工作台:导入视频,识别、翻译并逐句克隆原声,生成字幕和配音成片。
★ 5,792已汉化Cordis
71.4
失败经验库Ikalus1988/MisakaNet
📚 零依赖、由 git 支撑的微课程库,让 AI 智能体异步分享并检索经过验证的调试经验。| https://misakanet.org
★ 550已汉化DSH
71.2
桌面智能体工作区freestylefly/wesight
WeSight 是开源桌面控制台,面向本地 AI 智能体。它帮你安装或复用 Claude…
★ 933已汉化MCP
71
IM 机器人接入xmanrui/dsh-im
让 DeepSeek Harness 触手可及 Connecting DeepSeek Harness
★ 3,110已汉化DeepSeek 客户端
70.9
中国平台研究引擎Jesseovo/last30days-skill-cn
给 AI Agent 的中文互联网研究 Skill:检索 8 大平台最近 30…
★ 2,031已汉化DSH
70.7
这是你的仓库?认领条目