把一款 AI agent 应用搬上鸿蒙,通常只有两条老路:要么用 ArkUI 把界面重写一遍,从此背上一个永久 fork;要么把整个 Electron 塞进 HAP 里,为 JIT 去申请一个受限权限。
DSH Desktop 的 HarmonyOS Edition 选了第三条路:让系统 ArkWeb 来渲染官方界面,让进程内的 Node 来承载智能体宿主。 界面零重写、上游零补丁、安装不需要任何受限权限。
一、它是什么
先交代背景:DeepSeek Harness(dsh)是开源的 AI agent 运行框架,采用「一切皆插件」的架构——模型适配器、工具注册表、会话日志、甚至 agent 主循环本身,都是可替换的插件。它自带一套相当完整的 Web UI。
DSH Desktop 要做的,是把这个 Web UI 变成一等公民的应用形态。它一共有三个 edition,共用同一套外壳架构、同一条数据面:
| Edition | 运行时 | 平台 |
|---|---|---|
dsh-desktop | Electron 官方运行时 | Windows / Linux / macOS |
dsh-desktop-hos | Electron 37 on HarmonyOS | 鸿蒙 2in1 / 平板 |
dsh-desktop-ark(本篇) | 系统 ArkWeb + 进程内 Node | 鸿蒙 2in1 / 平板 |
三个 edition 的差别,只在由哪个运行时提供 Node 与渲染器。本篇讲的 dsh-desktop-ark,就是完全不含 Electron 的那一个:系统 ArkWeb 渲染 + 进程内 Node v24.2.0。
先交代现状:版本 0.2.1(versionCode 201),已在 HarmonyOS 真机上验证。零受限权限装机、载荷自解包并 ready、垫片全部安装、node:sqlite / FTS5 全文检索可用、文本与图片对话、三根目录工作区选择、git 克隆、插件市场安装/卸载,这些都在真机上跑过。
为什么执着于「零改动」?因为 dsh 还在 developer preview,上游动得很勤。一个外壳钉一个上游 tag,升级就是重跑产物管线,而不是重做补丁。 一旦把界面重写一遍,就等于把上游最活跃的部分永久 fork 进自己的仓库。这是很具体的工程判断,不是情怀。
二、鸿蒙上的两个版本
鸿蒙上有两个独立版本,壳架构、未改动的 Web UI、数据面完全相同,差别只在由谁提供 Node:
| HarmonyOS Edition(本篇) | Electron on HarmonyOS 版 | |
|---|---|---|
| 渲染 | 系统 ArkWeb,不随包带内核 | Electron 37 内置 Chromium |
| Node | 应用进程内 libnode.so(v24.2.0) | Electron 运行时内置(Node 22.17) |
| 上游 dsh 补丁 | 0 条(profile + bundle + 已审计垫片) | 13 个源码补丁 |
| 安装要求 | 无(默认无 JIT) | 需 JIT 受限权限(ACL) |
| 包体 | 约 88 MB | Electron 体量(数百 MB) |
这张表最扎眼的是后两行:补丁从 13 条降到 0 条,包体小了一个数量级,而且不需要任何受限权限。
两者不是替代关系,是两种成本结构。Electron 版用自带 Chromium 与 JIT,换来最接近桌面的保真度;本版用较小的包体与免权限安装,换来「平台差异由一层垫片兜住」的灵活度。
三、三条核心机制
整个方案能成立,靠的是三条机制:
1. 同源数据面。 Node 在进程内启动 dsh Host,把 webserver 绑到 127.0.0.1 的随机端口,拿到带一次性 token 的入口 URL,ArkWeb 直接 loadUrl() 同源加载。token 换签名 cookie,之后页面走 /api + WebSocket。零 CORS、零 Host 头改写、零自定义协议。
2. 适配层放在上游扩展点里。 新增一个 ohos profile + 自有 bundle,把 dsh 的能力 seam(命令执行、shell、沙箱、目录选择、文件系统等)换成鸿蒙实现。上游零源码补丁是设计目标,平台性缺陷走「带标记的垫片」。
3. 宿主能力走同进程桥。 ArkTS 与 Node 之间用文件信箱桥(真机实测往返约 2ms);不向网页注入任何 preload,桌面能力全部由 Host 侧插件经 RPC 提供。
这三条里,第一条是整个设计最值钱的事实:dsh 的 webserver 本来就同源服务页面和 API,所以数据面不需要 CORS、不需要鉴权、不需要自定义协议,也不需要新的 IPC 载体。这也是「上游代码保持原样」能成立的根本原因。
四、现在能用的,和明确不能用的
先给能用的(都是真机验证过的):
| 能力 | 状态 |
|---|---|
| dsh Web UI 在窗口内运行(100% 复用 dsh 前端) | ✅ |
会话持久化 + 全文检索(node:sqlite + FTS5) | ✅ |
| 文件读写 / 编辑、图片附件、文本与图片对话 | ✅ |
| 三根目录工作区选择(沙箱 / 文档 / 下载)+ 授权门 | ✅ |
Git(21 个工具,纯 JS,设备无需 git 二进制) | ✅ |
| 插件市场(内置 dshmarket,安装 / 卸载闭环) | ✅ |
| 零受限权限装机(默认无 JIT 形态) | ✅ |
再诚实地交代当前边界:
| 能力 | 状态 |
|---|---|
| 命令执行(bash 工具) | ⚠️ 执行器已交付,路径解析修复待落地 |
| 内容搜索(grep / glob) | ⚠️ 需要纯 JS 后端(arm64 上没有 ripgrep 二进制) |
| 删除 / 移动 / 复制 / chmod | ⚠️ 上游 ctx.fs 尚无这些原语 |
| 托盘图标、窗口状态记忆 | — 本版不提供 |
| PTY 真终端、进程沙箱 | ❌ 平台无 /dev/ptmx、无原生 launcher |
「不能用的」不是没做,而是做不了或按产品算账决定不做。比如 PTY:鸿蒙没有 /dev/ptmx,所以没有交互式终端面板,但命令执行本身仍可通过常驻 sh + 哨兵行协议来做。这些边界必须说清,否则容易变成错误的期待。
五、零 ACL:免受限权限的关键
这是本版和 Electron 版最本质的分野。
Electron 要跑,就得有 JIT;要有 JIT,就得申请 ohos.permission.kernel.ALLOW_WRITABLE_CODE_MEMORY 这个受限权限——它只对平板 / 2in1 开放,而且审核、上架都卡这一道门。
本版的答案是:默认跑无 JIT 的 compat 形态,用 --jitless 关闭 V8 的可执行内存分配,从而声明面收敛为零 ACL。 代价是 V8 退化为纯解释器执行,而且 WebAssembly 直接不存在(不是变慢,是 typeof WebAssembly === 'undefined')。
这带来一个致命问题:Node 内置的 fetch 依赖 WASM 实现的 HTTP 解析器,无 JIT 下「UI 能打开,一说话就失败」——因为模型调用走的就是全局 fetch。
于是本工程补了一个纯 JS 的 fetch 兼容层:基于原生 node:http/https + node:stream/web 的 ReadableStream,覆盖 SSE / FormData / 重定向 / AbortSignal,零第三方依赖。同时封印 node:http 的惰性 getter,阻断 undici 的初始化。这个兼容层只在 WebAssembly 不存在时才安装,WASM 可用时一个动作都不做。
所以本版是双形态:
| 形态 | JIT | ACL | 说明 |
|---|---|---|---|
compat(默认) | 无(--jitless) | 零 ACL | 解释器执行,纯 JS fetch 兼容层 |
jit | 有 | 需 1 项 ACL | 完整 V8 + WASM,性能最好 |
两种形态由编译期常量切换,运行期不做探测。这是踩坑得来的结论:权限位不是 JIT 可用性的预检——一旦误判,进程内 Node 会 SIGTRAP 崩溃,而它每进程只能启动一次,无法重启。
六、插件市场与进程内 pnpm
鸿蒙沙箱有个很「不讲情面」的约束:应用域 fork 出来的子进程会被 seccomp 直接杀掉,沙箱又禁止 symlink / hardlink。 这让「装插件」这件在桌面上稀松平常的事,在鸿蒙上变得很麻烦。
本版的解法是:构建期把纯 JS 的 pnpm 引擎物化进载荷;运行期在同一个进程的 worker 线程里跑 pnpm。 不 spawn 子进程、不建符号链接、不改市场与上游源码。
配置上刻意选择了 hoisted + copy 布局来绕开链接限制,ignoreScripts 关闭插件作者代码的执行(既是平台约束,也是安全属性)。于是设备上没有 pnpm 二进制,也能在插件市场里安装 / 卸载 npm 插件——这条链路已在真机闭环。
一个诚实的代价:因为 node::Start 每进程只能一次、ArkWeb 的客户端模块图固定,插件装卸是「重启生效」。装卸即持久化、重启即生效,这是真机上唯一稳定的路径。
七、真机验证:怎么证明它真的跑起来了
真机验证不是「看一眼能不能打开」。跑过的是完整链路:装机、载荷自解包、Host 就绪、文本与图片对话、三根目录工作区授权、git 克隆、插件市场安装/卸载。
设备是华为 MateBook Pro 2in1(HarmonyOS 6.x / 7.x,API 24–26)。几个值得记一笔的事实:
- 净启耗时:装机后首启 60–75 秒(解包 1.5 万个文件约 13 秒);命中幂等标记后冷启约 3 秒到 ready。
- 包体:HAP 约 88 MB(含约 42 MB 载荷归档),离 AppGallery 的上限很远。
node:sqlite+ FTS5 在真机直接可用,全文搜索不需要另编原生插件。
调试过程中还踩掉了一批「鸿蒙特有」的坑,最典型的一个:ArkWeb 输入即黑屏——默认的异步渲染模式下,系统输入法窗口出现/关闭后,Web 的图形 surface 会脱离合成树,截图整片黑、DOM 却正常。改用同步渲染模式后键入恢复。这类坑没有捷径,只能真机一条条撞出来。
八、架构:一张图
整个工程的骨架,一张图能画完:
┌─ 鸿蒙 HAP ────────────────────────────────────────────┐
│ ArkTS 外壳(窗口 + ArkWeb + 加载/错误/诊断视图) │
│ │ loadUrl(同源 http://127.0.0.1:<随机端口>) │
│ │ 宿主桥(文件信箱,往返约 2ms) │
│ ┌───▼───────────────────────────────────────────────┐ │
│ │ Node(进程内,libnode.so / node::Start) │ │
│ │ ├─ 垫片安装(带标记 + 自检) │ │
│ │ ├─ runProfile('ohos') │ │
│ │ ├─ Host:webserver + /api + connection │ │
│ │ └─ 鸿蒙 seam 插件(shell/subprocess/picker…) │ │
│ └───────────────────────────────────────────────────┘ │
│ 资源:resfile 载荷 tar.gz + libs/libnode.so │
└─────────────────────────────────────────────────────────┘
平台差异被收敛成三层:YAML patch 层(关 HMR、换 seam 行)、seam 插件包(鸿蒙实现)、带标记垫片(fs.link/fs.symlink 回退、flock 替身、sharp 替身等)。每一条垫片都有唯一标记、自检断言、登记理由与撤销条件——这是「零补丁」能长期成立、且经得起审计的原因。
九、怎么装到自己的机器上
三步:
1. 准备环境。 DevEco Command Line Tools(SDK API 24)、同级子模块 deepseek-harness、Node 18+、pnpm、HDC。Node 运行时来自 OpenHarmony 三方库中心仓的 @fellow99/node-bridge 与 @fellow99/node-runtime,ohpm install 自动拉取。
2. 跑构建。
node scripts/assemble-payload.mjs --deploy --apply # 物化 dsh 运行树
bash scripts/build-hap.sh # ohpm install → 载荷归档 + HAP 构建/签名
3. 安装并启动。
hdc app install -r entry/build/default/outputs/default/entry-default-signed.hap
默认构建是零 ACL 的 compat 形态,侧载不需要任何受限权限;如需 JIT 形态,用 scripts/set-node-mode.mjs jit 切换并重新构建(需已获可写代码内存权限)。
写在最后
DSH Desktop HarmonyOS Edition 的核心,其实只有一句话:抓住「dsh 的 webserver 本来就同源服务页面和 API」这个事实,然后老老实实把平台差异收敛成 profile + bundle + 一层可审计的垫片。
它换来的东西很具体:官方 Web UI 一个字节没改、上游零源码补丁、包体约 88 MB、安装零受限权限、升上游 tag 只需重跑管线。代价也很清楚:无 JIT 形态下解释器性能会打折,命令执行、内容搜索、文件变更原语等能力还需继续补齐。
如果你想自己动手,代码和文档都在这里:
- 鸿蒙 Edition(本篇):github.com/fellow99/ds…
- Electron on HarmonyOS 版:github.com/fellow99/ds…
- 桌面工程(Windows / Linux / macOS):github.com/fellow99/ds…
- 上游 DeepSeek Harness:github.com/deepseek-ai…
如果你也在折腾「鸿蒙上跑 AI agent 应用」,希望这篇能帮你少走一点弯路。后面我会继续把真机调试、ACL 申请、沙箱适配这些脏活写细。
感谢各位关注,欢迎访问我的GitHub主页:fellow99.github.io/