在 HarmonyOS 上自足运行 DeepSeek Harness 的应用
46.4/100
综合分
31
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/9 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
该插件未发布到 npm,按作者文档从 GitHub 源安装即可(静态检查只覆盖 npm 路径,不代表插件不可用)。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add sol5766/dshm该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:需留意
是什么
dsh 原生插件 · market
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 26 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
⚠︎ 实装验证未通过(unknown · 2026/10/21) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装仓库 package.json 标记 private,未发布到 npm,需从源码安装;安装清单取自 hostcore/plugins/dshm-office-system-preview/package.json
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包@deepseek-ai/dshm-office-system-preview(未发布到 npm,仅可源码安装)
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/2 04:35:10
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 193 处,展示前 85 条)
- 发起网络请求devrel/bili-discover.mjs:224
res = await fetch(url, { - 读取本地文件devrel/bili-discover.mjs:659
const src = readFileSync(SELF_PATH, 'utf8'); - 读取本地文件devrel/bili-discover.mjs:731
console.log(readFileSync(SELF_PATH, 'utf8').split('*/')[0].replace(/^#!.* /, '')); - 写入本地文件devrel/bili-discover.mjs:849
mkdirSync(dir, { recursive: true }); - 写入本地文件devrel/bili-discover.mjs:866
writeFileSync(join(dir, 'candidates.json'), JSON.stringify(payload, null, 2), 'utf8'); - 写入本地文件devrel/bili-discover.mjs:867
if (!opts.jsonOnly) writeFileSync(join(dir, 'report.md'), markdown, 'utf8'); - 发起网络请求hostcore/app/dshm-installer.js:95
const req = https.get(urlStr, { - 读取本地文件hostcore/app/dshm-installer.js:401
return JSON.parse(fs.readFileSync(p, 'utf8')); - 写入本地文件hostcore/app/dshm-installer.js:408
fs.mkdirSync(path.join(profileDir, 'node_modules'), { recursive: true }); - 读取本地文件hostcore/app/dshm-installer.js:433
entries = fs.readdirSync(dir, { withFileTypes: true }); - 读取本地文件hostcore/app/dshm-installer.js:506
entries = fs.readdirSync(nm, { withFileTypes: true }); - 读取本地文件hostcore/app/dshm-installer.js:518
inner = fs.readdirSync(path.join(nm, e.name), { withFileTypes: true }); - 写入本地文件hostcore/app/dshm-installer.js:599
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); - 读取本地文件hostcore/app/dshm-installer.js:684
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - 写入本地文件hostcore/app/dshm-installer.js:700
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8'); - 写入本地文件hostcore/app/dshm-installer.js:775
fs.mkdirSync(tmpRoot, { recursive: true }); - 读取本地文件hostcore/app/dshm-installer.js:813
if (fs.existsSync(path.join(dest, 'package.json'))) { - 删除本地文件hostcore/app/dshm-installer.js:838
fs.rmSync(dest, { recursive: true, force: true }); - 删除本地文件hostcore/app/dshm-installer.js:844
fs.rmSync(tmp, { recursive: true, force: true }); - 删除本地文件hostcore/app/dshm-installer.js:886
fs.rmSync(tmp, { recursive: true, force: true }); - 删除本地文件hostcore/app/dshm-installer.js:913
fs.rmSync(tmp, { recursive: true, force: true }); - 删除本地文件hostcore/app/dshm-installer.js:923
fs.rmSync(tmp, { recursive: true, force: true }); - 删除本地文件hostcore/app/dshm-installer.js:932
fs.rmSync(tmp, { recursive: true, force: true }); - 写入本地文件hostcore/app/dshm-installer.js:936
fs.mkdirSync(path.dirname(finalDest), { recursive: true }); - 写入本地文件hostcore/app/dshm-installer.js:937
fs.renameSync(realRoot, finalDest); - 读取本地文件hostcore/app/dshm-skills.js:37
return crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex'); - 读取本地文件hostcore/app/dshm-skills.js:46
return fs.statSync(p).isFile(); - 读取本地文件hostcore/app/dshm-skills.js:56
names = fs.readdirSync(dstDir); - 删除本地文件hostcore/app/dshm-skills.js:65
fs.rmSync(path.join(dstDir, name), { force: true }); - 读取本地文件hostcore/app/dshm-skills.js:88
names = fs.readdirSync(srcDir); - 写入本地文件hostcore/app/dshm-skills.js:94
fs.mkdirSync(dstDir, { recursive: true }); - 写入本地文件hostcore/app/dshm-skills.js:116
fs.copyFileSync(src, tmp); - 写入本地文件hostcore/app/dshm-skills.js:117
fs.renameSync(tmp, dst); - 删除本地文件hostcore/app/dshm-skills.js:121
fs.rmSync(tmp, { force: true }); - 发起网络请求hostcore/app/fetch-shim.js:634
const req = mod.request(target, options, (res) => { - 读取本地文件hostcore/app/jitless-env.cjs:135
if (String(libsDir).length === 0 || !fs.existsSync(libsDir)) { - 写入本地文件hostcore/app/main.js:94
diagStream = fs.createWriteStream(DIAG_LOG, { flags: 'a' }); - 写入本地文件hostcore/app/main.js:114
try { fs.appendFileSync(DIAG_LOG, text); } catch (e) { } - 写入本地文件hostcore/app/main.js:482
fsProf.mkdirSync(PROF_DIR, { recursive: true }); - 写入本地文件hostcore/app/main.js:484
fsProf.writeFileSync(file, JSON.stringify(r.profile)); - 读取本地文件hostcore/app/main.js:527
dshmJsTid = String(fs.readFileSync('/proc/thread-self/stat', 'utf8')).trim().split(' ')[0]; - 写入本地文件hostcore/app/main.js:530
try { fs.writeFileSync(dshmTidFile, dshmJsTid + '\n'); } catch (e) { } - 读取本地文件hostcore/app/main.js:1026
if (!fs.existsSync(statePath)) { - 读取本地文件hostcore/app/main.js:1029
const state = JSON.parse(fs.readFileSync(statePath, 'utf8')); - 启用终端会话(PTY)hostcore/app/main.js:1277
const term = pty.spawn('/system/bin/sh', ['-c', 'echo DSHM_PTY_OK'], - 写入本地文件hostcore/app/main.js:1357
fs.writeFileSync(path.join(HOME_DIR, 'host-ready.json'), JSON.stringify(payload, null, 2) + '\n', 'utf8'); - 读取本地文件hostcore/app/main.js:1388
const payload = JSON.parse(fs.readFileSync(file, `utf8`)); - 读取本地文件hostcore/app/main.js:1420
if (!fs.existsSync(p)) { - 读取本地文件hostcore/app/main.js:1423
const parsed = JSON.parse(fs.readFileSync(p, 'utf8')); - 删除本地文件hostcore/app/main.js:1582
fs.unlinkSync(probe); - 删除本地文件hostcore/app/main.js:1591
try { fs.unlinkSync(probe); } catch (e) { } - 删除本地文件hostcore/app/main.js:1769
fs.rmSync(dst, { force: true }); - 删除本地文件hostcore/app/main.js:1966
fs.rmSync(full, { recursive: true, force: true }); - 删除本地文件hostcore/app/main.js:3877
fs.rmSync(dst, { force: true }); - 执行外部命令来自配置/变量hostcore/app/main.js:3927
c = spawn(p, args, { stdio: ['ignore', 'ignore', 'pipe'], env }); - 执行外部命令来自配置/变量hostcore/app/main.js:4104
r = spawnSync(file, args, Object.assign({ encoding: 'utf8', timeout: 10000 }, opts || {})); - 删除本地文件hostcore/app/main.js:4265
fs.rmSync(path.join(TOOLCHAIN_DIR, 'py-stage'), { recursive: true, force: true }); - 执行外部命令来自配置/变量hostcore/app/main.js:4377
child = spawn(path.join(binDir, 'busybox'), ['ash', '-c', cmds.join('\n')], { - 发起网络请求hostcore/app/main.js:4932
const req = http.request({ host: '127.0.0.1', port: port, path: '/', method: 'GET', timeout: 2000 }, (res) => { - 写入本地文件hostcore/app/tar-gz.cjs:203
fs.mkdirSync(target, { recursive: true }); - 写入本地文件hostcore/app/tar-gz.cjs:208
fs.mkdirSync(path.dirname(target), { recursive: true }); - 写入本地文件hostcore/app/tar-gz.cjs:209
fs.writeFileSync(target, entry.body.subarray(0, entry.size)); - 读取本地文件hostcore/app/tar-gz.cjs:243
const raw = zlib.gunzipSync(fs.readFileSync(archivePath)); - 发起网络请求hostcore/app/undici-shim.mjs:180
export async function fetch(input, init) { - 写入本地文件hostcore/app/worker-bootstrap.cjs:35
fs.appendFileSync(path.join(dir, 'dshm-host.log'), text); - 读取本地文件hostcore/speech-models/index.js:82
if (!existsSync(p)) return false; - 读取本地文件hostcore/speech-models/index.js:84
try { st = statSync(p); } catch { return false; } - 读取本地文件hostcore/speech-models/index.js:89
return createHash('sha256').update(readFileSync(p)).digest('hex') === asset.sha256; - 写入本地文件hostcore/speech-models/index.js:117
mkdirSync(dir, { recursive: true }); - 删除本地文件hostcore/speech-models/index.js:120
rmSync(tmp, { force: true }); - 发起网络请求hostcore/speech-models/index.js:122
const res = await fetch(asset.url, { signal }); - 删除本地文件hostcore/speech-models/index.js:168
rmSync(tmp, { force: true }); - 写入本地文件hostcore/speech-models/index.js:174
renameSync(tmp, target); - 写入本地文件hostcore/speech-provider/index.js:98
appendFileSync(join(queueDir, 'requests.log'), - 写入本地文件hostcore/speech-provider/index.js:132
mkdirSync(queueDir, { recursive: true }); - 写入本地文件hostcore/speech-provider/index.js:237
writeFileSync(tmpPath, payload, 'utf8'); - 写入本地文件hostcore/speech-provider/index.js:238
renameSync(tmpPath, reqPath); - 写入本地文件hostcore/speech-provider/index.js:283
mkdirSync(modelDir, { recursive: true }); - 写入本地文件hostcore/speech-provider/index.js:295
writeFileSync(join(queueDir, 'model-dir-diag.txt'), lines.join('\n') + '\n', 'utf8'); - 读取本地文件hostcore/speech-provider/index.js:487
if (existsSync(donePath)) { - 读取本地文件hostcore/speech-provider/index.js:492
if (existsSync(failPath)) { - 读取本地文件hostcore/speech-provider/index.js:509
const parsed = JSON.parse(readFileSync(path, 'utf8')); - 读取本地文件hostcore/speech-provider/index.js:522
if (existsSync(p)) unlinkSync(p); - 删除本地文件hostcore/speech-provider/index.js:522
if (existsSync(p)) unlinkSync(p); - 执行外部命令来自配置/变量hostkit/src/server.mjs:622
const result = spawnSync(
扫描于 2026/11/2(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 鸿蒙PC客户端
README
由 DeepSeek 最新模型翻译生成DSHM
在 HarmonyOS 上自足运行 DeepSeek Harness 的应用
DSHM 把 DeepSeek Harness(dsh)的运行本体装进一个鸿蒙应用:HAP 内自带 Node 运行时与 dsh 核心树,核心在本机 127.0.0.1 上起 Host,应用内的原生 ArkUI 页面就是这个本地 Host 的客户端。
它不是 PC 上 dsh 的遥控器,也不需要在电脑上常驻任何服务:装上即用,数据只在本机应用沙箱内。
支持 HarmonyOS 手机 / 折叠屏 / 平板 / 2in1。
这是一次平台移植:把官方面向桌面(Electron)的运行时,搬到 OpenHarmony arm64 上自足运行。
移植过程中与鸿蒙平台语义的每一处冲突、根因与修法,都汇总在 docs/70-鸿蒙移植踩坑与修复总览.md(按技术主题组织)。
两条根本约束决定了后面几乎所有设计:jitless ⇒ WebAssembly === undefined,以及 execve 受签名域管辖 ⇒ 所有端侧原生二进制必须构建期自签名。
为什么需要它:官方没有鸿蒙端
上游 DeepSeek Harness 面向的是桌面端(Windows / macOS,Electron)与 CLI。
展开完整文档(README 剩余 1 节)
DSHM
在 HarmonyOS 上自足运行 DeepSeek Harness 的应用
DSHM 把 DeepSeek Harness(dsh)的运行本体装进一个鸿蒙应用:HAP 内自带 Node 运行时与 dsh 核心树,核心在本机 127.0.0.1 上起 Host,应用内的原生 ArkUI 页面就是这个本地 Host 的客户端。
它不是 PC 上 dsh 的遥控器,也不需要在电脑上常驻任何服务:装上即用,数据只在本机应用沙箱内。
支持 HarmonyOS 手机 / 折叠屏 / 平板 / 2in1。
这是一次平台移植:把官方面向桌面(Electron)的运行时,搬到 OpenHarmony arm64 上自足运行。
移植过程中与鸿蒙平台语义的每一处冲突、根因与修法,都汇总在 docs/70-鸿蒙移植踩坑与修复总览.md(按技术主题组织)。
两条根本约束决定了后面几乎所有设计:jitless ⇒ WebAssembly === undefined,以及 execve 受签名域管辖 ⇒ 所有端侧原生二进制必须构建期自签名。
为什么需要它:官方没有鸿蒙端
上游 DeepSeek Harness 面向的是桌面端(Windows / macOS,Electron)与 CLI。它的运行前提有两条,在鸿蒙应用沙箱里一条都不成立:
1. 系统里有可执行的 Node 运行时;
2. 进程能直接 execve 任意二进制。
本仓库做的事,就是在官方未覆盖的平台上把这两条前提逐条补出来,并提供一个原生宿主:
| 官方形态依赖的前提 | 鸿蒙端侧现状 | 本仓库的补法 |
| 系统提供 Node 运行时 | 沙箱内无 Node,且只能执行自己签名域内的原生二进制 | 运行时与原生库随包分发(Node、koffi、node-pty、sherpa-onnx、libvips 等),构建期自签名;Host 跑在应用进程内,只监听 127.0.0.1 |
| 上游直接 import('undici') 可用 | 端侧 jitless ⇒ WebAssembly === undefined,而 undici 的 HTTP 解析器是 WASM | 增加 undici 模块名解析钩子与全局 fetch 垫片,不改上游源码、不改核心树,由 tools/check-web-fetch-jitless.mjs 守着(自带对照实验) |
| 桌面工具链随手可用 | 沙箱里没有可执行文件 | git、Python 3.12、busybox、ripgrep 随包分发并自签名,启动时逐项 exec 探测 |
| Electron 提供窗口 / 托盘 / 文件选择器 | 平台是 ArkUI | 由 ArkUI 宿主提供窗口、菜单、托盘、系统文件选择器、剪贴板、通知与多形态布局 |
| 单机单形态 | 手机、折叠屏、平板、2in1 共用一份安装包 | 形态由断点判定,四种形态共享同一套信息架构 |
具体落成四件事:
- 运行时不依赖系统,随包分发。 HAP 内自带 Node 运行时与全部原生库,构建期完成自签名以进入应用签名域。代价是包体(约 300 MiB),换来"装上就能跑,不必先配环境"。
- 核心树可切换、可回滚。 dsh 核心树以归档形式随包分发,首次启动解包进应用数据目录下的版本仓库(dsh/cores/<version>),通过激活事务切换版本,失败可回滚 ⇒ 升级上游 = 换一个核心归档,App 代码不动。
- 宿主重写,界面不重写。 界面内容仍是官方 Web 前端;ArkUI 侧只承担平台宿主职责,设计令牌从官方 --dsw- 映射到鸿蒙语义色与系统符号,不做 Web CSS 的像素级翻译——官方主题更新时不需要重做整套界面。
- 上架友好、数据保全优先。 权限共 11 项(6 项普通 + 5 项 ACL 受限,逐项见 entry/src/main/module.json5),不含 JIT / 可写可执行内存一类(无 ohos.permission.kernel.、无 ALLOW_WRITABLE_CODE_MEMORY);装机一律 hdc install -r 覆盖安装,应用数据原地保留(详见下文「真机数据保全」)。
端侧与官方的逐项对照(哪些对齐、哪些存在边界)登记在 docs/parity-matrix.md,由 tools/check-parity.mjs 强制维护;移植过程中踩到的坑按主题汇总在 docs/70-鸿蒙移植踩坑与修复总览.md。
已知边界
- WASM 缺席是全局前提。 凡是上游直接 import('undici') 的功能都必须走垫片;新接入的上游功能若未覆盖,在端侧会直接失败。垫片覆盖由门禁守住,不靠自觉。
- 长连接抖动:官方网关默认心跳间隔(2 s)对有重计算的会话偏紧,可能表现为连接短暂中断后自动恢复;这是 profile 层参数,可按机器调整。
- 冷启自退:曾在旧核心版本上出现一次(启动约 40 s 后 process.exit(0)),当前版本长时间连续运行未复现,仍在定位中。
能力
| 领域 | 说明 |
| 本地核心 | 内置 Node 运行时(自建、jitless)与 dsh 核心树;Host 仅监听 127.0.0.1,随应用生命周期起停 |
| 对话与轨迹 | 对话视图(问答,思考过程折叠在回答上方)与轨迹视图(工具调用、子代理、目标/任务、交付物、错误)分开呈现 |
| 工作区 | 工作区为组、会话挂在组下;可在设备上选择文件夹作为工作区(系统文件夹选择器),并在其中浏览文件 |
| 模型与密钥 | 按提供方管理:API 密钥(只写)、baseURL 与模型目录;默认模型与推理强度可选 |
| 插件 | 查看随包插件清单与运行阶段;按行启用/禁用,并可恢复部署默认 |
| 核心版本 | 同时安装多个核心版本,一键切换 / 回滚(停旧起新,逐版本校验后激活) |
| 多语言 | 界面文案跟随系统语言,默认中文 |
| 上架友好 | 全部按 jitless 运行,不申请 JIT / 可写可执行内存(无 ohos.permission.kernel.、无 ALLOW_WRITABLE_CODE_MEMORY);权限共 11 项 = 6 项普通(网络、网络信息、后台保活、终止前回调、持久化数据、麦克风)+ 5 项 ACL 受限(用户文件读写、文件访问持久化、全盘访问、自定义沙箱、外部原生代码加载)——ACL 这 5 项都服务于「用户可见目录读写 + 自定义沙箱」,随签名 profile 的 acls 声明,上架时按 ACL 清单在应用市场申请(逐项见 entry/src/main/module.json5) |
架构
┌─ DSHM(一个 HAP)──────────────────────────────────────────────┐ │ ArkUI 原生页面(客户端) │ │ │ HTTP /api/ WebSocket /api/remote.mux │ │ ▼ │ │ 端侧 dsh Host(Node 运行时运行在本应用进程内) │ │ │ DSH_HOME = <应用沙箱>/dsh/home(跨版本共享的唯一数据) │ │ ▼ │ │ 核心版本仓库(多个版本可并存,切换 = 停旧 + 起新 + 校验) │ └─────────────────────────────────────────────────────────────────┘
同进程带来的是安全语义的简化:客户端与 Host 走回环,不需要把服务暴露到局域网,也不需要跨设备转发。
端侧运行时的硬约束:jitless ⇒ 没有 WASM
不申请 JIT 权限意味着 Host 全程以 --jitless 运行,而 V8 的 --jitless 与 --expose_wasm 互斥 (启动即打印 disabling flag --expose_wasm),因此端侧 typeof WebAssembly === 'undefined'——这是恒定的,不是配置问题。由此推出一条对上游代码的判据:
凡是上游直接 import('undici') 的功能,在端侧都会失败,因为 undici 的 HTTP 解析器是 WASM (lib/llhttp/llhttp-wasm.js)。
已实测证实的一个实例:dsh-web-fetch-http 不用全局 fetch,而是 await import('undici') 自建 Agent 再传 dispatcher,于是 web_fetch 打不开任何网页和 IP,而走本仓纯 JS node:http 垫片的 web_search 照常工作。
同一核心树、同一个本地 HTTP 服务下,只有 --jitless 一个变量就能复现两组结果(有 WASM → 200;无 WASM → fetch failed / WebAssembly is not defined)。细节与在飞修复见 docs/parity-matrix.md §3.2。
因此新增/升级核心版本时,除了跑既有门禁,还要搜一遍核心树里对 undici 的直接依赖,并把垫片覆盖率当作一项验收项。
对付它需要两层,缺一层就会出现「Host 起来了、模型也能回话,但某个工具静默坏掉」:
| 层 | 做什么 | 不做的后果 |
| 全局 fetch 垫片(fetch-shim.js) | 用 node:http/https(原生 llhttp)重写 fetch/Request/Response/Headers/FormData | 调模型就走不通(dsh 调模型就是用 fetch) |
| undici 模块名解析钩子(undici-shim.mjs + undici-loader.mjs) | 让上游的 await import("undici") 拿到同一个垫片,并把 dispatcher 翻译成 lookup | web_fetch 打不开任何网页(web_search 却正常,因为后者走第一层) |
第二层由 main.js 的 installUndiciNameHook() 在 WebAssembly 不可用时注册;不改上游源码、不改核心树。
这条路径由 tools/check-web-fetch-jitless.mjs 守着——它自带对照实验:不注册钩子时必须失败(并给出 WASM 因果证据),注册后必须全过,且跨源跳转仍须被拒。
构建
前置:DevEco Command Line Tools(含 hvigor / ohpm / codelinter / SDK)、JDK 17、Node.js (仅用于仓库内的构建与检查脚本)、Python 3(tools/sign-tar-elf.py 用它的 tarfile 改写工具链归档——Windows 上只有它能在保住归档内 symlink 的前提下改字节;缺失时 place-toolchain 会告警跳过签名,归档仍可用,只是端侧 git/python3.12 真身继续被执行策略拒绝)。
SDK 版本口径:compatibleSdkVersion / targetSdkVersion 固定为 6.1.1(24)(决策,2026-09-14)。
代价是不能用 API 26 的「沉浸光感」材质(官方要求 targetAPIVersion ≥ 26)——界面层次当前由系统阴影表达;升级路径与要改的几处列在 appstate/ui/HarmonyTheme.ets 的 HarmonyMaterial。
不入库的产物(新克隆必须先备齐,否则编不过):
| 产物 | 路径 | 说明 |
| Node 头文件 | entry/src/main/cpp/node-headers/ | 编 libdshhost 只需要它(libnode 不参与链接) |
| 原生库 | entry/libs/<abi>/ | 运行期需要(含 libnode.so.137);也是「编不编 koffi/flock」的门 |
| git 兼容垫片 | entry/libs/arm64-v8a/libdshm-gitcompat.so | 由 CMake 从 entry/src/main/cpp/gitcompat.c(源码在库里)编出;git 子进程类命令的 LD_PRELOAD 垫片(见 docs/device-validation.md 批次备注十三 三) |
| 核心包 | entry/src/main/resources/resfile/.zip | 首启解包出端侧核心树 |
| 工具链归档 | entry/src/main/resources/resfile/toolchain/{python,git}/ | 由 node tools/place-toolchain.mjs 从 third_party/ 放置并自签名(端侧 execve 只放行带 .codesign 的 ELF) |
| 入口脚本 | entry/src/main/resources/resfile/resources/app/ | 由 node tools/place-host-app.mjs 从 hostcore/app/ 生成 |
| 鲸鱼 SVG | entry/src/main/resources/base/media/fish_logo.svg | 由 node tools/gen-fish-logo.mjs 从核心树的官方 dsh-client-ui-primitives 逐字节复制路径生成(Image.fillColor 按主题着色) |
1) 打完整体(HAP + 原生库 + 内置核心资源) devecocli build
2) 需要重新打包核心树时(素材来自 dist/core/,产物落到应用资源目录)
node tools/pack-core.mjs --skip-install --place-in-app
node tools/place-host-app.mjs
2b) 需要重建工具链归档时(放置 + 自签名;会就地改写 resfile/toolchain/ 下的归档)
node tools/place-toolchain.mjs
2c) 重建顶栏鲸鱼 SVG 资源(从核心树的官方 primitives 包逐字节复制路径;内含 "真实包围盒 == 官方 viewBox" 与"落盘后仍与官方一致"两条自检,失败即退出)
node tools/gen-fish-logo.mjs
2d) 重建 APP 图标(官方鲸鱼 + HM/OS 角标)。需要 Python 3 + Pillow。
⚠ 它只写 APP 图标:AppScope 与 entry 的 {foreground,background}.png、 docs/brand/dshm-icon.png、dshm-mark.png。不写 startIcon.png / logo_dark.png —— 那两个是启动画面资源,必须保持原版(正本归档在 third_party/brand-original/)。
全部自检通过才写盘;失败则一个文件都不动(含"归档比对"与"mark 不得带角标"两项)。
python tools/make-icon.py
3) 安装到已连接设备(注意:产物是 unsigned;签名材料不在库内) hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
设计体系:Web 语义 + HarmonyOS 原生表达
产品语义对齐官方 Web,视觉与交互用 HarmonyOS 原生表达——不是把 Web 的 CSS 机械翻译成 ArkUI:
| 层 | 落点 | 作用 |
| 尺度原语 | appstate/ui/Tokens.ets | Sp / Radius / Border / Fs / Sz / Dur(「有哪些档位」) |
| 语义令牌 | appstate/ui/HarmonyTheme.ets | 角色 → 系统语义资源(sys.color.)+ 排版成套角色 + 层级/动效/触控;WEB_TOKEN_MAP 逐条映射官方 --dsw-* |
| 原生原语 | entry/src/main/ets/view/NativePrimitives.ets | NativeChip / NativeCard / NativeButton / NativeActionBar / NativeSectionTitle + Sheet 参数助手 |
四条已经定下来的规则(都是踩过或查证后写的,不是偏好):
1. 浮层用系统形态:半模态一律 bindSheet(NativePrimitives.harmonySheetOptions 统一参数),不再手写「整屏 Column + 自制遮罩」。原因:原生 Sheet 自带「非全屏、底层父视图可见」、拖拽关闭、遮罩与键盘避让——手写那套要逐个补回来,且必然补不全。
注意 bindSheet 是组件属性,同一节点只能绑一个。本项目因此用单一浮层宿主:全应用只在根节点挂一次 bindSheet,由 sheetKind()(从既有状态派生,不另立字段)
决定显示哪个浮层、closeSheet() 一处复位。这样结构上不可能同时开出两个浮层,
复位逻辑也只写一次。
2. 遮罩交给系统:不再手写 rgba(...) 遮罩(35% 黑在深色主题下观感就是错的),全部由原生 Sheet 提供。
3. 图标用系统符号:SymbolGlyph 只支持系统预置资源,不引入 Web SVG(这条是 API 约束,不是偏好)。
4. 裸值只许变少:tools/check-design-tokens.mjs 是棘轮门禁,管的是 fontSize/圆角/描边/颜色字面量(含 rgb()/rgba()/hsl()——首版漏检过,已补)。
仓库结构
| 目录 | 作用 |
| entry/ | 鸿蒙应用入口:ArkUI 页面与视图(含原生原语 view/NativePrimitives.ets)、原生桥(libdshhost)、随包资源(核心包与原生库) |
| hostcore/ | 端侧 Host 的入口脚本与配置档(cordis.patch.yml)、fetch 垫片,以及为绕开「端侧无 WASM」而做的 undici 模块名解析钩子 |
| hostruntime/ | 核心版本仓库、激活事务、运行时载体(RuntimePort → NodeRuntime) |
| appstate/ | 客户端状态中枢与投影(会话、轨迹、工作区、设置、凭据、插件、核心视图)。纯逻辑层(不 import ArkUI,可在本机直接测):设计令牌与布局/导航决策(ui/Tokens、ui/HarmonyTheme、ui/Breakpoints、ui/LayoutController、ui/NavigationController)、回合与时间线、贴底跟随、输入模态(策略与事实)、工具呈现与改动对照。由 tools/check-layout-fixtures.mjs 逐条断言 |
| platform/ | 系统能力封装(文件选择、剪贴板、通知、窗口记忆等) |
| dshcompat/ | 与上游协议有关的全部事实:端点、参数形状、事件类型与投影键 |
| tools/ | 构建与检查脚本(核心打包、依赖闭包、上架红线、协议往返、死按钮扫描等) |
| docs/ | 文档基线,索引见 docs/README.md;接手先读 docs/HANDOFF.md(现状 + 未完成事项 + Windows 端重装步骤) |
设计原则
1. 不 fork、不魔改 dsh:端侧差异只通过 dsh 自己的组合面(配置档 / cordis.patch.yml / bundle)表达。
2. 不申请 JIT / 可写可执行内存:需要它们作前提的方案一律不进入选型(这是 --jitless 的由来)。ACL 受限权限只在文件访问与自定义沙箱必需处申请——当前 5 项(用户文件读写、文件访问持久化、全盘访问、自定义沙箱、外部原生代码加载),随签名配置档的 acls 声明,逐项见 entry/src/main/module.json5。
3. 界面不撒谎:失败必须给出下一步;空态说明「可以做什么」;不可用的动作把原因写在旁边,而不是给一个点了没反应的入口。
4. 上游知识只出现在 dshcompat:字段名、端点形状、事件类型集中一处,升级上游时改一个地方。
5. 产品语义跟 Web,视觉与交互用鸿蒙原生:信息架构与行为对齐官方 Web(对等矩阵逐行登记),但视觉不用 Web CSS 的像素级翻译——用系统语义色、系统符号、原生控件(Sheet/菜单)、触控目标与多窗口语义表达。
这样官方主题更新时不需要重做整套界面。
快速开始
只想装上用
到本仓库 Releases 下载最新的 DSHM-1.1.0-core0.2.1-alpha.1+dshm.13-arm64-signed.hap,然后:
hdc install -r DSHM-1.1.0-core0.2.1-alpha.1+dshm.13-arm64-signed.hap
首次启动会解包核心树(约 60–90 秒),期间停在启动页,属正常。
下载后请用同目录的 SHA256SUMS.txt 校验。
Release 里附的是 debug 签名的侧载包,供尝鲜与验证;正式分发请用 DevEco 生成你自己的发布签名。
想从源码构建
见上面的「构建」一节。注意本仓库不含原生库、核心包、工具链归档、入口脚本、Node 头文件与签名材料(判据见「不入库的产物」表)—— 它们是构建产物或机器绑定材料,不入库。
真机数据保全(动手前务必读)
绝对不要执行 hdc uninstall / bm uninstall,或任何删除 el2 下用户数据的命令。
真机 el2(files/dsh/home)里的会话 / 插件 / 工作区没有任何可用备份通道 (hdc 读不到、smode 被拒、run-as 不存在),删掉不可恢复。
装机一律用覆盖安装 —— 它只换 el1(代码与资源),不动 el2(用户数据):
hdc install -r <hap>
判据:这条命令会不会碰 el2?会,就是禁止的。
完整事故复盘与流程见 docs/80-真机更新与数据保全.md。
许可
见 LICENSE。
本项目是移植:把官方面向桌面的 dsh 运行时搬到鸿蒙。dsh / DeepSeek Harness 的商标与上游代码版权归其各自所有者,本仓库只包含客户端侧的实现、适配与文档。
赞赏支持
DSHM 是业余时间做的移植:官方的 dsh 运行时没有鸿蒙端,端侧差异、沙箱绕行、手持档适配,以及两百多条回归门禁,都是照着真机读数一条条抠出来的。
项目完全免费、没有付费墙 —— 赞赏不会改变任何功能取舍,也不会影响 issue 的处理顺序。
只是如果它正好省了你的事,愿意请我喝杯咖啡,我会很受用。
微信支付宝
提 issue、附上复现步骤、或者把「已知边界」里少写的那条补给我们 —— 同样是实实在在的帮忙。谢谢。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
sol5766/dshm
在 HarmonyOS 上自足运行 DeepSeek Harness 的应用
相关插件 · 市场 / 管理
插件精选列表awesome-dsh-plugin/awesome-dsh-plugin
DeepSeek Harness(dsh)插件精选列表
★ 22,025未汉化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,358已汉化DSH
71.9
插件市场目录imsai-sh/awesome-deepseek-harness-plugins
面向 DeepSeek Harnessdsh生态的社区插件目录,共收录 13938 个插件含 PR 收录与…
★ 350已汉化DSH
71.8
插件精选清单Anil-matcha/awesome-dsh-plugin
DeepSeek Harnessdsh精选指南——DeepSeek…
★ 1,013已汉化DSH
71.7