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

鸿蒙端运行环境 sol5766/dshm

DeepSeek Harness
sol576626 天前有提交已提供中文文档扫描:含敏感能力

在 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
装得上吗
实装验证未通过(unknown),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 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
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

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

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 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、附上复现步骤、或者把「已知边界」里少写的那条补给我们 —— 同样是实实在在的帮忙。谢谢。
预览分享卡片长什么样 ↓sol5766/dshm
分享 · DeepSeek Harness Hub
sol5766/dshm

在 HarmonyOS 上自足运行 DeepSeek Harness 的应用

回到顶部